diff --git a/core/templates/core/user_godfathers.jinja b/core/templates/core/user_godfathers.jinja index 80331c5e..fa2bbca6 100644 --- a/core/templates/core/user_godfathers.jinja +++ b/core/templates/core/user_godfathers.jinja @@ -6,6 +6,8 @@ {% endblock %} {% block content %} +

+ {% trans %}Show family picture{% endtrans %}

{% if profile.godfathers.exists() %}

{% trans %}Godfathers{% endtrans %}

+

+ {% trans %}Show ancestors tree{% endtrans %}

{% else %}

{% trans %}No godfathers{% endtrans %} {% endif %} @@ -25,6 +29,8 @@ {{ u.get_mini_item()|safe }} {{ delete_godfather(user, profile, u, False) }} {% endfor %} +

+ {% trans %}Show descent tree{% endtrans %}

{% else %}

{% trans %}No godchildren{% endtrans %} {% endif %} diff --git a/core/templates/core/user_godfathers_tree.jinja b/core/templates/core/user_godfathers_tree.jinja new file mode 100644 index 00000000..9c1791f4 --- /dev/null +++ b/core/templates/core/user_godfathers_tree.jinja @@ -0,0 +1,54 @@ +{% 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() %} +

+{% endif %} +{% endmacro %} + +{% block content %} +

+ {% trans %}Back to family{% endtrans %}

+ {% if profile.__getattribute__(param).exists() %} + {% if param == "godchildren" %} +

+ {% trans %}Show a picture of the tree{% endtrans %}

+

{% trans u=profile.get_short_name() %}Descent tree of {{ u }}{% endtrans %}

+ {% else %} +

+ {% trans %}Show a picture of the tree{% endtrans %}

+

{% trans u=profile.get_short_name() %}Ancestors tree of {{ u }}{% endtrans %}

+ {% endif %} + {{ members_set.add(profile) or "" }} + {{ display_members_list(profile) }} + {% else %} + {% if param == "godchildren" %} +

{% trans %}No godchildren{% endtrans %} + {% else %} +

{% trans %}No godfathers{% endtrans %} + {% endif %} + {% endif %} +{% endblock %} + diff --git a/core/urls.py b/core/urls.py index a056b4b7..216ec064 100644 --- a/core/urls.py +++ b/core/urls.py @@ -61,6 +61,8 @@ urlpatterns = [ url(r'^user/(?P[0-9]+)/$', UserView.as_view(), name='user_profile'), url(r'^user/(?P[0-9]+)/pictures$', UserPicturesView.as_view(), name='user_pictures'), url(r'^user/(?P[0-9]+)/godfathers$', UserGodfathersView.as_view(), name='user_godfathers'), + url(r'^user/(?P[0-9]+)/godfathers/tree$', UserGodfathersTreeView.as_view(), name='user_godfathers_tree'), + url(r'^user/(?P[0-9]+)/godfathers/tree/pict$', UserGodfathersTreePictureView.as_view(), name='user_godfathers_tree_pict'), url(r'^user/(?P[0-9]+)/godfathers/(?P[0-9]+)/(?P(True)|(False))/delete$', DeleteUserGodfathers, name='user_godfathers_delete'), url(r'^user/(?P[0-9]+)/edit$', UserUpdateProfileView.as_view(), name='user_edit'), url(r'^user/(?P[0-9]+)/profile_upload$', UserUploadProfilePictView.as_view(), name='user_profile_upload'), diff --git a/core/views/user.py b/core/views/user.py index 6bbbccef..25f7a7d5 100644 --- a/core/views/user.py +++ b/core/views/user.py @@ -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): """ diff --git a/locale/fr/LC_MESSAGES/django.po b/locale/fr/LC_MESSAGES/django.po index fb2af5e0..3378a0f8 100644 --- a/locale/fr/LC_MESSAGES/django.po +++ b/locale/fr/LC_MESSAGES/django.po @@ -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 \n" "Language-Team: AE info \n" @@ -745,9 +745,10 @@ msgid "" "Warning: if you select Account, the opposite operation will be " "created in the target account. If you don't want that, select Club " "instead of Account." -msgstr "Attention : si vous sélectionnez Compte, l'opération inverse sera " -"créée dans le compte cible. Si vous ne le voulez pas, sélectionnez Club " -"à la place de Compte." +msgstr "" +"Attention : si vous sélectionnez Compte, l'opération inverse sera " +"créée dans le compte cible. Si vous ne le voulez pas, sélectionnez Club à la place de Compte." #: 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:21 +#: core/templates/core/user_godfathers.jinja:25 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"