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