search
[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 search_fields = ('nom', 'prenom' )
231 exclude = ('actif', )
232 list_editable = ('statut', )
233 list_display = ('nom', 'prenom', 'offre_emploi',
234 'voir_offre_emploi', 'calculer_moyenne',
235 'afficher_candidat', '_date_creation', 'statut', )
236 list_filter = ('offre_emploi', 'offre_emploi__region', 'statut', )
237
238 fieldsets = (
239 ("Offre d'emploi", {
240 'fields': ('offre_emploi', )
241 }),
242 ('Informations personnelles', {
243 'fields': ('prenom','nom','genre', 'nationalite',
244 'situation_famille', 'nombre_dependant',)
245 }),
246 ('Coordonnées', {
247 'fields': ('telephone', 'email', 'adresse', 'ville',
248 'etat_province', 'code_postal', 'pays', )
249 }),
250 ('Informations professionnelles', {
251 'fields': ('niveau_diplome','employeur_actuel',
252 'poste_actuel', 'domaine_professionnel',)
253 }),
254 ('Traitement', {
255 'fields': ('statut', )
256 }),
257 )
258 inlines = [
259 CandidatPieceInline,
260 CandidatEvaluationInline,
261 ]
262
263 actions = ['envoyer_courriel_candidats']
264
265 def _date_creation(self, obj):
266 return obj.date_creation
267 _date_creation.order_field = "date_creation"
268 _date_creation.short_description = "Date de création"
269
270 ### Actions à afficher
271 def get_actions(self, request):
272 actions = super(CandidatAdmin, self).get_actions(request)
273 del actions['delete_selected']
274 return actions
275
276 ### Envoyer un courriel à des candidats
277 def envoyer_courriel_candidats(modeladmin, obj, candidats):
278 selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
279
280 return HttpResponseRedirect(reverse('selectionner_template')+
281 "?ids=%s" % (",".join(selected)))
282 envoyer_courriel_candidats.short_description = u'Envoyer courriel'
283
284 ### Évaluer un candidat
285 def evaluer_candidat(self, obj):
286 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
287 (reverse('admin:recrutement_candidatevaluation_changelist'),
288 obj.id)
289 evaluer_candidat.allow_tags = True
290 evaluer_candidat.short_description = 'Évaluation'
291
292 ### Afficher un candidat
293 def afficher_candidat(self, obj):
294 return "<a href='%s'>Voir le candidat</a>" % \
295 (reverse('admin:recrutement_proxycandidat_change', args=(obj.id,)))
296 afficher_candidat.allow_tags = True
297 afficher_candidat.short_description = u'Détails du candidat'
298
299 ### Voir l'offre d'emploi
300 def voir_offre_emploi(self, obj):
301 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
302 (reverse('admin:recrutement_proxyoffreemploi_change',
303 args=(obj.offre_emploi.id,)))
304 voir_offre_emploi.allow_tags = True
305 voir_offre_emploi.short_description = "Afficher l'offre d'emploi"
306
307 ### Calculer la moyenne des notes
308 def calculer_moyenne(self, obj):
309 evaluations = CandidatEvaluation.objects.filter(candidat=obj)
310
311 notes = [evaluation.note for evaluation in evaluations \
312 if evaluation.note is not None]
313
314 if len(notes) > 0:
315 moyenne_votes = float(sum(notes)) / len(notes)
316 else:
317 moyenne_votes = "Non disponible"
318
319 totales = len(evaluations)
320 faites = len(notes)
321
322 if obj.statut == 'REC':
323 if totales == faites:
324 color = "green"
325 elif faites > 0 and float(totales) / float(faites) >= 2:
326 color = "orange"
327 else:
328 color = "red"
329 else:
330 color = "black"
331
332 return """<span style="color: %s;">%s (%s/%s)</span>""" % (color, moyenne_votes, faites, totales)
333 calculer_moyenne.allow_tags = True
334 calculer_moyenne.short_description = "Notation"
335
336 ### Permissions add, delete, change
337 def has_add_permission(self, request):
338 user_groupes = request.user.groups.all()
339 if request.user.is_superuser is True or \
340 grp_drh_recrutement in user_groupes or \
341 grp_directeurs_bureau_recrutement in user_groupes or \
342 grp_administrateurs_recrutement in user_groupes:
343 return True
344 return False
345
346 def has_delete_permission(self, request, obj=None):
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_change_permission(self, request, obj=None):
356 user_groupes = request.user.groups.all()
357 if request.user.is_superuser is True or \
358 grp_correspondants_rh_recrutement in user_groupes or \
359 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_correspondants_rh_recrutement in user_groupes or \
382 grp_administrateurs_recrutement in user_groupes:
383 employe = get_emp(request.user)
384 return qs.select_related('candidats').\
385 filter(offre_emploi__region=employe.implantation.region)
386
387 if grp_evaluateurs_recrutement in user_groupes:
388 evaluateur = Evaluateur.objects.get(user=request.user)
389 candidat_ids = [e.candidat.id for e in
390 CandidatEvaluation.objects.filter(evaluateur=evaluateur)]
391 return qs.select_related('candidats').filter(id__in=candidat_ids)
392 return qs.none()
393
394
395 class ProxyCandidatAdmin(CandidatAdmin):
396 list_editable = ()
397 readonly_fields = ('statut', 'offre_emploi', 'prenom', 'nom',
398 'genre', 'nationalite', 'situation_famille',
399 'nombre_dependant', 'telephone', 'email', 'adresse',
400 'ville', 'etat_province', 'code_postal', 'pays',
401 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
402 'domaine_professionnel', 'pieces_jointes',)
403 fieldsets = (
404 ("Offre d'emploi", {
405 'fields': ('offre_emploi', )
406 }),
407 ('Informations personnelles', {
408 'fields': ('prenom','nom','genre', 'nationalite',
409 'situation_famille', 'nombre_dependant',)
410 }),
411 ('Coordonnées', {
412 'fields': ('telephone', 'email', 'adresse', 'ville',
413 'etat_province', 'code_postal', 'pays', )
414 }),
415 ('Informations professionnelles', {
416 'fields': ('niveau_diplome','employeur_actuel',
417 'poste_actuel', 'domaine_professionnel',)
418 }),
419 )
420 inlines = ()
421
422 ### Lieu de redirection après le change
423 def response_change(self, request, obj):
424 response = super(ProxyCandidatAdmin, self).response_change(request, obj)
425 user_groupes = request.user.groups.all()
426 return HttpResponseRedirect(reverse\
427 ('admin:recrutement_proxycandidat_changelist'))
428
429 ### Permissions add, delete, change
430 def has_add_permission(self, request):
431 return False
432
433 def has_delete_permission(self, request, obj=None):
434 return False
435
436 def has_change_permission(self, request, obj=None):
437 user_groupes = request.user.groups.all()
438 if request.user.is_superuser is True or \
439 grp_drh_recrutement in user_groupes or \
440 grp_evaluateurs_recrutement in user_groupes or \
441 grp_directeurs_bureau_recrutement in user_groupes or \
442 grp_administrateurs_recrutement in user_groupes:
443 return True
444 return False
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 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 def has_delete_permission(self, request, obj=None):
487 user_groupes = request.user.groups.all()
488 if request.user.is_superuser is True or \
489 grp_drh_recrutement in user_groupes or \
490 grp_directeurs_bureau_recrutement in user_groupes or \
491 grp_administrateurs_recrutement in user_groupes:
492 return True
493 return False
494
495 def has_change_permission(self, request, obj=None):
496 user_groupes = request.user.groups.all()
497 if request.user.is_superuser is True or \
498 grp_drh_recrutement in user_groupes or \
499 grp_directeurs_bureau_recrutement in user_groupes or \
500 grp_administrateurs_recrutement in user_groupes:
501 return True
502 return False
503
504 class CandidatEvaluationAdmin(admin.ModelAdmin):
505 search_fields = ('candidat__nom', 'candidat__prenom' )
506 list_display = ('_candidat', '_statut', '_offre_emploi', 'evaluateur', '_note',
507 '_commentaire', )
508 readonly_fields = ('candidat', 'evaluateur')
509 list_filter = ('candidat__statut', 'candidat__offre_emploi',)
510 fieldsets = (
511 ('Évaluation du candidat', {
512 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
513 }),
514 )
515
516 ### Actions à afficher
517 def get_actions(self, request):
518 actions = super(CandidatEvaluationAdmin, self).get_actions(request)
519 del actions['delete_selected']
520 return actions
521
522 ### Afficher la note
523 def _note(self, obj):
524 """
525 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
526 un lien pour Évaluer le candidat.
527 Sinon afficher la note.
528 """
529 if obj.note is None:
530 return "<a href='%s'>Candidat non évalué</a>" % \
531 (reverse('admin:recrutement_candidatevaluation_change',
532 args=(obj.id,)))
533 return "<a href='%s'>%s</a>" % \
534 (reverse('admin:recrutement_candidatevaluation_change',
535 args=(obj.id,)), obj.note)
536 _note.allow_tags = True
537 _note.short_description = "Note"
538 _note.admin_order_field = 'note'
539
540 def _statut(self, obj):
541 return obj.candidat.get_statut_display()
542 _statut.order_field = 'candidat__statut'
543 _statut.short_description = 'Statut'
544
545
546 ### Lien en lecture seule vers le candidat
547 def _candidat(self, obj):
548 return "<a href='%s'>%s</a>" \
549 % (reverse('admin:recrutement_proxycandidat_change',
550 args=(obj.candidat.id,)), obj.candidat)
551 _candidat.allow_tags = True
552 _candidat.short_description = 'Candidat'
553
554 ### Afficher commentaire
555 def _commentaire(self, obj):
556 """
557 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
558 dans le champ commentaire, Aucun au lieu de (None)
559 Sinon afficher la note.
560 """
561 if obj.commentaire is None:
562 return "Aucun"
563 return obj.commentaire
564 _commentaire.allow_tags = True
565 _commentaire.short_description = "Commentaire"
566
567 ### Afficher offre d'emploi
568 def _offre_emploi(self, obj):
569 return "<a href='%s'>%s</a>" % \
570 (reverse('admin:recrutement_proxyoffreemploi_change',
571 args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
572 _offre_emploi.allow_tags = True
573 _offre_emploi.short_description = "Voir offre d'emploi"
574
575 def has_add_permission(self, request):
576 return False
577
578 def has_delete_permission(self, request, obj=None):
579 return False
580
581 def has_change_permission(self, request, obj=None):
582 """
583 Permettre la visualisation dans la changelist
584 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
585 le request.user
586 """
587 user_groupes = request.user.groups.all()
588
589 if grp_drh_recrutement in user_groupes or \
590 grp_correspondants_rh_recrutement in user_groupes or \
591 grp_directeurs_bureau_recrutement in user_groupes or \
592 grp_administrateurs_recrutement in user_groupes:
593 is_recrutement = True
594 else:
595 is_recrutement = False
596
597 try:
598 Evaluateur.objects.get(user=request.user)
599 is_evaluateur = True
600 except:
601 is_evaluateur = False
602
603 if obj is None and (is_recrutement or is_evaluateur):
604 return True
605
606 if request.user.is_superuser is True:
607 return True
608
609 try:
610 return request.user == obj.evaluateur.user
611 except:
612 return False
613
614 ### Queryset
615 def queryset(self, request):
616 """
617 Afficher uniquement les évaluations de l'évaluateur, sauf si
618 l'utilisateur est dans les groupes suivants.
619 """
620 qs = self.model._default_manager.get_query_set().select_related('offre_emploi')
621 user_groupes = request.user.groups.all()
622
623 if grp_drh_recrutement in user_groupes or \
624 grp_correspondants_rh_recrutement in user_groupes or \
625 grp_directeurs_bureau_recrutement in user_groupes or \
626 grp_administrateurs_recrutement in user_groupes:
627 return qs
628
629 evaluateur = Evaluateur.objects.get(user=request.user)
630 candidats_evaluations = \
631 CandidatEvaluation.objects.filter(evaluateur=evaluateur,
632 candidat__statut__in=('REC', ))
633 candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
634 return qs.filter(id__in=candidats_evaluations_ids)
635
636 class CourrielTemplateAdmin(VersionAdmin):
637 ### Actions à afficher
638 def get_actions(self, request):
639 actions = super(CourrielTemplateAdmin, self).get_actions(request)
640 del actions['delete_selected']
641 return actions
642
643 admin.site.register(OffreEmploi, OffreEmploiAdmin)
644 admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
645 admin.site.register(Candidat, CandidatAdmin)
646 admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
647 admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
648 admin.site.register(Evaluateur, EvaluateurAdmin)
649 admin.site.register(CourrielTemplate, CourrielTemplateAdmin)