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