Skip to content
GitLab
Projects
Groups
Snippets
Help
Loading...
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
Sith
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
59
Issues
59
List
Boards
Labels
Service Desk
Milestones
Merge Requests
9
Merge Requests
9
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Operations
Operations
Incidents
Environments
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
AE
Sith
Commits
7879b6dd
Commit
7879b6dd
authored
Oct 11, 2017
by
Skia
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
core: add family graphs
Signed-off-by:
Skia
<
skia@libskia.so
>
parent
47bace20
Pipeline
#1352
passed with stage
in 5 minutes and 41 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
221 additions
and
30 deletions
+221
-30
core/templates/core/user_godfathers.jinja
core/templates/core/user_godfathers.jinja
+6
-0
core/templates/core/user_godfathers_tree.jinja
core/templates/core/user_godfathers_tree.jinja
+54
-0
core/urls.py
core/urls.py
+2
-0
core/views/user.py
core/views/user.py
+100
-15
locale/fr/LC_MESSAGES/django.po
locale/fr/LC_MESSAGES/django.po
+59
-15
No files found.
core/templates/core/user_godfathers.jinja
View file @
7879b6dd
...
...
@@ -6,6 +6,8 @@
{%
endblock
%}
{%
block
content
%}
<p><a
href=
"
{{
url
(
"core:user_godfathers_tree_pict"
,
user_id
=
profile.id
)
}}
?family"
>
{%
trans
%}
Show family picture
{%
endtrans
%}
</a></p>
{%
if
profile.godfathers.exists
()
%}
<h4>
{%
trans
%}
Godfathers
{%
endtrans
%}
</h4>
<ul>
...
...
@@ -14,6 +16,8 @@
{{
u.get_mini_item
()
|
safe
}}
</a>
{{
delete_godfather
(
user
,
profile
,
u
,
True
)
}}
</li>
{%
endfor
%}
</ul>
<p><a
href=
"
{{
url
(
"core:user_godfathers_tree"
,
user_id
=
profile.id
)
}}
"
>
{%
trans
%}
Show ancestors tree
{%
endtrans
%}
</a></p>
{%
else
%}
<p>
{%
trans
%}
No godfathers
{%
endtrans
%}
{%
endif
%}
...
...
@@ -25,6 +29,8 @@
{{
u.get_mini_item
()
|
safe
}}
</a>
{{
delete_godfather
(
user
,
profile
,
u
,
False
)
}}
</li>
{%
endfor
%}
</ul>
<p><a
href=
"
{{
url
(
"core:user_godfathers_tree"
,
user_id
=
profile.id
)
}}
?descent"
>
{%
trans
%}
Show descent tree
{%
endtrans
%}
</a></p>
{%
else
%}
<p>
{%
trans
%}
No godchildren
{%
endtrans
%}
{%
endif
%}
...
...
core/templates/core/user_godfathers_tree.jinja
0 → 100644
View file @
7879b6dd
{%
extends
"core/base.jinja"
%}
{%
block
title
%}
{%
if
param
==
"godchildren"
%}
{%
trans
user_name
=
profile.get_display_name
()
%}{{
user_name
}}
's godchildren
{%
endtrans
%}
{%
else
%}
{%
trans
user_name
=
profile.get_display_name
()
%}{{
user_name
}}
's godfathers
{%
endtrans
%}
{%
endif
%}
{%
endblock
%}
{%
macro
display_members_list
(
user
)
%}
{%
if
user.__getattribute__
(
param
)
.
exists
()
%}
<ul>
{%
for
u
in
user.__getattribute__
(
param
)
.
all
()
%}
<li>
<a
href=
"
{{
url
(
"core:user_godfathers"
,
user_id
=
u.id
)
}}
"
>
{{
u.get_short_name
()
}}
</a>
{%
if
u
in
members_set
%}
{%
trans
%}
Already seen (check above)
{%
endtrans
%}
{%
else
%}
{{
members_set.add
(
u
)
or
""
}}
{{
display_members_list
(
u
)
}}
{%
endif
%}
</li>
{%
endfor
%}
</ul>
{%
endif
%}
{%
endmacro
%}
{%
block
content
%}
<p><a
href=
"
{{
url
(
"core:user_godfathers"
,
user_id
=
profile.id
)
}}
"
>
{%
trans
%}
Back to family
{%
endtrans
%}
</a></p>
{%
if
profile.__getattribute__
(
param
)
.
exists
()
%}
{%
if
param
==
"godchildren"
%}
<p><a
href=
"
{{
url
(
"core:user_godfathers_tree_pict"
,
user_id
=
profile.id
)
}}
?descent"
>
{%
trans
%}
Show a picture of the tree
{%
endtrans
%}
</a></p>
<h4>
{%
trans
u
=
profile.get_short_name
()
%}
Descent tree of
{{
u
}}{%
endtrans
%}
</h4>
{%
else
%}
<p><a
href=
"
{{
url
(
"core:user_godfathers_tree_pict"
,
user_id
=
profile.id
)
}}
?ancestors"
>
{%
trans
%}
Show a picture of the tree
{%
endtrans
%}
</a></p>
<h4>
{%
trans
u
=
profile.get_short_name
()
%}
Ancestors tree of
{{
u
}}{%
endtrans
%}
</h4>
{%
endif
%}
{{
members_set.add
(
profile
)
or
""
}}
{{
display_members_list
(
profile
)
}}
{%
else
%}
{%
if
param
==
"godchildren"
%}
<p>
{%
trans
%}
No godchildren
{%
endtrans
%}
{%
else
%}
<p>
{%
trans
%}
No godfathers
{%
endtrans
%}
{%
endif
%}
{%
endif
%}
{%
endblock
%}
core/urls.py
View file @
7879b6dd
...
...
@@ -61,6 +61,8 @@ urlpatterns = [
url
(
r
'^user/(?P<user_id>[0-9]+)/$'
,
UserView
.
as_view
(),
name
=
'user_profile'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/pictures$'
,
UserPicturesView
.
as_view
(),
name
=
'user_pictures'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/godfathers$'
,
UserGodfathersView
.
as_view
(),
name
=
'user_godfathers'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/godfathers/tree$'
,
UserGodfathersTreeView
.
as_view
(),
name
=
'user_godfathers_tree'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/godfathers/tree/pict$'
,
UserGodfathersTreePictureView
.
as_view
(),
name
=
'user_godfathers_tree_pict'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/godfathers/(?P<godfather_id>[0-9]+)/(?P<is_father>(True)|(False))/delete$'
,
DeleteUserGodfathers
,
name
=
'user_godfathers_delete'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/edit$'
,
UserUpdateProfileView
.
as_view
(),
name
=
'user_edit'
),
url
(
r
'^user/(?P<user_id>[0-9]+)/profile_upload$'
,
UserUploadProfilePictView
.
as_view
(),
name
=
'user_profile_upload'
),
...
...
core/views/user.py
View file @
7879b6dd
...
...
@@ -28,7 +28,7 @@ from django.contrib.auth import views
from
django.utils.translation
import
ugettext
as
_
from
django.core.urlresolvers
import
reverse
from
django.core.exceptions
import
PermissionDenied
,
ValidationError
from
django.http
import
Http404
from
django.http
import
Http404
,
HttpResponse
from
django.views.generic.edit
import
UpdateView
from
django.views.generic
import
ListView
,
DetailView
,
TemplateView
from
django.forms.models
import
modelform_factory
...
...
@@ -233,20 +233,6 @@ class UserView(UserTabsMixin, CanViewMixin, DetailView):
current_tab
=
'infos'
def
DeleteUserGodfathers
(
request
,
user_id
,
godfather_id
,
is_father
):
user
=
User
.
objects
.
get
(
id
=
user_id
)
if
((
user
==
request
.
user
)
or
request
.
user
.
is_root
or
request
.
user
.
is_board_member
):
ud
=
get_object_or_404
(
User
,
id
=
godfather_id
)
if
is_father
==
"True"
:
user
.
godfathers
.
remove
(
ud
)
else
:
user
.
godchildren
.
remove
(
ud
)
else
:
raise
PermissionDenied
return
redirect
(
'core:user_godfathers'
,
user_id
=
user_id
)
class
UserPicturesView
(
UserTabsMixin
,
CanViewMixin
,
DetailView
):
"""
...
...
@@ -274,6 +260,20 @@ class UserPicturesView(UserTabsMixin, CanViewMixin, DetailView):
kwargs
[
'pictures'
][
album
.
id
].
append
(
pict_relation
.
picture
)
return
kwargs
def
DeleteUserGodfathers
(
request
,
user_id
,
godfather_id
,
is_father
):
user
=
User
.
objects
.
get
(
id
=
user_id
)
if
((
user
==
request
.
user
)
or
request
.
user
.
is_root
or
request
.
user
.
is_board_member
):
ud
=
get_object_or_404
(
User
,
id
=
godfather_id
)
if
is_father
==
"True"
:
user
.
godfathers
.
remove
(
ud
)
else
:
user
.
godchildren
.
remove
(
ud
)
else
:
raise
PermissionDenied
return
redirect
(
'core:user_godfathers'
,
user_id
=
user_id
)
class
UserGodfathersView
(
UserTabsMixin
,
CanViewMixin
,
DetailView
):
"""
Display a user's godfathers
...
...
@@ -305,6 +305,91 @@ class UserGodfathersView(UserTabsMixin, CanViewMixin, DetailView):
kwargs
[
'form'
]
=
UserGodfathersForm
()
return
kwargs
class
UserGodfathersTreeView
(
UserTabsMixin
,
CanViewMixin
,
DetailView
):
"""
Display a user's family tree
"""
model
=
User
pk_url_kwarg
=
"user_id"
context_object_name
=
"profile"
template_name
=
"core/user_godfathers_tree.jinja"
current_tab
=
'godfathers'
def
get_context_data
(
self
,
**
kwargs
):
kwargs
=
super
(
UserGodfathersTreeView
,
self
).
get_context_data
(
**
kwargs
)
if
"descent"
in
self
.
request
.
GET
:
kwargs
[
'param'
]
=
"godchildren"
else
:
kwargs
[
'param'
]
=
"godfathers"
kwargs
[
'members_set'
]
=
set
()
return
kwargs
class
UserGodfathersTreePictureView
(
CanViewMixin
,
DetailView
):
"""
Display a user's tree as a picture
"""
model
=
User
pk_url_kwarg
=
"user_id"
def
build_complex_graph
(
self
):
import
pygraphviz
as
pgv
self
.
depth
=
int
(
self
.
request
.
GET
.
get
(
"depth"
,
4
))
if
self
.
param
==
"godfathers"
:
self
.
graph
=
pgv
.
AGraph
(
strict
=
False
,
directed
=
True
,
rankdir
=
"BT"
)
else
:
self
.
graph
=
pgv
.
AGraph
(
strict
=
False
,
directed
=
True
)
family
=
set
()
self
.
level
=
1
# Since the tree isn't very deep, we can build it recursively
def
crawl_family
(
user
):
if
self
.
level
>
self
.
depth
:
return
self
.
level
+=
1
for
u
in
user
.
__getattribute__
(
self
.
param
).
all
():
self
.
graph
.
add_edge
(
user
.
get_short_name
(),
u
.
get_short_name
())
if
u
not
in
family
:
family
.
add
(
u
)
crawl_family
(
u
)
self
.
level
-=
1
self
.
graph
.
add_node
(
self
.
object
.
get_short_name
())
family
.
add
(
self
.
object
)
crawl_family
(
self
.
object
)
def
build_family_graph
(
self
):
import
pygraphviz
as
pgv
self
.
graph
=
pgv
.
AGraph
(
strict
=
False
,
directed
=
True
)
self
.
graph
.
add_node
(
self
.
object
.
get_short_name
())
for
u
in
self
.
object
.
godfathers
.
all
():
self
.
graph
.
add_edge
(
u
.
get_short_name
(),
self
.
object
.
get_short_name
())
for
u
in
self
.
object
.
godchildren
.
all
():
self
.
graph
.
add_edge
(
self
.
object
.
get_short_name
(),
u
.
get_short_name
())
def
get
(
self
,
request
,
*
args
,
**
kwargs
):
self
.
object
=
self
.
get_object
()
if
"descent"
in
self
.
request
.
GET
:
self
.
param
=
"godchildren"
elif
"ancestors"
in
self
.
request
.
GET
:
self
.
param
=
"godfathers"
else
:
self
.
param
=
"family"
if
self
.
param
==
"family"
:
self
.
build_family_graph
()
else
:
self
.
build_complex_graph
()
# Pimp the graph before display
self
.
graph
.
node_attr
[
'color'
]
=
"lightblue"
self
.
graph
.
node_attr
[
'style'
]
=
"filled"
main_node
=
self
.
graph
.
get_node
(
self
.
object
.
get_short_name
())
main_node
.
attr
[
'color'
]
=
"sandybrown"
main_node
.
attr
[
'shape'
]
=
"rect"
if
self
.
param
==
"godchildren"
:
self
.
graph
.
graph_attr
[
'label'
]
=
_
(
"Godchildren"
)
elif
self
.
param
==
"godfathers"
:
self
.
graph
.
graph_attr
[
'label'
]
=
_
(
"Godfathers"
)
else
:
self
.
graph
.
graph_attr
[
'label'
]
=
_
(
"Family"
)
img
=
self
.
graph
.
draw
(
format
=
"png"
,
prog
=
"dot"
)
return
HttpResponse
(
img
,
content_type
=
"image/png"
)
class
UserStatsView
(
UserTabsMixin
,
CanViewMixin
,
DetailView
):
"""
...
...
locale/fr/LC_MESSAGES/django.po
View file @
7879b6dd
...
...
@@ -6,7 +6,7 @@
msgid
""
msgstr
""
"Report-Msgid-Bugs-To:
\n
"
"POT-Creation-Date: 2017-10-
09 16:19
+0200
\n
"
"POT-Creation-Date: 2017-10-
11 12:22
+0200
\n
"
"PO-Revision-Date: 2016-07-18
\n
"
"Last-Translator: Skia <skia@libskia.so>
\n
"
"Language-Team: AE info <ae.info@utbm.fr>
\n
"
...
...
@@ -745,9 +745,10 @@ msgid ""
"Warning: if you select <em>Account</em>, the opposite operation will be "
"created in the target account. If you don't want that, select <em>Club</em> "
"instead of <em>Account</em>."
msgstr
"Attention : si vous sélectionnez <em>Compte</em>, l'opération inverse sera "
"créée dans le compte cible. Si vous ne le voulez pas, sélectionnez <em>Club</em> "
"à la place de <em>Compte</em>."
msgstr
""
"Attention : si vous sélectionnez <em>Compte</em>, l'opération inverse sera "
"créée dans le compte cible. Si vous ne le voulez pas, sélectionnez <em>Club</"
"em> à la place de <em>Compte</em>."
#: accounting/templates/accounting/operation_edit.jinja:46
msgid
"Linked operation:"
...
...
@@ -760,7 +761,7 @@ msgstr "Opération liée : "
#: core/templates/core/file_edit.jinja:8
#: core/templates/core/macros_pages.jinja:26
#: core/templates/core/page_prop.jinja:11
#: core/templates/core/user_godfathers.jinja:
35
#: core/templates/core/user_godfathers.jinja:
41
#: core/templates/core/user_preferences.jinja:12
#: core/templates/core/user_preferences.jinja:19
#: counter/templates/counter/cash_register_summary.jinja:22
...
...
@@ -896,16 +897,12 @@ msgid "logo"
msgstr
"logo"
#: club/models.py:62
#, fuzzy
#| msgid "active"
msgid
"is active"
msgstr
"actif"
#: club/models.py:63
#, fuzzy
#| msgid "description"
msgid
"short description"
msgstr
"description"
msgstr
"description
courte
"
#: club/models.py:64 core/models.py:199
msgid
"address"
...
...
@@ -2895,26 +2892,69 @@ msgid "Change user password"
msgstr
"Changer le mot de passe"
#: core/templates/core/user_godfathers.jinja:5
#: core/templates/core/user_godfathers_tree.jinja:7
#, python-format
msgid
"%(user_name)s's godfathers"
msgstr
"Parrains de %(user_name)s"
#: core/templates/core/user_godfathers.jinja:10 core/views/user.py:169
#: core/templates/core/user_godfathers.jinja:10
msgid
"Show family picture"
msgstr
"Voir une image de la famille"
#: core/templates/core/user_godfathers.jinja:12 core/views/user.py:169
#: core/views/user.py:388
msgid
"Godfathers"
msgstr
"Parrains"
#: core/templates/core/user_godfathers.jinja:18
#: core/templates/core/user_godfathers.jinja:20
msgid
"Show ancestors tree"
msgstr
"Voir l'arbre des ancêtres"
#: core/templates/core/user_godfathers.jinja:22
#: core/templates/core/user_godfathers_tree.jinja:50
msgid
"No godfathers"
msgstr
"Pas de parrains"
#: core/templates/core/user_godfathers.jinja:2
1
#: core/templates/core/user_godfathers.jinja:2
5 core/views/user.py:386
msgid
"Godchildren"
msgstr
"Fillots"
#: core/templates/core/user_godfathers.jinja:29
#: core/templates/core/user_godfathers.jinja:33
msgid
"Show descent tree"
msgstr
"Voir l'arbre de la descendance"
#: core/templates/core/user_godfathers.jinja:35
#: core/templates/core/user_godfathers_tree.jinja:48
msgid
"No godchildren"
msgstr
"Pas de fillots"
#: core/templates/core/user_godfathers_tree.jinja:5
msgid
"%(user_name)s's godchildren"
msgstr
"Fillots de %(user_name)s"
#: core/templates/core/user_godfathers_tree.jinja:20
msgid
"Already seen (check above)"
msgstr
"Déjà vu (voir plus haut)"
#: core/templates/core/user_godfathers_tree.jinja:33
msgid
"Back to family"
msgstr
"Retour à la famille"
#: core/templates/core/user_godfathers_tree.jinja:37
#: core/templates/core/user_godfathers_tree.jinja:41
msgid
"Show a picture of the tree"
msgstr
"Voir une image de l'arbre"
#: core/templates/core/user_godfathers_tree.jinja:38
#, python-format
msgid
"Descent tree of %(u)s"
msgstr
"Descendants de %(u)s"
#: core/templates/core/user_godfathers_tree.jinja:42
#, python-format
msgid
"Ancestors tree of %(u)s"
msgstr
"Ancêtres de %(u)s"
#: core/templates/core/user_group.jinja:4
#, python-format
msgid
"Edit user groups for %(user_name)s"
...
...
@@ -3181,7 +3221,11 @@ msgstr "Fillot"
msgid
"Pictures"
msgstr
"Photos"
#: core/views/user.py:388
#: core/views/user.py:390
msgid
"Family"
msgstr
"Famille"
#: core/views/user.py:473
msgid
"User already has a profile picture"
msgstr
"L'utilisateur a déjà une photo de profil"
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment