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