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