Cleaning code
[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 ### Permissions add, delete, change
202 def has_add_permission(self, request):
203 return False
204
205 def has_delete_permission(self, request, obj=None):
206 return False
207
208 def has_change_permission(self, request, obj=None):
209 user_groupes = request.user.groups.all()
210 if grp_evaluateurs_recrutement in user_groupes or \
211 grp_drh_recrutement in user_groupes or \
212 grp_directeurs_bureau_recrutement in user_groupes or \
213 grp_administrateurs_recrutement in user_groupes:
214 return True
215 return False
216
217 class CandidatPieceInline(admin.TabularInline):
218 model = CandidatPiece
219 fields = ('candidat', 'nom', 'path',)
220 extra = 1
221 max_num = 3
222
223 class CandidatEvaluationInlineFormSet(BaseInlineFormSet):
224 """
225 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
226 """
227 def __init__(self, *args, **kwargs):
228 super(CandidatEvaluationInlineFormSet, self).__init__(*args, **kwargs)
229 self.can_delete = False
230
231 class CandidatEvaluationInline(admin.TabularInline):
232 model = CandidatEvaluation
233 fields = ('evaluateur', 'note', 'commentaire')
234 max_num = 0
235 extra = 0
236 formset = CandidatEvaluationInlineFormSet
237
238 ### Fields readonly
239 def get_readonly_fields(self, request, obj=None):
240 """
241 Empêche la modification des évaluations
242 """
243 if obj:
244 return self.readonly_fields+('evaluateur', 'note', 'commentaire')
245 return self.readonly_fields
246
247 class CandidatAdmin(VersionAdmin):
248 exclude = ('actif', )
249 date_hierarchy = 'date_creation'
250 list_display = ('nom', 'prenom', 'offre_emploi','statut',
251 'voir_offre_emploi', 'calculer_moyenne',
252 'afficher_candidat',)
253 list_filter = ('offre_emploi', )
254
255 fieldsets = (
256 ("Offre d'emploi", {
257 'fields': ('offre_emploi', )
258 }),
259 ('Informations personnelles', {
260 'fields': ('prenom','nom','genre', 'nationalite',
261 'situation_famille', 'nombre_dependant',)
262 }),
263 ('Coordonnées', {
264 'fields': ('telephone', 'email', 'adresse', 'ville',
265 'etat_province', 'code_postal', 'pays', )
266 }),
267 ('Informations professionnelles', {
268 'fields': ('niveau_diplome','employeur_actuel',
269 'poste_actuel', 'domaine_professionnel',)
270 }),
271 ('Traitement', {
272 'fields': ('statut', )
273 }),
274 )
275 inlines = [
276 CandidatPieceInline,
277 CandidatEvaluationInline,
278 ]
279
280 actions = ['envoyer_courriel_candidats']
281
282 ### Actions à afficher
283 def get_actions(self, request):
284 actions = super(CandidatAdmin, self).get_actions(request)
285 del actions['delete_selected']
286 return actions
287
288 ### Envoyer un courriel à des candidats
289 def envoyer_courriel_candidats(modeladmin, obj, candidats):
290 selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
291
292 return HttpResponseRedirect(reverse('selectionner_template')+
293 "?ids=%s" % (",".join(selected)))
294 envoyer_courriel_candidats.short_description = u'Envoyer courriel'
295
296 ### Évaluer un candidat
297 def evaluer_candidat(self, obj):
298 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
299 (reverse('admin:recrutement_candidatevaluation_changelist'),
300 obj.id)
301 evaluer_candidat.allow_tags = True
302 evaluer_candidat.short_description = 'Évaluation'
303
304 ### Afficher un candidat
305 def afficher_candidat(self, obj):
306 return "<a href='%s'>Voir le candidat</a>" % \
307 (reverse('admin:recrutement_proxycandidat_change', args=(obj.id,)))
308 afficher_candidat.allow_tags = True
309 afficher_candidat.short_description = u'Détails du candidat'
310
311 ### Voir l'offre d'emploi
312 def voir_offre_emploi(self, obj):
313 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
314 (reverse('admin:recrutement_proxyoffreemploi_change',
315 args=(obj.offre_emploi.id,)))
316 voir_offre_emploi.allow_tags = True
317 voir_offre_emploi.short_description = "Afficher l'offre d'emploi"
318
319 ### Calculer la moyenne des notes
320 def calculer_moyenne(self, obj):
321 evaluations = CandidatEvaluation.objects.filter(candidat=obj)
322 offre_emploi = obj.offre_emploi
323
324 notes = [evaluation.note for evaluation in evaluations.all() \
325 if evaluation.note is not None]
326
327 if len(notes) > 0:
328 moyenne_votes = float(sum(notes)) / len(notes)
329 else:
330 moyenne_votes = "Non disponible"
331 return moyenne_votes
332 calculer_moyenne.allow_tags = True
333 calculer_moyenne.short_description = "Moyenne des notes"
334
335 ### Permissions add, delete, change
336 def has_add_permission(self, request):
337 user_groupes = request.user.groups.all()
338 if grp_drh_recrutement in user_groupes or \
339 grp_directeurs_bureau_recrutement in user_groupes or \
340 grp_administrateurs_recrutement in user_groupes:
341 return True
342 return False
343
344 def has_delete_permission(self, request, obj=None):
345 user_groupes = request.user.groups.all()
346 if 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 grp_drh_recrutement in user_groupes or \
355 grp_directeurs_bureau_recrutement in user_groupes or \
356 grp_administrateurs_recrutement in user_groupes:
357 return True
358 return False
359
360 ### Queryset
361 def queryset(self, request):
362 """
363 Spécifie un queryset limité, autrement Django exécute un
364 select_related() sans paramètre, ce qui a pour effet de charger tous
365 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
366 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
367 génération d'une requête infinie.
368
369 """
370 qs = self.model._default_manager.get_query_set()
371 user_groupes = request.user.groups.all()
372 if grp_drh_recrutement in user_groupes:
373 return qs.select_related('candidats')
374
375 if grp_directeurs_bureau_recrutement in user_groupes or \
376 grp_administrateurs_recrutement in user_groupes:
377 employe = get_emp(request.user)
378 return qs.select_related('candidats').\
379 filter(offre_emploi__region=employe.implantation.region)
380
381 if grp_evaluateurs_recrutement in user_groupes:
382 try:
383 user = Evaluateur.objects.get(user=request.user)
384 except Evaluateur.DoesNotExist:
385 return qs.none()
386
387 offres_emploi = [o for o in user.offres_emploi.all()]
388 candidat_ids = [o.candidat.id for o in offres_emploi]
389 return qs.select_related('candidats').filter(id__in=candidat_ids)
390 return qs.none()
391
392 class ProxyCandidatAdmin(CandidatAdmin):
393 readonly_fields = ('statut', 'offre_emploi', 'prenom', 'nom',
394 'genre', 'nationalite', 'situation_famille',
395 'nombre_dependant', 'telephone', 'email', 'adresse',
396 'ville', 'etat_province', 'code_postal', 'pays',
397 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
398 'domaine_professionnel', 'pieces_jointes',)
399 fieldsets = (
400 ("Offre d'emploi", {
401 'fields': ('offre_emploi', )
402 }),
403 ('Informations personnelles', {
404 'fields': ('prenom','nom','genre', 'nationalite',
405 'situation_famille', 'nombre_dependant',)
406 }),
407 ('Coordonnées', {
408 'fields': ('telephone', 'email', 'adresse', 'ville',
409 'etat_province', 'code_postal', 'pays', )
410 }),
411 ('Informations professionnelles', {
412 'fields': ('niveau_diplome','employeur_actuel',
413 'poste_actuel', 'domaine_professionnel',)
414 }),
415 )
416 inlines = []
417
418 ### Lieu de redirection après le change
419 def response_change(self, request, obj):
420 response = super(ProxyCandidatAdmin, self).response_change(request, obj)
421 user_groupes = request.user.groups.all()
422 if grp_drh_recrutement in user_groupes or \
423 grp_directeurs_bureau_recrutement in user_groupes or \
424 grp_administrateurs_recrutement in user_groupes:
425 return HttpResponseRedirect(reverse\
426 ('admin:recrutement_candidat_changelist'))
427 return HttpResponseRedirect(reverse\
428 ('admin:recrutement_proxycandidat_changelist'))
429
430 ### Permissions add, delete, change
431 def has_add_permission(self, request):
432 return False
433
434 def has_delete_permission(self, request, obj=None):
435 return False
436
437 def has_change_permission(self, request, obj=None):
438 user_groupes = request.user.groups.all()
439 if 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 ("Offres d'emploi à évaluer", {
469 'fields': ('offres_emploi',)
470 }),
471 )
472
473 ### Actions à afficher
474 def get_actions(self, request):
475 actions = super(EvaluateurAdmin, self).get_actions(request)
476 del actions['delete_selected']
477 return actions
478
479 ### Permissions add, delete, change
480 def has_add_permission(self, request):
481 user_groupes = request.user.groups.all()
482 if grp_drh_recrutement in user_groupes or \
483 grp_directeurs_bureau_recrutement in user_groupes or \
484 grp_administrateurs_recrutement in user_groupes:
485 return True
486 return False
487
488 def has_delete_permission(self, request, obj=None):
489 user_groupes = request.user.groups.all()
490 if grp_drh_recrutement in user_groupes or \
491 grp_directeurs_bureau_recrutement in user_groupes or \
492 grp_administrateurs_recrutement in user_groupes:
493 return True
494 return False
495
496 def has_change_permission(self, request, obj=None):
497 user_groupes = request.user.groups.all()
498 if 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(VersionAdmin):
505 list_display = ('_candidat', '_offre_emploi', 'evaluateur', '_note',
506 '_commentaire', )
507 readonly_fields = ('candidat', 'evaluateur')
508 fieldsets = (
509 ('Évaluation du candidat', {
510 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
511 }),
512 )
513
514 ### Actions à afficher
515 def get_actions(self, request):
516 actions = super(CandidatEvaluationAdmin, self).get_actions(request)
517 del actions['delete_selected']
518 return actions
519
520 ### Afficher la note
521 def _note(self, obj):
522 """
523 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
524 un lien pour Évaluer le candidat.
525 Sinon afficher la note.
526 """
527 if obj.note is None:
528 return "<a href='%s'>Candidat non évalué</a>" % \
529 (reverse('admin:recrutement_candidatevaluation_change',
530 args=(obj.id,)))
531 return "<a href='%s'>%s</a>" % \
532 (reverse('admin:recrutement_candidatevaluation_change',
533 args=(candidat_evaluation.id,)), obj.note)
534 _note.allow_tags = True
535 _note.short_description = "Note"
536 _note.admin_order_field = 'note'
537
538 ### Lien en lecture seule vers le candidat
539 def _candidat(self, obj):
540 return "<a href='%s'>%s</a>" \
541 % (reverse('admin:recrutement_proxycandidat_change',
542 args=(obj.candidat.id,)), obj.candidat)
543 _candidat.allow_tags = True
544 _candidat.short_description = 'Candidat'
545
546 ### Afficher commentaire
547 def _commentaire(self, obj):
548 """
549 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
550 dans le champ commentaire, Aucun au lieu de (None)
551 Sinon afficher la note.
552 """
553 if obj.commentaire is None:
554 return "Aucun"
555 return obj.commentaire
556 _commentaire.allow_tags = True
557 _commentaire.short_description = "Commentaire"
558
559 ### Afficher offre d'emploi
560 def _offre_emploi(self, obj):
561 return "<a href='%s'>%s</a>" % \
562 (reverse('admin:recrutement_proxyoffreemploi_change',
563 args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
564 _offre_emploi.allow_tags = True
565 _offre_emploi.short_description = "Voir offre d'emploi"
566
567 ### Permissions add, delete, change
568 def has_add_permission(self, request):
569 user_groupes = request.user.groups.all()
570 if grp_drh_recrutement in user_groupes or \
571 grp_directeurs_bureau_recrutement in user_groupes or \
572 grp_administrateurs_recrutement in user_groupes:
573 return True
574 return False
575
576 def has_change_permission(self, request, obj=None):
577 """
578 Permettre la visualisation dans la changelist
579 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
580 le request.user
581 """
582 return obj is None or request.user == obj.evaluateur.user
583
584 ### Queryset
585 def queryset(self, request):
586 """
587 Afficher uniquement les évaluations de l'évaluateur, sauf si
588 l'utilisateur est dans les groupes suivants.
589 """
590 qs = self.model._default_manager.get_query_set()
591 user_groupes = request.user.groups.all()
592 if grp_drh_recrutement in user_groupes or \
593 grp_directeurs_bureau_recrutement in user_groupes or \
594 grp_administrateurs_recrutement in user_groupes:
595 return qs.select_related('offre_emploi')
596
597 try:
598 evaluateur = Evaluateur.objects.get(user=request.user)
599 except Evaluateur.DoesNotExist:
600 return qs.none()
601
602 candidats_evaluations = CandidatEvaluation.objects.\
603 filter(evaluateur=evaluateur)
604 candidats_evaluations_ids = [ce.id for ce in \
605 candidats_evaluations.all()]
606 return qs.select_related('offre_emploi').\
607 filter(id__in=candidats_evaluations_ids)
608
609 class CourrielTemplateAdmin(VersionAdmin):
610 ### Actions à afficher
611 def get_actions(self, request):
612 actions = super(CourrielTemplateAdmin, self).get_actions(request)
613 del actions['delete_selected']
614 return actions
615
616 admin.site.register(OffreEmploi, OffreEmploiAdmin)
617 admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
618 admin.site.register(Candidat, CandidatAdmin)
619 admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
620 admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
621 admin.site.register(Evaluateur, EvaluateurAdmin)
622 admin.site.register(CourrielTemplate, CourrielTemplateAdmin)