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