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