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