eed84ba6ffd090f3a11e0804b1f2c04845d9d8fb
[auf_rh_dae.git] / project / rh / admin.py
1 # -*- encoding: utf-8 -*-
2
3 import datetime
4
5 import reversion
6 from ajax_select import make_ajax_form
7 from auf.django.references import models as ref
8 from django import forms
9 from django.core.urlresolvers import reverse
10 from django.contrib import admin
11 from django.contrib.contenttypes.models import ContentType
12 from django.conf import settings
13 from django.db.models import Q, Count
14 from django.template.defaultfilters import date
15 from django.utils.formats import date_format
16
17 from project import groups
18 from project.decorators import in_drh_or_admin
19 from project.groups import grp_correspondants_rh
20 from project.groups import get_employe_from_user
21 from project.rh import models as rh
22 from project.permissions import get_region_user, \
23 user_gere_obj_de_sa_region, \
24 user_can_add_obj, \
25 user_can_change_obj, \
26 user_can_delete_obj
27
28 import project.rh.models as rh
29 from project.rh.forms import \
30 ContratForm, AyantDroitForm, EmployeAdminForm, AjaxSelect, DossierForm
31 from project.rh.change_list import ChangeList
32 from project.rh.forms import ContratForm, AyantDroitForm, EmployeAdminForm, \
33 AjaxSelect, DossierForm, ResponsableInlineForm
34
35
36 class BaseAdmin(admin.ModelAdmin):
37
38 class Media:
39 css = {'screen': (
40 'css/admin_custom.css',
41 'jquery-autocomplete/jquery.autocomplete.css',
42 )}
43 js = (
44 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',
45 'jquery-autocomplete/jquery.autocomplete.min.js',
46 )
47
48 # Admin pour reversion
49
50 class ArchivableAdmin(admin.ModelAdmin):
51 """
52 Admin pour les modèles archivables
53 """
54 list_filter = ('archive', )
55
56 def queryset(self, request):
57 return self.model.avec_archives.all()
58
59 def _archive(self, obj):
60 if obj.archive:
61 return "oui"
62 else:
63 return "non"
64 _archive.short_description = u'Archivé'
65 _archive.admin_order_field = 'archive'
66
67
68 class RegionProxy(ref.Region):
69 """ Proxy utilisé pour les organigrammes par région """
70 class Meta:
71 managed = False
72 proxy = True
73 verbose_name = u"Organigramme par région"
74 verbose_name_plural = u"Organigramme par région"
75
76
77 class ImplantationProxy(ref.Implantation):
78 """ Proxy utilisé pour les organigrammes par implantation """
79 class Meta:
80 managed = False
81 proxy = True
82 verbose_name = u"Organigramme par implantations"
83 verbose_name_plural = u"Organigramme par implantations"
84
85
86 class ServiceProxy(rh.Service):
87 """ Proxy utilisé pour les organigrammes par service """
88
89 class Meta:
90 managed = False
91 proxy = True
92 verbose_name = u"Organigramme par services"
93 verbose_name_plural = u"Organigramme par services"
94
95
96 class EmployeProxy(rh.Employe):
97 """ Proxy utilisé pour les organigrammes des employés """
98 class Meta:
99 managed = False
100 proxy = True
101 verbose_name = u"Organigramme des employés"
102 verbose_name_plural = u"Organigramme des employés"
103
104
105 class DateRangeMixin(object):
106 prefixe_recherche_temporelle = ""
107
108 def get_changelist(self, request, **kwargs):
109 if 'HTTP_REFERER' in request.META.keys():
110 referer = request.META['HTTP_REFERER']
111 referer = "/".join(referer.split('/')[3:])
112 referer = "/%s" % referer.split('?')[0]
113 change_list_view = 'admin:%s_%s_changelist' % (
114 self.model._meta.app_label,
115 self.model.__name__.lower(),)
116 if referer != reverse(change_list_view):
117 params = request.GET.copy()
118 params.update({'statut': 'Actif'})
119 request.GET = params
120 return ChangeList
121
122
123 # Override of the InlineModelAdmin to support the link in the tabular inline
124 class LinkedInline(admin.options.InlineModelAdmin):
125 template = "admin/linked.html"
126 admin_model_path = None
127
128 def __init__(self, *args):
129 super(LinkedInline, self).__init__(*args)
130 if self.admin_model_path is None:
131 self.admin_model_path = self.model.__name__.lower()
132
133
134 class ProtectRegionMixin(object):
135
136 def queryset(self, request):
137 qs = super(ProtectRegionMixin, self).queryset(request)
138
139 if in_drh_or_admin(request.user):
140 return qs
141
142 if user_gere_obj_de_sa_region(request.user):
143 region_user = get_region_user(request.user)
144 q = Q(**{self.model.prefix_implantation: \
145 region_user})
146 qs = qs.filter(q).distinct()
147 return qs
148 return qs.none()
149
150 def has_add_permission(self, request):
151 return user_can_add_obj(request.user)
152
153 def has_change_permission(self, request, obj=None):
154 return user_can_change_obj(request.user, obj) if obj else True
155
156 def has_delete_permission(self, request, obj=None):
157 return user_can_delete_obj(request.user, obj) if obj else True
158
159
160 class DerniereModificationAdmin(admin.ModelAdmin):
161
162 def queryset(self, request):
163 qs = super(DerniereModificationAdmin, self).queryset(request)
164 ct = ContentType.objects.get_for_model(self.model)
165 db_table = self.model._meta.db_table
166 pk = self.model._meta.pk.column
167 return qs.extra(select={
168 'date_modification':
169 "SELECT action_time FROM django_admin_log "
170 "WHERE content_type_id = %d AND object_id = %s.%s "
171 "ORDER BY action_time DESC "
172 "LIMIT 1" % (ct.id, db_table, pk),
173 'user_modification':
174 "SELECT u.username "
175 "FROM auth_user u "
176 "INNER JOIN django_admin_log l ON l.user_id = u.id "
177 "WHERE l.content_type_id = %d AND object_id = %s.%s "
178 "ORDER BY action_time DESC "
179 "LIMIT 1" % (ct.id, db_table, pk),
180 })
181
182 def derniere_modification(self, obj):
183 text = ''
184 if obj.date_modification:
185 text += obj.date_modification.strftime('%d-%m-%Y %H:%M')
186 if obj.user_modification:
187 text += ' par ' + obj.user_modification
188 return text
189 derniere_modification.short_description = u'dernière modification'
190 derniere_modification.admin_order_field = 'date_modification'
191
192
193 # Inlines
194
195 class CommentaireInlineForm(forms.ModelForm):
196
197 def save(self, commit=True):
198
199 # Hack: reversion.VersionAdmin ne sauvegarde pas les champs qui ne
200 # sont pas explicitement dans le formulaire. Il plante cependant
201 # leur valeur dans `self.initial`. Ceci est un peu fragile. Si
202 # c'est possible, il serait plus approprié que Reversion se rende
203 # compte qu'il manque des champs.
204 instance = super(CommentaireInlineForm, self).save(commit=False)
205 if instance.owner_id is None and 'owner' in self.initial:
206 instance.owner_id = self.initial['owner']
207 if instance.date_creation is None and 'date_creation' in self.initial:
208 instance.date_creation = self.initial['date_creation']
209 if commit:
210 instance.save()
211 self.save_m2m()
212 return instance
213
214
215 class ReadOnlyInlineMixin(object):
216
217 def get_readonly_fields(self, request, obj=None):
218 return [f.name for f in self.model._meta.fields]
219
220
221 class AyantDroitInline(admin.StackedInline):
222 model = rh.AyantDroit
223 form = AyantDroitForm
224 extra = 0
225
226 fieldsets = (
227 (None, {
228 'fields': (
229 ('nom', 'prenom'),
230 ('nom_affichage', 'genre'),
231 'nationalite',
232 'date_naissance',
233 'lien_parente',
234 )}),
235 )
236
237
238 class AyantDroitCommentaireInline(admin.TabularInline):
239 readonly_fields = ('owner',)
240 model = rh.AyantDroitCommentaire
241 extra = 1
242 form = CommentaireInlineForm
243
244
245 class ContratInline(admin.TabularInline):
246 form = ContratForm
247 model = rh.Contrat
248 extra = 1
249
250
251 class DossierROInline(ReadOnlyInlineMixin, LinkedInline):
252 template = "admin/rh/dossier/linked.html"
253 model = rh.Dossier
254 extra = 0
255 can_delete = False
256
257 def has_add_permission(self, request=None):
258 return False
259
260 def has_change_permission(self, request, obj=None):
261 return False
262
263 def has_delete_permission(self, request, obj=None):
264 return False
265
266
267 class DossierCommentaireInline(admin.TabularInline):
268 readonly_fields = ('owner',)
269 model = rh.DossierCommentaire
270 extra = 1
271 form = CommentaireInlineForm
272
273
274 class DossierPieceInline(admin.TabularInline):
275 model = rh.DossierPiece
276 extra = 4
277
278
279 class EmployeInline(admin.TabularInline):
280 model = rh.Employe
281
282
283 class EmployeCommentaireInline(admin.TabularInline):
284 readonly_fields = ('owner',)
285 model = rh.EmployeCommentaire
286 extra = 1
287 form = CommentaireInlineForm
288
289
290 class EmployePieceInline(admin.TabularInline):
291 model = rh.EmployePiece
292 extra = 4
293
294
295 class PosteCommentaireInline(admin.TabularInline):
296 readonly_fields = ('owner',)
297 model = rh.PosteCommentaire
298 extra = 1
299 form = CommentaireInlineForm
300
301
302 class PosteFinancementInline(admin.TabularInline):
303 model = rh.PosteFinancement
304
305
306 class PostePieceInline(admin.TabularInline):
307 model = rh.PostePiece
308
309
310 class RemunerationInline(admin.TabularInline):
311 model = rh.Remuneration
312 extra = 1
313
314
315 class RemunerationROInline(ReadOnlyInlineMixin, RemunerationInline):
316 pass
317
318
319 class TypePosteInline(admin.TabularInline):
320 model = rh.TypePoste
321
322
323 class PosteComparaisonInline(admin.TabularInline):
324 model = rh.PosteComparaison
325
326
327 class ClassementAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
328 BaseAdmin):
329 ignore_duplicate_revisions = True
330 list_display = ('_classement', 'derniere_modification')
331 fieldsets = (
332 (None, {'fields': ('type', 'echelon', 'degre', 'coefficient',)}),
333 )
334
335 def _classement(self, obj):
336 return unicode(obj)
337 _classement.short_description = u"Classement"
338
339
340 class DeviseAdmin(reversion.VersionAdmin, ArchivableAdmin,
341 DerniereModificationAdmin, BaseAdmin):
342 ignore_duplicate_revisions = True
343 list_display = (
344 'code', 'nom', '_archive', 'derniere_modification',
345 )
346 list_filter = ('archive', )
347 fieldsets = (
348 (None, {'fields': ('code', 'nom', 'archive', )}),
349 )
350
351
352 class DossierAdmin(DateRangeMixin, ProtectRegionMixin, reversion.VersionAdmin,
353 AjaxSelect, DerniereModificationAdmin, BaseAdmin):
354 change_list_template = "admin/rh/dossier/change_list.html"
355 ignore_duplicate_revisions = True
356 alphabet_filter = 'employe__nom'
357 search_fields = (
358 'id',
359 'employe__id',
360 'poste__id',
361 'employe__nom',
362 'employe__prenom',
363 'poste__nom',
364 'poste__nom_feminin',
365 'poste__implantation__nom',
366 )
367 list_display = (
368 '_id',
369 '_apercu',
370 '_nom',
371 '_employe',
372 '_poste',
373 '_region',
374 '_implantation',
375 '_date_debut',
376 '_date_fin',
377 'derniere_modification',
378 '_dae',
379 )
380 list_display_links = ('_nom',)
381 list_filter = (
382 'poste__implantation__region',
383 'poste__implantation',
384 'poste__type_poste__categorie_emploi',
385 'poste__type_poste',
386 'rh_contrats__type_contrat',
387 'principal',
388 )
389 inlines = (DossierPieceInline, ContratInline,
390 RemunerationInline,
391 DossierCommentaireInline,
392 )
393 fieldsets = (
394 (None, {
395 'fields': (
396 'employe',
397 'poste',
398 'principal',
399 'statut',
400 'organisme_bstg',)}),
401 ('Recrutement', {
402 'fields': (
403 'statut_residence',
404 'remplacement',
405 'remplacement_de', )}),
406 ('Rémunération', {
407 'fields': (
408 'classement',
409 ('regime_travail', 'regime_travail_nb_heure_semaine'),)}),
410 ('Occupation du Poste par cet Employe', {
411 'fields': (('date_debut', 'date_fin'), )}
412 ),
413 )
414 form = make_ajax_form(rh.Dossier, {
415 'employe': 'employes',
416 'poste': 'postes',
417 'remplacement_de': 'dossiers',
418 }, superclass=DossierForm)
419
420 def lookup_allowed(self, key, value):
421 if key in (
422 'employe__nom__istartswith',
423 'poste__implantation__region__id__exact',
424 'poste__implantation__id__exact',
425 'poste__type_poste__id__exact',
426 'poste__type_poste__categorie_emploi__id__exact',
427 'rh_contrats__type_contrat__id__exact',
428 'principal__exact',
429 'principal__isnull',
430 ):
431 return True
432
433 def _id(self, obj):
434 return obj.id
435 _id.short_description = u"#"
436 _id.admin_order_field = "id"
437
438 def _apercu(self, d):
439 apercu_link = u"""<a title="Aperçu du dossier"
440 onclick="return showAddAnotherPopup(this);"
441 href='%s'>
442 <img src="%simg/dossier-apercu.png" />
443 </a>""" % \
444 (reverse('dossier_apercu', args=(d.id,)),
445 settings.STATIC_URL,
446 )
447 return apercu_link
448 _apercu.allow_tags = True
449 _apercu.short_description = u""
450
451 def _nom(self, obj):
452 return "Dossier"
453 _nom.allow_tags = True
454 _nom.short_description = u"Dossier"
455
456 def _employe(self, obj):
457 employe = obj.employe
458 view_link = reverse('employe_apercu', args=(employe.id,))
459 edit_link = reverse('admin:rh_employe_change', args=(employe.id,))
460 style = ""
461 view = u"""<a href="%s"
462 title="Aperçu l'employé"
463 onclick="return showAddAnotherPopup(this);">
464 <img src="%simg/employe-apercu.png" />
465 </a>""" % (view_link, settings.STATIC_URL,)
466 return u"""%s<a href='%s' style="%s;">%s</a>""" % \
467 (view, edit_link, style, employe)
468 _employe.allow_tags = True
469 _employe.short_description = u"Employé"
470 _employe.admin_order_field = "employe__nom"
471
472 def _poste(self, dossier):
473 link = u"""<a title="Aperçu du poste"
474 onclick="return showAddAnotherPopup(this);"
475 href='%s'><img src="%simg/poste-apercu.png" />
476 </a>
477 <a href="%s" title="Modifier le poste">%s [%d]</a>""" % \
478 (reverse('poste_apercu', args=(dossier.poste.id,)),
479 settings.STATIC_URL,
480 reverse('admin:rh_poste_change', args=(dossier.poste.id,)),
481 dossier.poste.nom,
482 dossier.poste.id,
483 )
484 return link
485 _poste.allow_tags = True
486 _poste.short_description = u'Poste'
487 _poste.admin_order_field = 'poste__nom'
488
489 def _region(self, obj):
490 return obj.poste.implantation.region.code
491 _region.short_description = u"Région"
492 _region.admin_order_field = 'poste__implantation__region__code'
493
494 def _implantation(self, obj):
495 return obj.poste.implantation.nom
496 _implantation.short_description = u"Implantation"
497 _implantation.admin_order_field = 'poste__implantation__nom'
498
499 def _date_debut(self, obj):
500 return date(obj.date_debut)
501
502 _date_debut.short_description = u'Début'
503 _date_debut.admin_order_field = 'date_debut'
504
505 def _date_fin(self, obj):
506 return date(obj.date_fin)
507 _date_fin.short_description = u'Fin'
508 _date_fin.admin_order_field = 'date_fin'
509
510 def _date_modification(self, obj):
511 return date(obj.date_modification) \
512 if obj.date_modification is not None else "(aucune)"
513 _date_modification.short_description = u'date modification'
514 _date_modification.admin_order_field = 'date_modification'
515
516 def _dae(self, d):
517 apercu_link = ""
518 dossiers_dae = d.dossiers_dae.all()
519 if len(dossiers_dae) > 0:
520 dossier_dae = dossiers_dae[0]
521 apercu_link = u"""<a title="Aperçu du dossier"
522 onclick="return showAddAnotherPopup(this);"
523 href='%s'>
524 <img src="%simg/loupe.png" />
525 </a>""" % \
526 (reverse('embauche_consulter', args=(dossier_dae.id,)),
527 settings.STATIC_URL,
528 )
529 return apercu_link
530 _dae.allow_tags = True
531 _dae.short_description = u"DAE"
532
533 def save_formset(self, request, form, formset, change):
534 instances = formset.save(commit=False)
535 for instance in instances:
536 if instance.__class__ == rh.DossierCommentaire:
537 instance.owner = request.user
538 instance.date_creation = datetime.datetime.now()
539 instance.save()
540
541
542 class EmployeAdminBase(DateRangeMixin, ProtectRegionMixin,
543 DerniereModificationAdmin, BaseAdmin):
544 prefixe_recherche_temporelle = "rh_dossiers__"
545 alphabet_filter = 'nom'
546 DEFAULT_ALPHABET = u'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
547 search_fields = (
548 'id', 'nom', 'prenom', 'nom_affichage',
549 'rh_dossiers__poste__nom',
550 'rh_dossiers__poste__nom_feminin'
551 )
552 ordering = ('nom', )
553 form = EmployeAdminForm
554 list_display = (
555 '_id', '_apercu', '_nom', '_dossiers_postes',
556 #'_region',
557 #'_implantation',
558 'date_entree',
559 'derniere_modification'
560 )
561 list_display_links = ('_nom',)
562 list_filter = (
563 'rh_dossiers__poste__implantation__region',
564 'rh_dossiers__poste__implantation', 'nb_postes'
565 )
566 inlines = (
567 AyantDroitInline, DossierROInline, EmployePieceInline,
568 EmployeCommentaireInline
569 )
570 fieldsets = (
571 ('Identification', {
572 'fields': (
573 ('nom', 'prenom'),
574 ('nom_affichage', 'genre'),
575 'nationalite',
576 'date_naissance',
577 )
578 }),
579 ('Informations personnelles', {
580 'fields': ('situation_famille', 'date_entree', )
581 }),
582 ('Coordonnées personnelles', {
583 'fields': (
584 ('tel_domicile', 'tel_cellulaire'),
585 ('adresse', 'ville'),
586 ('code_postal', 'province'),
587 'pays',
588 'courriel_perso'
589 )
590 }),
591 )
592
593 def _id(self, obj):
594 return obj.id
595 _id.short_description = u"#"
596 _id.admin_order_field = "id"
597
598 def _apercu(self, obj):
599 return u"""<a title="Aperçu de l'employé"
600 onclick="return showAddAnotherPopup(this);"
601 href='%s'>
602 <img src="%simg/employe-apercu.png" />
603 </a>""" % \
604 (reverse('employe_apercu', args=(obj.id,)), settings.STATIC_URL)
605 _apercu.allow_tags = True
606 _apercu.short_description = u""
607
608 def _nom(self, obj):
609 edit_link = reverse('admin:rh_employe_change', args=(obj.id,))
610 return u"""<a href='%s'><strong>%s</strong></a>""" % \
611 (edit_link, "%s %s" % (obj.nom.upper(), obj.prenom))
612 _nom.allow_tags = True
613 _nom.short_description = u"Employé"
614 _nom.admin_order_field = "nom"
615
616 def _region(self, obj):
617 try:
618 d = rh.Dossier.objects.filter(employe=obj.id, principal=True)[0]
619 region = d.poste.implantation.region.code
620 except:
621 region = None
622 return region
623 _region.short_description = u"Région"
624
625 def _implantation(self, obj):
626 try:
627 d = rh.Dossier.objects.filter(employe=obj.id, principal=True)[0]
628 implantation = d.poste.implantation.nom
629 except:
630 implantation = None
631 return implantation
632 _implantation.short_description = u"Implantation"
633
634 def _dossiers_postes(self, obj):
635 l = []
636 for d in obj.rh_dossiers.all().order_by('-date_debut'):
637 link_style = u''
638 list_style = u''
639 if d.date_fin is not None and d.date_fin < datetime.date.today():
640 link_style = u' style="color:#666;"'
641 list_style = u' style="color:grey;"'
642
643 dossier = u"""<a title="Aperçu du dossier"
644 href="%s"
645 onclick="return showAddAnotherPopup(this);"
646 title="Aperçu du dossier">
647 <img src="%simg/dossier-apercu.png" />
648 </a>
649 <a href="%s"%s>Dossier</a>
650 &nbsp;""" % \
651 (reverse('dossier_apercu', args=(d.id,)),
652 settings.STATIC_URL,
653 reverse('admin:rh_dossier_change', args=(d.id,)),
654 link_style,)
655
656 poste = u"""<a title="Aperçu du poste"
657 href="%s"
658 onclick="return showAddAnotherPopup(this);"
659 title="Aperçu du poste">
660 <img src="%simg/poste-apercu.png" />
661 </a>
662 <a href="%s"%s>%s [%d]</a>
663 &nbsp;""" % \
664 (reverse('poste_apercu', args=(d.poste.id,)),
665 settings.STATIC_URL,
666 reverse('admin:rh_poste_change', args=(d.poste.id,)),
667 link_style,
668 d.poste.nom,
669 d.poste.id)
670
671 link = u"""<li%s>%s %s</li>""" % \
672 (list_style, dossier, poste)
673
674 l.append(link)
675 return "<ul>%s</ul>" % "\n".join(l)
676 _dossiers_postes.allow_tags = True
677 _dossiers_postes.short_description = u"Dossiers et postes"
678
679 def _date_modification(self, obj):
680 return date(obj.date_modification) \
681 if obj.date_modification is not None else "(aucune)"
682 _date_modification.short_description = u'date modification'
683 _date_modification.admin_order_field = 'date_modification'
684
685 def queryset(self, request):
686 qs = super(EmployeAdminBase, self).queryset(request)
687 return qs.select_related(depth=1).order_by('nom')
688
689 def save_formset(self, request, form, formset, change):
690 instances = formset.save(commit=False)
691 for instance in instances:
692 if instance.__class__ == rh.EmployeCommentaire:
693 instance.owner = request.user
694 instance.date_creation = datetime.datetime.now()
695 instance.save()
696
697
698 class EmployeAdmin(reversion.VersionAdmin, EmployeAdminBase):
699 change_list_template = "admin/rh/employe/change_list.html"
700 ignore_duplicate_revisions = True
701
702
703 class EmployeProxyAdmin(EmployeAdminBase):
704 list_display = ('_id', '_apercu', '_nom', '_organigramme')
705 list_per_page = 500
706 actions = None
707
708 def __init__(self, *args, **kwargs):
709 super(EmployeProxyAdmin, self).__init__(*args, **kwargs)
710 self.list_display_links = (None, )
711
712 def queryset(self, request):
713 qs = super(ProtectRegionMixin, self).queryset(request)
714
715 if in_drh_or_admin(request.user) or \
716 user_gere_obj_de_sa_region(request.user):
717 return qs
718
719 return qs.none()
720
721 def has_add_permission(self, obj):
722 return False
723
724 def has_change_permission(self, request, obj=None):
725 user_groups = request.user.groups.all()
726 if groups.grp_correspondants_rh in user_groups or \
727 groups.grp_administrateurs in user_groups or \
728 groups.grp_directeurs_bureau in user_groups or \
729 in_drh_or_admin(request.user):
730 return True
731 return False
732
733 def _organigramme(self, obj):
734 l = []
735 for d in rh.Dossier.objects.filter(
736 Q(date_fin__gt=datetime.date.today()) | Q(date_fin=None),
737 Q(date_debut__lt=datetime.date.today()) | Q(date_debut=None),
738 employe=obj.id
739 ):
740 organigramme = \
741 u'Organigramme, niveau: ' \
742 u'<input type="text" id="level_%s" ' \
743 u'style="width:30px;height:15px;" /> ' \
744 u'<input type="button" value="Générer" ' \
745 u"""onclick="window.location='%s' + """ \
746 u"""document.getElementById('level_%s').value" />""" % (
747 d.poste.id,
748 reverse('rho_employe_sans_niveau', args=(d.poste.id,)),
749 d.poste.id
750 )
751 link = u"""<li>%s [%s]:<br />%s</li>""" % (
752 d.poste.nom,
753 d.poste.id,
754 organigramme
755 )
756 l.append(link)
757 return "<ul>%s</ul>" % "\n".join(l)
758
759 _organigramme.allow_tags = True
760 _organigramme.short_description = "Organigramme"
761
762
763 class CategorieEmploiAdmin(reversion.VersionAdmin,
764 DerniereModificationAdmin, BaseAdmin):
765 ignore_duplicate_revisions = True
766 list_display = ('nom', 'derniere_modification')
767 inlines = (TypePosteInline,)
768 fieldsets = (
769 (None, {'fields': ('nom', )}),
770 )
771
772
773 class OrganismeBstgAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
774 BaseAdmin):
775 ignore_duplicate_revisions = True
776 search_fields = ('nom',)
777 list_display = ('nom', 'type', 'pays', 'derniere_modification')
778 list_filter = ('type', )
779 inlines = (DossierROInline,)
780 fieldsets = (
781 (None, {'fields': ('nom', 'type', 'pays',)}),
782 )
783
784
785 class PosteAdmin(DateRangeMixin, ProtectRegionMixin, reversion.VersionAdmin,
786 AjaxSelect, DerniereModificationAdmin, BaseAdmin):
787 change_list_template = "admin/rh/poste/change_list.html"
788 ignore_duplicate_revisions = True
789 form = make_ajax_form(rh.Poste, {
790 'implantation': 'implantations',
791 'type_poste': 'typepostes',
792 'responsable': 'postes',
793 'valeur_point_min': 'valeurpoints',
794 'valeur_point_max': 'valeurpoints',
795 })
796 alphabet_filter = 'nom'
797 search_fields = (
798 'id',
799 'nom',
800 'implantation__nom',
801 'implantation__region__code',
802 'implantation__region__nom',
803 'rh_dossiers__employe__id',
804 'rh_dossiers__employe__nom',
805 'rh_dossiers__employe__prenom',
806 )
807 list_display = (
808 '_id', '_apercu', '_nom', '_occupe_par', 'implantation', '_service',
809 '_responsable', 'date_debut', 'date_fin', 'derniere_modification',
810 '_dae'
811 )
812 list_filter = (
813 'implantation__region',
814 'implantation',
815 'service',
816 'type_poste',
817 'type_poste__categorie_emploi',
818 'type_poste__famille_professionnelle',
819 'vacant',
820 )
821 list_display_links = ('_nom',)
822 fieldsets = (
823 (None, {'fields': (
824 ('nom', 'nom_feminin'),
825 'implantation',
826 'type_poste',
827 'service',
828 'responsable',
829 )}
830 ),
831 ('Contrat', {
832 'fields': ((
833 'regime_travail',
834 'regime_travail_nb_heure_semaine'),
835 )}
836 ),
837 ('Recrutement', {
838 'fields': (('local', 'expatrie', 'mise_a_disposition', 'appel'),)}
839 ),
840 ('Rémunération', {
841 'fields': (('classement_min',
842 'valeur_point_min',
843 'devise_min',
844 'salaire_min',
845 'indemn_min',
846 'autre_min',),
847 ('classement_max',
848 'valeur_point_max',
849 'devise_max',
850 'salaire_max',
851 'indemn_max',
852 'autre_max',),
853 )}),
854 ('Comparatifs de rémunération', {
855 'fields': ('devise_comparaison',
856 ('comp_locale_min', 'comp_locale_max'),
857 ('comp_universite_min', 'comp_universite_max'),
858 ('comp_fonctionpub_min', 'comp_fonctionpub_max'),
859 ('comp_ong_min', 'comp_ong_max'),
860 ('comp_autre_min', 'comp_autre_max'))}
861 ),
862 ('Justification', {
863 'fields': ('justification',)}
864 ),
865 ('Autres Méta-données', {
866 'fields': ('date_debut', 'date_fin')}
867 ),
868 )
869
870 inlines = (PosteFinancementInline,
871 PostePieceInline,
872 DossierROInline,
873 PosteComparaisonInline,
874 PosteCommentaireInline, )
875
876 def lookup_allowed(self, key, value):
877 return key in (
878 'date_debut__gte', 'date_debut__isnull', 'date_fin__lte',
879 'date_fin__isnull', 'implantation__region__id__exact',
880 'implantation__id__exact', 'type_poste__id__exact',
881 'type_poste__categorie_emploi__id__exact', 'service__id__exact',
882 'service__isnull', 'vacant__exact', 'vacant__isnull',
883 ) or super(PosteAdmin, self).lookup_allowed(key, value)
884
885 def _id(self, obj):
886 return "%s" % obj.id
887 _id.short_description = '#'
888 _id.admin_order_field = 'id'
889
890 def _apercu(self, poste):
891 view_link = u"""<a onclick="return showAddAnotherPopup(this);"
892 title="Aperçu du poste"
893 href='%s'>
894 <img src="%simg/poste-apercu.png" />
895 </a>""" % \
896 (reverse('poste_apercu', args=(poste.id,)),
897 settings.STATIC_URL,)
898 return view_link
899 _apercu.allow_tags = True
900 _apercu.short_description = ''
901
902 def _nom(self, poste):
903 return """<a href="%s">%s</a>""" % \
904 (reverse('admin:rh_poste_change', args=(poste.id,)),
905 poste.nom)
906 _nom.allow_tags = True
907 _nom.short_description = u'Poste'
908 _nom.admin_order_field = 'nom'
909
910 def _occupe_par(self, obj):
911 """Formatte la méthode Poste.occupe_par() pour l'admin"""
912 output = u"Vacant"
913 if obj.date_fin is not None and obj.date_fin < datetime.date.today():
914 return u"s/o"
915 employes = obj.occupe_par()
916 if employes:
917 l = []
918 for e in employes:
919 link = u"""<a href='%s'
920 title='Aperçu de l\'employé'
921 onclick='return showAddAnotherPopup(this)'>
922 <img src='%simg/employe-apercu.png' />
923 </a>
924 <a href='%s'>%s</a>""" % \
925 (reverse('employe_apercu', args=(e.id,)),
926 settings.STATIC_URL,
927 reverse('admin:rh_employe_change', args=(e.id,)),
928 e)
929 l.append(link)
930 output = "\n<br />".join(l)
931 return output
932 _occupe_par.allow_tags = True
933 _occupe_par.short_description = "Occupé par"
934
935 def _region(self, poste):
936 return poste.implantation.region.code
937 _region.short_description = 'Région'
938 _region.admin_order_field = 'implantation__region__code'
939
940 def _implantation(self, poste):
941 return poste.implantation.nom
942 _implantation.short_description = 'Implantation'
943 _implantation.admin_order_field = 'implantation'
944
945 def _service(self, obj):
946 return obj.service
947 _service.short_description = 'Service'
948 _service.allow_tags = True
949
950 def _responsable(self, obj):
951 try:
952 responsable = u"""<a href="%s"
953 onclick="return showAddAnotherPopup(this)">
954 <img src="%simg/poste-apercu.png"
955 title="Aperçu du poste" />
956 </a>
957 <a href="%s">%s [%d]</a>
958 <br />""" % \
959 (reverse('poste_apercu', args=(obj.responsable.id,)),
960 settings.STATIC_URL,
961 reverse('admin:rh_poste_change', args=(obj.responsable.id,)),
962 obj.responsable.nom,
963 obj.responsable.id)
964 except:
965 responsable = ''
966
967 try:
968 dossier = obj.responsable.rh_dossiers.all() \
969 .order_by('-date_debut')[0]
970 employe_id = dossier.employe.id
971 employe_html = u"""<br />
972 <a href="%s"
973 onclick="return showAddAnotherPopup(this)">
974 <img src="%simg/employe-apercu.png"
975 title="Aperçu de l'employé">
976 </a>
977 <a href="%s">%s</a>""" % \
978 (reverse('employe_apercu', args=(employe_id,)),
979 settings.STATIC_URL,
980 reverse('admin:rh_employe_change', args=(employe_id,)),
981 dossier.employe)
982 except:
983 employe_html = ""
984
985 return "%s %s" % (responsable, employe_html)
986 _responsable.short_description = 'Responsable'
987 _responsable.allow_tags = True
988
989 def _date_debut(self, obj):
990 return date_format(obj.date_debut)
991 _date_debut.short_description = u'Début'
992 _date_debut.admin_order_field = 'date_debut'
993
994 def _date_fin(self, obj):
995 return date_format(obj.date_fin)
996 _date_fin.short_description = u'Fin'
997 _date_fin.admin_order_field = 'date_fin'
998
999 def _dae(self, poste):
1000 apercu_link = ""
1001 postes_dae = poste.postes_dae.all()
1002 if len(postes_dae) > 0:
1003 poste_dae = postes_dae[0]
1004 apercu_link = \
1005 u'<a title="Aperçu du dossier" href="%s" ' \
1006 u'onclick="return showAddAnotherPopup(this);">' \
1007 u'<img src="%simg/loupe.png" /></a>' % (reverse(
1008 'poste_consulter', args=("dae-%s" % poste_dae.id,)
1009 ), settings.STATIC_URL)
1010 return apercu_link
1011 _dae.allow_tags = True
1012 _dae.short_description = u"DAE"
1013
1014 def save_formset(self, request, form, formset, change):
1015 instances = formset.save(commit=False)
1016 for instance in instances:
1017 if instance.__class__ == rh.PosteCommentaire:
1018 instance.owner = request.user
1019 instance.date_creation = datetime.datetime.now()
1020 instance.save()
1021 formset.save_m2m()
1022
1023
1024 class ResponsableInline(admin.TabularInline):
1025 model = rh.ResponsableImplantation
1026 extra = 0
1027 fk_name = "implantation"
1028 form = ResponsableInlineForm
1029
1030
1031 class ResponsableImplantationAdmin(BaseAdmin):
1032 actions = None
1033 fields = ('nom', )
1034 inlines = (ResponsableInline, )
1035 list_filter = ('region', 'statut', )
1036 list_display = ('_region', '_nom', 'statut', '_responsable', )
1037 list_display_links = ('_nom',)
1038 list_per_page = 500
1039 readonly_fields = ('nom', )
1040 search_fields = (
1041 'nom',
1042 'responsable__employe__id',
1043 'responsable__employe__nom',
1044 'responsable__employe__prenom',
1045 )
1046 ordering = ('nom',)
1047 inlines = (ResponsableInline, )
1048
1049 def _region(self, obj):
1050 return obj.region.code
1051 _region.short_description = u"Région"
1052 _region.admin_order_field = 'region__code'
1053
1054 def _nom(self, obj):
1055 return obj.nom
1056 _nom.short_description = u"Implantation"
1057 _nom.admin_order_field = 'nom'
1058
1059 def _responsable(self, obj):
1060 try:
1061 employe = obj.responsable.employe
1062 dossiers = employe.dossiers_encours()
1063 if len(dossiers) == 0:
1064 return u"<span style='color: red;'>%s %s </span>" % (
1065 employe, u"sans dossier actif")
1066 else:
1067 return employe
1068 except Exception:
1069 if obj.statut in (1, 2): # ouverte, ouverture imminente
1070 css = "style='color: red;'"
1071 else:
1072 css = ""
1073 return u"<span %s>Pas de responsable</span>" % css
1074 _responsable.allow_tags = True
1075 _responsable.short_description = u"Responsable"
1076 _responsable.admin_order_field = 'responsable__employe__nom'
1077
1078 def has_add_permission(self, request=None):
1079 return False
1080
1081 def has_change_permission(self, request, obj=None):
1082 return in_drh_or_admin(request.user)
1083
1084 def has_delete_permission(self, request, obj=None):
1085 return False
1086
1087
1088 class ServiceAdminBase(ArchivableAdmin, DerniereModificationAdmin, BaseAdmin):
1089 list_display = ('nom', '_archive', 'derniere_modification')
1090 list_filter = ('archive', )
1091 fieldsets = (
1092 (None, {'fields': ('nom', 'archive')}),
1093 )
1094
1095
1096 class ServiceAdmin(reversion.VersionAdmin, ServiceAdminBase):
1097 ignore_duplicate_revisions = True
1098
1099
1100 class ServiceProxyAdmin(ServiceAdminBase):
1101 list_display = ('nom', '_organigramme', '_archive', )
1102 actions = None
1103
1104 def __init__(self, *args, **kwargs):
1105 super(ServiceProxyAdmin, self).__init__(*args, **kwargs)
1106 self.list_display_links = (None, )
1107
1108 def queryset(self, request):
1109 return super(ServiceProxyAdmin, self).queryset(request) \
1110 .annotate(num_postes=Count('rh_postes')) \
1111 .filter(num_postes__gt=0)
1112
1113 def has_add_permission(self, obj):
1114 return False
1115
1116 def has_change_permission(self, request, obj=None):
1117 user_groups = request.user.groups.all()
1118 if groups.grp_correspondants_rh in user_groups or \
1119 groups.grp_administrateurs in user_groups or \
1120 groups.grp_directeurs_bureau in user_groups or \
1121 in_drh_or_admin(request.user):
1122 return True
1123 return False
1124
1125 def _organigramme(self, obj):
1126 return """<a href="%s"><strong>Organigramme</strong></a>""" % \
1127 (reverse('rho_service', args=(obj.id,)))
1128 _organigramme.allow_tags = True
1129 _organigramme.short_description = "Organigramme"
1130
1131
1132 class StatutAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
1133 BaseAdmin):
1134 ignore_duplicate_revisions = True
1135 list_display = ('code', 'nom', 'derniere_modification')
1136 fieldsets = (
1137 (None, {
1138 'fields': ('code', 'nom', ),
1139 }),
1140 )
1141
1142
1143 class TauxChangeAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
1144 BaseAdmin):
1145 ignore_duplicate_revisions = True
1146 list_display = ('taux', 'devise', 'annee', 'derniere_modification')
1147 list_filter = ('devise',)
1148 fieldsets = (
1149 (None, {
1150 'fields': ('taux', 'devise', 'annee', ),
1151 }),
1152 )
1153
1154
1155 class TypeContratAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
1156 BaseAdmin):
1157 ignore_duplicate_revisions = True
1158 list_display = ('nom', 'nom_long', 'derniere_modification')
1159 fieldsets = (
1160 (None, {
1161 'fields': ('nom', 'nom_long', ),
1162 }),
1163 )
1164
1165
1166 class TypePosteAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
1167 BaseAdmin):
1168 ignore_duplicate_revisions = True
1169 search_fields = ('nom', 'nom_feminin', )
1170 list_display = ('nom', 'categorie_emploi', 'derniere_modification')
1171 list_filter = ('categorie_emploi', 'famille_professionnelle')
1172 fieldsets = (
1173 (None, {
1174 'fields': (
1175 'nom', 'nom_feminin', 'is_responsable', 'categorie_emploi',
1176 'famille_professionnelle',
1177 )
1178 }),
1179 )
1180
1181
1182 class TypeRemunerationAdmin(reversion.VersionAdmin, ArchivableAdmin,
1183 DerniereModificationAdmin, BaseAdmin):
1184 ignore_duplicate_revisions = True
1185 list_display = (
1186 'nom', 'type_paiement', 'nature_remuneration', '_archive',
1187 'derniere_modification'
1188 )
1189 list_filter = ('archive', )
1190 fieldsets = (
1191 (None, {
1192 'fields': (
1193 'nom', 'type_paiement', 'nature_remuneration', 'archive'
1194 )
1195 }),
1196 )
1197
1198
1199 class TypeRevalorisationAdmin(reversion.VersionAdmin,
1200 DerniereModificationAdmin, BaseAdmin):
1201 ignore_duplicate_revisions = True
1202 list_display = ('nom', 'derniere_modification')
1203 fieldsets = (
1204 (None, {'fields': ('nom',)}),
1205 )
1206
1207
1208 class ValeurPointAdmin(reversion.VersionAdmin, DerniereModificationAdmin,
1209 BaseAdmin):
1210 ignore_duplicate_revisions = True
1211 list_display = (
1212 '_devise_code', '_devise_nom', 'annee', 'implantation',
1213 'valeur', 'derniere_modification'
1214 )
1215 list_filter = ('annee', 'devise', 'implantation__region', )
1216 fieldsets = (
1217 (None, {'fields': ('valeur', 'devise', 'implantation', 'annee')}),
1218 )
1219
1220 def _devise_code(self, obj):
1221 return obj.devise.code
1222 _devise_code.short_description = "Code de la devise"
1223
1224 def _devise_nom(self, obj):
1225 return obj.devise.nom
1226 _devise_nom.short_description = "Nom de la devise"
1227
1228
1229 class ImplantationProxyAdmin(BaseAdmin):
1230 list_display = ('nom', '_organigramme')
1231 actions = None
1232
1233 def __init__(self, *args, **kwargs):
1234 super(ImplantationProxyAdmin, self).__init__(*args, **kwargs)
1235 self.list_display_links = (None, )
1236
1237 def has_add_permission(self, obj):
1238 return False
1239
1240 def has_change_permission(self, request, obj=None):
1241 user_groups = request.user.groups.all()
1242 if groups.grp_correspondants_rh in user_groups or \
1243 groups.grp_administrateurs in user_groups or \
1244 groups.grp_directeurs_bureau in user_groups or \
1245 in_drh_or_admin(request.user):
1246 return True
1247 return False
1248
1249 def _organigramme(self, obj):
1250 return '<a href="%s"><strong>Organigramme</strong></a>' % (
1251 reverse('rho_implantation', args=(obj.id,))
1252 )
1253 _organigramme.allow_tags = True
1254 _organigramme.short_description = "Organigramme"
1255
1256
1257 class RegionProxyAdmin(BaseAdmin):
1258 list_display = ('nom', '_organigramme')
1259 actions = None
1260
1261 def __init__(self, *args, **kwargs):
1262 super(RegionProxyAdmin, self).__init__(*args, **kwargs)
1263 self.list_display_links = (None, )
1264
1265 def has_add_permission(self, obj):
1266 return False
1267
1268 def has_change_permission(self, request, obj=None):
1269 user_groups = request.user.groups.all()
1270 if groups.grp_correspondants_rh in user_groups or \
1271 groups.grp_administrateurs in user_groups or \
1272 groups.grp_directeurs_bureau in user_groups or \
1273 in_drh_or_admin(request.user):
1274 return True
1275 return False
1276
1277 def _organigramme(self, obj):
1278 return """<a href="%s"><strong>Organigramme</strong></a>""" % (
1279 reverse('rho_region', args=(obj.id,))
1280 )
1281 _organigramme.allow_tags = True
1282 _organigramme.short_description = "Organigramme"
1283
1284
1285 admin.site.register(rh.Classement, ClassementAdmin)
1286 admin.site.register(rh.Devise, DeviseAdmin)
1287 admin.site.register(rh.Dossier, DossierAdmin)
1288 admin.site.register(EmployeProxy, EmployeProxyAdmin)
1289 admin.site.register(ServiceProxy, ServiceProxyAdmin)
1290 admin.site.register(rh.Employe, EmployeAdmin)
1291 admin.site.register(rh.CategorieEmploi, CategorieEmploiAdmin)
1292 admin.site.register(rh.FamilleProfessionnelle)
1293 admin.site.register(rh.OrganismeBstg, OrganismeBstgAdmin)
1294 admin.site.register(rh.Poste, PosteAdmin)
1295 admin.site.register(
1296 rh.ResponsableImplantationProxy, ResponsableImplantationAdmin
1297 )
1298 admin.site.register(rh.Service, ServiceAdmin)
1299 admin.site.register(rh.Statut, StatutAdmin)
1300 admin.site.register(rh.TauxChange, TauxChangeAdmin)
1301 admin.site.register(rh.TypeContrat, TypeContratAdmin)
1302 admin.site.register(rh.TypePoste, TypePosteAdmin)
1303 admin.site.register(rh.TypeRemuneration, TypeRemunerationAdmin)
1304 admin.site.register(rh.TypeRevalorisation, TypeRevalorisationAdmin)
1305 admin.site.register(rh.ValeurPoint, ValeurPointAdmin)
1306 admin.site.register(ImplantationProxy, ImplantationProxyAdmin)
1307 admin.site.register(RegionProxy, RegionProxyAdmin)