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