From 5c0f1778f81213b7a0e2487d8f4f6a0ab950b4c8 Mon Sep 17 00:00:00 2001 From: Jean-Philippe Caissy Date: Tue, 6 Mar 2012 15:24:58 -0600 Subject: [PATCH] Refactoring des graphs et organigrammes par service --- project/menu.py | 3 +- project/rh/admin.py | 23 +++- project/rh/graph.py | 27 +++++ project/rh/templates/rh/organigrammes/service.html | 1 + project/rh/urls.py | 1 + project/rh/views.py | 119 +++++++++++++------- 6 files changed, 132 insertions(+), 42 deletions(-) create mode 100644 project/rh/graph.py create mode 100644 project/rh/templates/rh/organigrammes/service.html diff --git a/project/menu.py b/project/menu.py index c57b943..b2a0b35 100644 --- a/project/menu.py +++ b/project/menu.py @@ -54,7 +54,8 @@ class CustomMenu(Menu): ), items.MenuItem('Organigrammes', children=[ - items.MenuItem('Organigramme par employé', reverse('admin:rh_employeproxy_changelist')) + items.MenuItem('Organigramme par employé', reverse('admin:rh_employeproxy_changelist')), + items.MenuItem('Organigramme par service', reverse('admin:rh_serviceproxy_changelist')), ] ), ] diff --git a/project/rh/admin.py b/project/rh/admin.py index 3762051..336e05a 100644 --- a/project/rh/admin.py +++ b/project/rh/admin.py @@ -18,8 +18,16 @@ from groups import grp_drh import models as rh -class EmployeProxy(rh.Employe): +class ServiceProxy(rh.Service): + """ Proxy utilisé pour les organigrammes opar service """ + class Meta: + proxy = True + verbose_name = u"Organigramme par services" + verbose_name_plural = u"Organigramme par services" + +class EmployeProxy(rh.Employe): + """ Proxy utilisé pour les organigrammes des employés """ class Meta: proxy = True verbose_name = u"Organigramme des employés" @@ -911,6 +919,18 @@ class ServiceAdmin(AUFMetadataAdminMixin, admin.ModelAdmin): _date_modification.admin_order_field = 'date_modification' +class ServiceProxyAdmin(ServiceAdmin): + list_display = ('nom', '_organigramme') + list_display_links = ('nom',) + + def has_add_permission(self, obj): + return False + + def _organigramme(self, obj): + return """Organigramme""" % (reverse('rho_service', args=(obj.id,))) + _organigramme.allow_tags = True + _organigramme.short_description = "Organigramme" + class StatutAdmin(AUFMetadataAdminMixin, admin.ModelAdmin): list_display = ('code', 'nom', '_date_modification', 'user_modification', ) fieldsets = AUFMetadataAdminMixin.fieldsets + ( @@ -1059,6 +1079,7 @@ admin.site.register(rh.Classement, ClassementAdmin) admin.site.register(rh.Devise, DeviseAdmin) admin.site.register(rh.Dossier, DossierAdmin) admin.site.register(EmployeProxy, EmployeProxyAdmin) +admin.site.register(ServiceProxy, ServiceProxyAdmin) admin.site.register(rh.Employe, EmployeAdmin) admin.site.register(rh.FamilleEmploi, FamilleEmploiAdmin) admin.site.register(rh.OrganismeBstg, OrganismeBstgAdmin) diff --git a/project/rh/graph.py b/project/rh/graph.py new file mode 100644 index 0000000..f62db0c --- /dev/null +++ b/project/rh/graph.py @@ -0,0 +1,27 @@ +import unicodedata +from datetime import date +from django.db.models import Q +from django.core.urlresolvers import reverse + +from rh import models as rh + +def bind_poste_to_graph(graph, postes_by_id): + for n in graph.nodes(): + p = postes_by_id[int(n)] + try: + d = rh.Dossier.objects.select_related('employe').filter((Q(date_fin__gt=date.today()) | Q(date_fin=None)) & (Q(date_debut__lt=date.today()) | Q(date_debut=None)) & Q(poste=p)).exclude(supprime=True).all()[0] + + + label = u"%s\\n[%s] %s\\n%s" % (d.poste.nom, d.employe_id, "%s %s" % + (d.employe.nom.upper(), d.employe.prenom), + d.poste.implantation) + except IndexError: + label = u"%s\\n---\\n%s" % (d.poste.nom, d.poste.implantation) + n.attr['fillcolor'] = 'azure4' + n.attr['style'] = 'filled' + + label = unicodedata.normalize('NFKD', label).encode('ascii','ignore') + n.attr['label'] = label + n.attr['href'] = reverse("admin:rh_employe_change", args=(d.employe_id,)) + + return graph diff --git a/project/rh/templates/rh/organigrammes/service.html b/project/rh/templates/rh/organigrammes/service.html new file mode 100644 index 0000000..221d7c5 --- /dev/null +++ b/project/rh/templates/rh/organigrammes/service.html @@ -0,0 +1 @@ +{{ svg|safe }} diff --git a/project/rh/urls.py b/project/rh/urls.py index 48aedd2..37aff13 100644 --- a/project/rh/urls.py +++ b/project/rh/urls.py @@ -16,4 +16,5 @@ urlpatterns = patterns( url(r'^admin/rh/employe/(\d+)/apercu/$', 'employe_apercu', name='employe_apercu'), url(r'^admin/rh/poste/(\d+)/apercu/$', 'poste_apercu', name='poste_apercu'), url(r'^admin/rh/organigrammes/employe/(\d+)$', 'organigrammes_employe', name='rho_employe'), + url(r'^admin/rh/organigrammes/service/(\d+)$', 'organigrammes_service', name='rho_service'), ) diff --git a/project/rh/views.py b/project/rh/views.py index d378fff..bbefc74 100644 --- a/project/rh/views.py +++ b/project/rh/views.py @@ -1,16 +1,15 @@ # -*- encoding: utf-8 -*- -import unicodedata from datetime import date from itertools import izip import networkx as nx +import pygraphviz as pgv from django.db.models import Q from django.contrib.auth.decorators import login_required from django.utils.encoding import smart_str from django.shortcuts import render_to_response, get_object_or_404 from django.template import RequestContext -from django.core.urlresolvers import reverse from django.http import HttpResponse from datamaster_modeles import models as ref @@ -20,6 +19,7 @@ from rh.lib import calc_remun from rh.decorators import drh_or_admin_required from rh.templatetags.rapports import SortHeaders from rh.change_list import RechercheTemporelle +from rh import graph as rh_graph # pas de reference a DAE devrait etre refactorisé from dae.utils import get_employe_from_user @@ -481,11 +481,15 @@ def employe_apercu(request, employe_id): def organigrammes_employe(request, id): poste = get_object_or_404(rh.Poste, pk=id) + dossiers_by_poste = dict((d.poste_id, d) for d in rh.Dossier.objects.select_related('employe', 'poste').all()) + postes_by_id = dict((p.id, p) for p in rh.Poste.objects.all()) + + e = dossiers_by_poste[poste.id].employe + name = u"Organigramme de [%s] %s %s" % (e.id, e.nom.upper(), e.prenom) + graph = nx.DiGraph(name=name) + if rh.Poste.objects.filter(responsable=poste).count() > 0: - graph = nx.DiGraph() postes = rh.Poste.objects.select_related('implantation').filter((Q(date_fin__gt=date.today()) | Q(date_fin=None)) & (Q(date_debut__lt=date.today()) | Q(date_debut=None)) ).exclude(supprime=True).exclude(responsable=None).all() - dossiers_by_poste = dict((d.poste_id, d) for d in rh.Dossier.objects.select_related('employe', 'poste').all()) - postes_by_id = dict((p.id, p) for p in rh.Poste.objects.all()) for p in rh.Poste.objects.filter((Q(date_fin__gt=date.today()) | Q(date_fin=None)) & (Q(date_debut__lt=date.today()) | Q(date_debut=None)) ).exclude(supprime=True).exclude(responsable=None).all(): graph.add_node(p.id) @@ -495,42 +499,26 @@ def organigrammes_employe(request, id): graph.add_edge(dossiers_by_poste[p.responsable_id].poste_id, p.id) graph = nx.bfs_tree(graph, poste.id) - a = nx.to_agraph(graph) - - poste_remontant = poste - while poste_remontant.responsable_id: - a.add_edge(poste_remontant.responsable_id, poste_remontant.id) - poste_remontant = poste_remontant.responsable - - for n in a.nodes(): - p = postes_by_id[int(n)] - try: - d = rh.Dossier.objects.select_related('employe').filter((Q(date_fin__gt=date.today()) | Q(date_fin=None)) & (Q(date_debut__lt=date.today()) | Q(date_debut=None)) & Q(poste=p)).exclude(supprime=True).all()[0] - - - label = u"%s\\n[%s] %s\\n%s" % (d.poste.nom, d.employe_id, "%s %s" % - (d.employe.nom.upper(), d.employe.prenom), - d.poste.implantation) - except IndexError: - label = u"%s\\n---\\n%s" % (d.poste.nom, d.poste.implantation) - n.attr['fillcolor'] = 'azure4' - n.attr['style'] = 'filled' - - label = unicodedata.normalize('NFKD', label).encode('ascii','ignore') - n.attr['label'] = label - n.attr['href'] = reverse("admin:rh_employe_change", args=(d.employe_id,)) - - #a.graph_attr['normalize'] = True - #a.graph_attr['level'] = 2 - a.layout(prog='dot') - - svg = a.draw(format='svg') - - c = { - 'svg': svg - } else: - c = {} + graph.add_node(poste.id) + + a = nx.to_agraph(graph) + + poste_remontant = poste + while poste_remontant.responsable_id: + a.add_edge(poste_remontant.responsable_id, poste_remontant.id) + poste_remontant = poste_remontant.responsable + + rh_graph.bind_poste_to_graph(a, postes_by_id) + #a.graph_attr['normalize'] = True + #a.graph_attr['level'] = 2 + a.layout(prog='dot') + + svg = a.draw(format='svg') + + c = { + 'svg': svg + } if 'forcer' in request.GET: response = HttpResponse(svg, mimetype='image/svg+xml') @@ -538,3 +526,54 @@ def organigrammes_employe(request, id): return response return render_to_response('rh/organigrammes/employe.html', c, RequestContext(request), mimetype="image/svg+xml") + + +@login_required +@drh_or_admin_required +def organigrammes_service(request, id): + + service = get_object_or_404(rh.Service, pk=id) + + postes_by_id = dict((p.id, p) for p in rh.Poste.objects.all()) + + postes = rh.Poste.objects.select_related('implantation').filter((Q(date_fin__gt=date.today()) | Q(date_fin=None)) & (Q(date_debut__lt=date.today()) | Q(date_debut=None)) ).filter(service=service).exclude(supprime=True, responsable=None).all() + + graph = pgv.AGraph(directed=True, name=u"Organigramme de « %s »" % service.nom) + graph_service = graph.subgraph(nbunch=[1,2], \ + name="cluster1", \ + style='filled', \ + color='lightgrey', \ + label=service.nom, + labeljust="l") + + for p in postes: + graph_service.add_node(p.id) + if p.responsable_id: + graph.add_edge(p.responsable_id, p.id) + + for p_id in graph_service.nodes(): + if postes_by_id[int(p_id)].responsable_id: + poste_remontant = postes_by_id[int(p_id)] + while poste_remontant.responsable_id and poste_remontant.responsable_id and poste_remontant.responsable_id != poste_remontant.id: + poste_remontant = postes_by_id[poste_remontant.responsable_id] + if poste_remontant.responsable_id: + graph.add_edge(poste_remontant.responsable_id, poste_remontant.id) + + rh_graph.bind_poste_to_graph(graph, postes_by_id) + + graph.layout(prog='dot') + + svg = graph.draw(format='svg') + + c = { + 'svg': svg + } + + if 'forcer' in request.GET: + response = HttpResponse(svg, mimetype='image/svg+xml') + response['Content-Disposition'] = 'attachment; filename=organigramme.svg' + return response + + return render_to_response('rh/organigrammes/service.html', c, RequestContext(request), mimetype="image/svg+xml") + + -- 1.7.10.4