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