DRH evaluateur uniquement
[auf_rh_dae.git] / project / recrutement / admin.py
1 # -*- encoding: utf-8 -*-
2
3 from django.core.urlresolvers import reverse
4 from django.http import HttpResponseRedirect
5 from django.contrib import admin
6 from django.forms.models import BaseInlineFormSet
7 from django.db.models import Avg
8
9 from reversion.admin import VersionAdmin
10 from datamaster_modeles.models import Region, Bureau
11 from project.rh import models as rh
12
13 from project.dae.utils import get_employe_from_user as get_emp
14 from recrutement.models import *
15 from recrutement.workflow import grp_drh_recrutement, grp_directeurs_bureau_recrutement, \
16 grp_administrateurs_recrutement, \
17 grp_correspondants_rh_recrutement
18
19 from recrutement.forms import *
20
21 ### CONSTANTES
22 IMPLANTATIONS_CENTRALES = [15, 19]
23
24 class OrderedChangeList(admin.views.main.ChangeList):
25 """
26 Surcharge pour appliquer le order_by d'un annotate
27 """
28 def get_query_set(self):
29 qs = super(OrderedChangeList, self).get_query_set()
30 qs = qs.order_by('-moyenne')
31 return qs
32
33 class OffreEmploiAdmin(VersionAdmin):
34 date_hierarchy = 'date_creation'
35 list_display = ('nom', 'date_limite', 'region', 'statut',
36 'est_affiche', '_candidatsList', )
37 exclude = ('actif', 'poste_nom', 'resume',)
38 list_filter = ('statut',)
39 actions = ['affecter_evaluateurs_offre_emploi', ]
40 form = OffreEmploiForm
41
42 ### Actions à afficher
43 def get_actions(self, request):
44 actions = super(OffreEmploiAdmin, self).get_actions(request)
45 del actions['delete_selected']
46 return actions
47
48 ### Affecter un évaluateurs à des offres d'emploi
49 def affecter_evaluateurs_offre_emploi(modeladmin, obj, candidats):
50 selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
51
52 return HttpResponseRedirect(reverse('affecter_evaluateurs_offre_emploi')+
53 "?ids=%s" % (",".join(selected)))
54 affecter_evaluateurs_offre_emploi.short_description = u'Affecter évaluateur(s)'
55
56 ### Afficher la liste des candidats pour l'offre d'emploi
57 def _candidatsList(self, obj):
58 return "<a href='%s?offre_emploi__id__exact=%s'>Voir les candidats \
59 </a>" % (reverse('admin:recrutement_candidat_changelist'), obj.id)
60 _candidatsList.allow_tags = True
61 _candidatsList.short_description = "Afficher la liste des candidats"
62
63 ### Formulaire
64 def get_form(self, request, obj=None, **kwargs):
65 form = super(OffreEmploiAdmin, self).get_form(request, obj, **kwargs)
66 employe = get_emp(request.user)
67 user_groupes = request.user.groups.all()
68
69
70 # Region
71 if form.declared_fields.has_key('region'):
72 region_field = form.declared_fields['region']
73 else:
74 region_field = form.base_fields['region']
75
76 if grp_drh_recrutement in user_groupes:
77 region_field.queryset = Region.objects.all()
78 else:
79 region_field.queryset = Region.objects.\
80 filter(id=employe.implantation.region.id)
81
82 # Poste
83 if form.declared_fields.has_key('poste'):
84 poste_field = form.declared_fields['poste']
85 else:
86 poste_field = form.base_fields['poste']
87
88 if grp_drh_recrutement in user_groupes:
89 poste_field.queryset = rh.Poste.objects.all()
90 else:
91 poste_field.queryset = rh.Poste.objects.\
92 filter(implantation__region=employe.implantation.region).\
93 exclude(implantation__in=IMPLANTATIONS_CENTRALES)
94
95 # Bureau
96 if form.declared_fields.has_key('bureau'):
97 bureau_field = form.declared_fields['bureau']
98 else:
99 bureau_field = form.base_fields['bureau']
100
101 if grp_drh_recrutement in user_groupes:
102 bureau_field.queryset = Bureau.objects.all()
103 else:
104 bureau_field.queryset = Bureau.objects.\
105 filter(region=employe.implantation.region)
106
107 return form
108
109 ### Queryset
110 def queryset(self, request):
111 qs = self.model._default_manager.get_query_set().select_related('offre_emploi')
112 user_groupes = request.user.groups.all()
113 if grp_drh_recrutement in user_groupes:
114 return qs
115
116 if grp_directeurs_bureau_recrutement in user_groupes or \
117 grp_correspondants_rh_recrutement in user_groupes or \
118 grp_administrateurs_recrutement in user_groupes:
119 employe = get_emp(request.user)
120 return qs.filter(region=employe.implantation.region)
121
122 if Evaluateur.objects.filter(user=request.user).exists():
123 evaluateur = Evaluateur.objects.get(user=request.user)
124 offre_ids = [e.candidat.offre_emploi_id for e in
125 CandidatEvaluation.objects.select_related('candidat').filter(evaluateur=evaluateur)]
126 return qs.filter(id__in=offre_ids)
127
128 return qs.none()
129
130 ### Permission add, delete, change
131 def has_add_permission(self, request):
132 user_groupes = request.user.groups.all()
133 if request.user.is_superuser is True or \
134 grp_drh_recrutement in user_groupes or \
135 grp_directeurs_bureau_recrutement in user_groupes or \
136 grp_administrateurs_recrutement in user_groupes:
137 return True
138 return False
139
140 def has_delete_permission(self, request, obj=None):
141 user_groupes = request.user.groups.all()
142 if request.user.is_superuser is True or \
143 grp_drh_recrutement in user_groupes or \
144 grp_directeurs_bureau_recrutement in user_groupes or \
145 grp_administrateurs_recrutement in user_groupes:
146 return True
147 return False
148
149 def has_change_permission(self, request, obj=None):
150 user_groupes = request.user.groups.all()
151 if request.user.is_superuser is True or \
152 grp_drh_recrutement in user_groupes or \
153 grp_directeurs_bureau_recrutement in user_groupes or \
154 grp_administrateurs_recrutement in user_groupes:
155 return True
156 return False
157
158 class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
159 list_display = ('nom', 'date_limite', 'region', 'statut',
160 'est_affiche')
161 readonly_fields = ('description', 'bureau', 'duree_affectation',
162 'renumeration', 'debut_affectation', 'lieu_affectation',
163 'nom', 'resume', 'date_limite', 'region', 'poste')
164 fieldsets = (
165 ('Nom', {
166 'fields': ('nom', )
167 }),
168 ('Description générale', {
169 'fields': ('description', 'date_limite', )
170 }),
171 ('Coordonnées', {
172 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
173 }),
174 ('Autre', {
175 'fields': ('debut_affectation', 'duree_affectation',
176 'renumeration', )
177 }),
178 )
179 inlines = []
180
181
182 ### Lieu de redirection après le change
183 def response_change(self, request, obj):
184 return HttpResponseRedirect(reverse\
185 ('admin:recrutement_proxyoffreemploi_changelist'))
186
187 ### Formulaire
188 def get_form(self, request, obj=None, **kwargs):
189 form = super(OffreEmploiAdmin, self).get_form(request, obj, **kwargs)
190 return form
191
192 ### Permissions add, delete, change
193 def has_add_permission(self, request):
194 return False
195
196 def has_delete_permission(self, request, obj=None):
197 return False
198
199 def has_change_permission(self, request, obj=None):
200 return True
201
202 class CandidatPieceInline(admin.TabularInline):
203 model = CandidatPiece
204 fields = ('candidat', 'nom', 'path',)
205 extra = 1
206 max_num = 3
207
208 class ReadOnlyCandidatPieceInline(CandidatPieceInline):
209 readonly_fields = ('candidat', 'nom', 'path', )
210 cand_delete = False
211
212
213 class CandidatEvaluationInlineFormSet(BaseInlineFormSet):
214 """
215 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
216 """
217 def __init__(self, *args, **kwargs):
218 super(CandidatEvaluationInlineFormSet, self).__init__(*args, **kwargs)
219 self.can_delete = False
220
221 class CandidatEvaluationInline(admin.TabularInline):
222 model = CandidatEvaluation
223 fields = ('evaluateur', 'note', 'commentaire')
224 max_num = 0
225 extra = 0
226 formset = CandidatEvaluationInlineFormSet
227
228 ### Fields readonly
229 def get_readonly_fields(self, request, obj=None):
230 """
231 Empêche la modification des évaluations
232 """
233 if obj:
234 return self.readonly_fields+('evaluateur', 'note', 'commentaire')
235 return self.readonly_fields
236
237 class CandidatAdmin(VersionAdmin):
238 search_fields = ('nom', 'prenom' )
239 exclude = ('actif', )
240 list_editable = ('statut', )
241 list_display = ('nom', 'prenom', 'offre_emploi',
242 'voir_offre_emploi', 'calculer_moyenne',
243 'afficher_candidat', '_date_creation', 'statut', )
244 list_filter = ('offre_emploi', 'offre_emploi__region', 'statut', )
245
246 fieldsets = (
247 ("Offre d'emploi", {
248 'fields': ('offre_emploi', )
249 }),
250 ('Informations personnelles', {
251 'fields': ('prenom','nom','genre', 'nationalite',
252 'situation_famille', 'nombre_dependant',)
253 }),
254 ('Coordonnées', {
255 'fields': ('telephone', 'email', 'adresse', 'ville',
256 'etat_province', 'code_postal', 'pays', )
257 }),
258 ('Informations professionnelles', {
259 'fields': ('niveau_diplome','employeur_actuel',
260 'poste_actuel', 'domaine_professionnel',)
261 }),
262 ('Traitement', {
263 'fields': ('statut', )
264 }),
265 )
266 inlines = [
267 CandidatPieceInline,
268 CandidatEvaluationInline,
269 ]
270
271 actions = ['envoyer_courriel_candidats']
272
273 def _date_creation(self, obj):
274 return obj.date_creation
275 _date_creation.order_field = "date_creation"
276 _date_creation.short_description = "Date de création"
277
278 ### Actions à afficher
279 def get_actions(self, request):
280 actions = super(CandidatAdmin, self).get_actions(request)
281 del actions['delete_selected']
282 return actions
283
284 ### Envoyer un courriel à des candidats
285 def envoyer_courriel_candidats(modeladmin, obj, candidats):
286 selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
287
288 return HttpResponseRedirect(reverse('selectionner_template')+
289 "?ids=%s" % (",".join(selected)))
290 envoyer_courriel_candidats.short_description = u'Envoyer courriel'
291
292 ### Évaluer un candidat
293 def evaluer_candidat(self, obj):
294 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
295 (reverse('admin:recrutement_candidatevaluation_changelist'),
296 obj.id)
297 evaluer_candidat.allow_tags = True
298 evaluer_candidat.short_description = 'Évaluation'
299
300 ### Afficher un candidat
301 def afficher_candidat(self, obj):
302 return "<a href='%s'>Voir le candidat</a>" % \
303 (reverse('admin:recrutement_proxycandidat_change', args=(obj.id,)))
304 afficher_candidat.allow_tags = True
305 afficher_candidat.short_description = u'Détails du candidat'
306
307 ### Voir l'offre d'emploi
308 def voir_offre_emploi(self, obj):
309 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
310 (reverse('admin:recrutement_proxyoffreemploi_change',
311 args=(obj.offre_emploi.id,)))
312 voir_offre_emploi.allow_tags = True
313 voir_offre_emploi.short_description = "Afficher l'offre d'emploi"
314
315 ### Calculer la moyenne des notes
316 def calculer_moyenne(self, obj):
317 evaluations = CandidatEvaluation.objects.filter(candidat=obj)
318
319 notes = [evaluation.note for evaluation in evaluations \
320 if evaluation.note is not None]
321
322 if len(notes) > 0:
323 moyenne_votes = round(float(sum(notes)) / len(notes), 2)
324 else:
325 moyenne_votes = "Non disponible"
326
327 totales = len(evaluations)
328 faites = len(notes)
329
330 if obj.statut == 'REC':
331 if totales == faites:
332 color = "green"
333 elif faites > 0 and float(totales) / float(faites) >= 2:
334 color = "orange"
335 else:
336 color = "red"
337 else:
338 color = "black"
339
340 return """<span style="color: %s;">%s (%s/%s)</span>""" % (color, moyenne_votes, faites, totales)
341 calculer_moyenne.allow_tags = True
342 calculer_moyenne.short_description = "Notation"
343 calculer_moyenne.admin_order_field = ""
344
345 ### Permissions add, delete, change
346 def has_add_permission(self, request):
347 user_groupes = request.user.groups.all()
348 if request.user.is_superuser is True or \
349 grp_drh_recrutement in user_groupes or \
350 grp_directeurs_bureau_recrutement in user_groupes or \
351 grp_administrateurs_recrutement in user_groupes:
352 return True
353 return False
354
355 def has_delete_permission(self, request, obj=None):
356 user_groupes = request.user.groups.all()
357 if request.user.is_superuser is True or \
358 grp_drh_recrutement in user_groupes or \
359 grp_directeurs_bureau_recrutement in user_groupes or \
360 grp_administrateurs_recrutement in user_groupes:
361 return True
362 return False
363
364 def has_change_permission(self, request, obj=None):
365 user_groupes = request.user.groups.all()
366 if request.user.is_superuser is True or \
367 grp_correspondants_rh_recrutement in user_groupes or \
368 grp_drh_recrutement in user_groupes or \
369 grp_directeurs_bureau_recrutement in user_groupes or \
370 grp_administrateurs_recrutement in user_groupes:
371 return True
372 return False
373
374 def get_changelist(self, request, **kwargs):
375 return OrderedChangeList
376
377 def queryset(self, request):
378 """
379 Spécifie un queryset limité, autrement Django exécute un
380 select_related() sans paramètre, ce qui a pour effet de charger tous
381 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
382 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
383 génération d'une requête infinie.
384
385 """
386
387 qs = self.model._default_manager.get_query_set().select_related('offre_emploi').annotate(moyenne=Avg('evaluations__note'))
388
389 user_groupes = request.user.groups.all()
390 if grp_drh_recrutement in user_groupes:
391 return qs
392
393 if grp_directeurs_bureau_recrutement in user_groupes or \
394 grp_correspondants_rh_recrutement in user_groupes or \
395 grp_administrateurs_recrutement in user_groupes:
396 employe = get_emp(request.user)
397 return qs.filter(offre_emploi__region=employe.implantation.region)
398
399 if Evaluateur.objects.filter(user=request.user).exists():
400 evaluateur = Evaluateur.objects.get(user=request.user)
401 candidat_ids = [e.candidat.id for e in
402 CandidatEvaluation.objects.filter(evaluateur=evaluateur)]
403 return qs.filter(id__in=candidat_ids)
404 return qs.none()
405
406
407 class ProxyCandidatAdmin(CandidatAdmin):
408 list_editable = ()
409 readonly_fields = ('statut', 'offre_emploi', 'prenom', 'nom',
410 'genre', 'nationalite', 'situation_famille',
411 'nombre_dependant', 'telephone', 'email', 'adresse',
412 'ville', 'etat_province', 'code_postal', 'pays',
413 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
414 'domaine_professionnel', 'pieces_jointes',)
415 fieldsets = (
416 ("Offre d'emploi", {
417 'fields': ('offre_emploi', )
418 }),
419 ('Informations personnelles', {
420 'fields': ('prenom','nom','genre', 'nationalite',
421 'situation_famille', 'nombre_dependant',)
422 }),
423 ('Coordonnées', {
424 'fields': ('telephone', 'email', 'adresse', 'ville',
425 'etat_province', 'code_postal', 'pays', )
426 }),
427 ('Informations professionnelles', {
428 'fields': ('niveau_diplome','employeur_actuel',
429 'poste_actuel', 'domaine_professionnel',)
430 }),
431 )
432 inlines = (CandidatEvaluationInline, )
433
434 def has_add_permission(self, request):
435 return False
436
437 def has_delete_permission(self, request, obj=None):
438 return False
439
440 def has_change_permission(self, request, obj=None):
441 return True
442
443 def get_actions(self, request):
444 return None
445
446 class CandidatPieceAdmin(admin.ModelAdmin):
447 list_display = ('nom', 'candidat', )
448
449 ### Queryset
450 def queryset(self, request):
451 """
452 Spécifie un queryset limité, autrement Django exécute un
453 select_related() sans paramètre, ce qui a pour effet de charger tous
454 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
455 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
456 génération d'une requête infinie.
457 Affiche la liste de candidats que si le user connecté
458 possède un Evaluateur
459 """
460 qs = self.model._default_manager.get_query_set()
461 return qs.select_related('candidat')
462
463 class EvaluateurAdmin(VersionAdmin):
464 fieldsets = (
465 ("Utilisateur", {
466 'fields': ('user',)
467 }),
468 )
469
470 ### Actions à afficher
471 def get_actions(self, request):
472 actions = super(EvaluateurAdmin, self).get_actions(request)
473 del actions['delete_selected']
474 return actions
475
476 ### Permissions add, delete, change
477 def has_add_permission(self, request):
478 user_groupes = request.user.groups.all()
479 if request.user.is_superuser is True or \
480 grp_drh_recrutement in user_groupes:
481 return True
482 return False
483
484 def has_delete_permission(self, request, obj=None):
485 user_groupes = request.user.groups.all()
486 if request.user.is_superuser is True or \
487 grp_drh_recrutement in user_groupes:
488 return True
489 return False
490
491 def has_change_permission(self, request, obj=None):
492 user_groupes = request.user.groups.all()
493 if request.user.is_superuser is True or \
494 grp_drh_recrutement in user_groupes:
495 return True
496 return False
497
498 class CandidatEvaluationAdmin(admin.ModelAdmin):
499 search_fields = ('candidat__nom', 'candidat__prenom' )
500 list_display = ('_candidat', '_statut', '_offre_emploi', 'evaluateur', '_note',
501 '_commentaire', )
502 readonly_fields = ('candidat', 'evaluateur')
503 list_filter = ('candidat__statut', 'candidat__offre_emploi',)
504 fieldsets = (
505 ('Évaluation du candidat', {
506 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
507 }),
508 )
509
510 def get_actions(self, request):
511 # on stocke l'evaluateur connecté (pas forcément la meilleure place...)
512 try:
513 self.evaluateur = Evaluateur.objects.get(user=request.user)
514 except:
515 self.evaluateur = None
516
517 actions = super(CandidatEvaluationAdmin, self).get_actions(request)
518 del actions['delete_selected']
519 return actions
520
521 ### Afficher la note
522 def _note(self, obj):
523 """
524 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
525 un lien pour Évaluer le candidat.
526 Sinon afficher la note.
527 """
528 page = self.model.__name__.lower()
529 redirect_url = 'admin:recrutement_%s_change' % page
530
531 if obj.note is None:
532 label = "Candidat non évalué"
533 else:
534 label = obj.note
535
536 if self.evaluateur == obj.evaluateur:
537 return "<a href='%s'>%s</a>" % (reverse(redirect_url, args=(obj.id,)), label)
538 else:
539 return label
540 _note.allow_tags = True
541 _note.short_description = "Note"
542 _note.admin_order_field = 'note'
543
544 def _statut(self, obj):
545 return obj.candidat.get_statut_display()
546 _statut.order_field = 'candidat__statut'
547 _statut.short_description = 'Statut'
548
549
550 ### Lien en lecture seule vers le candidat
551 def _candidat(self, obj):
552 return "<a href='%s'>%s</a>" \
553 % (reverse('admin:recrutement_proxycandidat_change',
554 args=(obj.candidat.id,)), obj.candidat)
555 _candidat.allow_tags = True
556 _candidat.short_description = 'Candidat'
557
558 ### Afficher commentaire
559 def _commentaire(self, obj):
560 """
561 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
562 dans le champ commentaire, Aucun au lieu de (None)
563 Sinon afficher la note.
564 """
565 if obj.commentaire is None:
566 return "Aucun"
567 return obj.commentaire
568 _commentaire.allow_tags = True
569 _commentaire.short_description = "Commentaire"
570
571 ### Afficher offre d'emploi
572 def _offre_emploi(self, obj):
573 return "<a href='%s'>%s</a>" % \
574 (reverse('admin:recrutement_proxyoffreemploi_change',
575 args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
576 _offre_emploi.allow_tags = True
577 _offre_emploi.short_description = "Voir offre d'emploi"
578
579 def has_add_permission(self, request):
580 return False
581
582 def has_delete_permission(self, request, obj=None):
583 return False
584
585 def has_change_permission(self, request, obj=None):
586 """
587 Permettre la visualisation dans la changelist
588 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
589 le request.user
590 """
591 user_groupes = request.user.groups.all()
592
593 if request.user.is_superuser or \
594 grp_drh_recrutement in user_groupes or \
595 grp_correspondants_rh_recrutement in user_groupes or \
596 grp_directeurs_bureau_recrutement in user_groupes or \
597 grp_administrateurs_recrutement in user_groupes:
598 is_recrutement = True
599 else:
600 is_recrutement = False
601
602 try:
603 Evaluateur.objects.get(user=request.user)
604 is_evaluateur = True
605 except:
606 is_evaluateur = False
607
608 if obj is None and (is_recrutement or is_evaluateur):
609 return True
610
611 if request.user.is_superuser is True:
612 return True
613
614 try:
615 return request.user == obj.evaluateur.user
616 except:
617 return False
618
619 ### Queryset
620 def queryset(self, request):
621 """
622 Afficher uniquement les évaluations de l'évaluateur, sauf si
623 l'utilisateur est dans les groupes suivants.
624 """
625 qs = self.model._default_manager.get_query_set().select_related('offre_emploi')
626 user_groupes = request.user.groups.all()
627
628 if grp_drh_recrutement in user_groupes or \
629 grp_correspondants_rh_recrutement in user_groupes or \
630 grp_directeurs_bureau_recrutement in user_groupes or \
631 grp_administrateurs_recrutement in user_groupes:
632 return qs
633
634 evaluateur = Evaluateur.objects.get(user=request.user)
635 candidats_evaluations = \
636 CandidatEvaluation.objects.filter(evaluateur=evaluateur,
637 candidat__statut__in=('REC', ))
638 candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
639 return qs.filter(id__in=candidats_evaluations_ids)
640
641
642 class MesCandidatEvaluationAdmin(CandidatEvaluationAdmin):
643
644 def has_change_permission(self, request, obj=None):
645 try:
646 Evaluateur.objects.get(user=request.user)
647 is_evaluateur = True
648 except:
649 is_evaluateur = False
650
651 if obj is None and is_evaluateur:
652 return True
653
654 try:
655 return request.user == obj.evaluateur.user
656 except:
657 return False
658
659 def queryset(self, request):
660 qs = self.model._default_manager.get_query_set().select_related('offre_emploi')
661 evaluateur = Evaluateur.objects.get(user=request.user)
662 candidats_evaluations = \
663 CandidatEvaluation.objects.filter(evaluateur=evaluateur,
664 candidat__statut__in=('REC', ))
665 candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
666 return qs.filter(id__in=candidats_evaluations_ids)
667
668
669 class CourrielTemplateAdmin(VersionAdmin):
670 ### Actions à afficher
671 def get_actions(self, request):
672 actions = super(CourrielTemplateAdmin, self).get_actions(request)
673 del actions['delete_selected']
674 return actions
675
676 admin.site.register(OffreEmploi, OffreEmploiAdmin)
677 admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
678 admin.site.register(Candidat, CandidatAdmin)
679 admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
680 admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
681 admin.site.register(MesCandidatEvaluation, MesCandidatEvaluationAdmin)
682 admin.site.register(Evaluateur, EvaluateurAdmin)
683 admin.site.register(CourrielTemplate, CourrielTemplateAdmin)