1966: Modification de Offre d'emploi (visualisation)
[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
74 # Region
75 if form.declared_fields.has_key('region'):
76 region_field = form.declared_fields['region']
77 else:
78 region_field = form.base_fields['region']
79
80 if grp_drh_recrutement in user_groupes:
81 region_field.queryset = Region.objects.all()
82 else:
83 region_field.queryset = Region.objects.\
84 filter(id=employe.implantation.region.id)
85
86 # Poste
87 if form.declared_fields.has_key('poste'):
88 poste_field = form.declared_fields['poste']
89 else:
90 poste_field = form.base_fields['poste']
91
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.\
96 filter(implantation__region=employe.implantation.region).\
97 exclude(implantation__in=IMPLANTATIONS_CENTRALES)
98
99 # Bureau
100 if form.declared_fields.has_key('bureau'):
101 bureau_field = form.declared_fields['bureau']
102 else:
103 bureau_field = form.base_fields['bureau']
104
105 if grp_drh_recrutement in user_groupes:
106 bureau_field.queryset = Bureau.objects.all()
107 else:
108 bureau_field.queryset = Bureau.objects.\
109 filter(region=employe.implantation.region)
110
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', 'poste')
167 fieldsets = (
168 ('Nom', {
169 'fields': ('nom', )
170 }),
171 ('Description générale', {
172 'fields': ('description', 'date_limite', )
173 }),
174 ('Coordonnées', {
175 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
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,obj)
193 return HttpResponseRedirect(reverse\
194 ('admin:recrutement_proxyoffreemploi_changelist'))
195
196 ### Formulaire
197 def get_form(self, request, obj=None, **kwargs):
198 form = super(OffreEmploiAdmin, self).get_form(request, obj, **kwargs)
199 return form
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 return HttpResponseRedirect(reverse\
423 ('admin:recrutement_proxycandidat_changelist'))
424
425 ### Permissions add, delete, change
426 def has_add_permission(self, request):
427 return False
428
429 def has_delete_permission(self, request, obj=None):
430 return False
431
432 def has_change_permission(self, request, obj=None):
433 user_groupes = request.user.groups.all()
434 if grp_drh_recrutement in user_groupes or \
435 grp_evaluateurs_recrutement in user_groupes or \
436 grp_directeurs_bureau_recrutement in user_groupes or \
437 grp_administrateurs_recrutement in user_groupes:
438 return True
439 return False
440
441 class CandidatPieceAdmin(admin.ModelAdmin):
442 list_display = ('nom', 'candidat', )
443
444 ### Queryset
445 def queryset(self, request):
446 """
447 Spécifie un queryset limité, autrement Django exécute un
448 select_related() sans paramètre, ce qui a pour effet de charger tous
449 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
450 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
451 génération d'une requête infinie.
452 Affiche la liste de candidats que si le user connecté
453 possède un Evaluateur
454 """
455 qs = self.model._default_manager.get_query_set()
456 return qs.select_related('candidat')
457
458 class EvaluateurAdmin(VersionAdmin):
459 fieldsets = (
460 ("Utilisateur", {
461 'fields': ('user',)
462 }),
463 ("Offres d'emploi à évaluer", {
464 'fields': ('offres_emploi',)
465 }),
466 )
467
468 ### Actions à afficher
469 def get_actions(self, request):
470 actions = super(EvaluateurAdmin, self).get_actions(request)
471 del actions['delete_selected']
472 return actions
473
474 ### Permissions add, delete, change
475 def has_add_permission(self, request):
476 user_groupes = request.user.groups.all()
477 if grp_drh_recrutement in user_groupes or \
478 grp_directeurs_bureau_recrutement in user_groupes or \
479 grp_administrateurs_recrutement in user_groupes:
480 return True
481 return False
482
483 def has_delete_permission(self, request, obj=None):
484 user_groupes = request.user.groups.all()
485 if grp_drh_recrutement in user_groupes or \
486 grp_directeurs_bureau_recrutement in user_groupes or \
487 grp_administrateurs_recrutement in user_groupes:
488 return True
489 return False
490
491 def has_change_permission(self, request, obj=None):
492 user_groupes = request.user.groups.all()
493 if grp_drh_recrutement in user_groupes or \
494 grp_directeurs_bureau_recrutement in user_groupes or \
495 grp_administrateurs_recrutement in user_groupes:
496 return True
497 return False
498
499 class CandidatEvaluationAdmin(VersionAdmin):
500 list_display = ('_candidat', '_offre_emploi', 'evaluateur', '_note',
501 '_commentaire', )
502 readonly_fields = ('candidat', 'evaluateur')
503 fieldsets = (
504 ('Évaluation du candidat', {
505 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
506 }),
507 )
508
509 ### Actions à afficher
510 def get_actions(self, request):
511 actions = super(CandidatEvaluationAdmin, self).get_actions(request)
512 del actions['delete_selected']
513 return actions
514
515 ### Afficher la note
516 def _note(self, obj):
517 """
518 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
519 un lien pour Évaluer le candidat.
520 Sinon afficher la note.
521 """
522 if obj.note is None:
523 return "<a href='%s'>Candidat non évalué</a>" % \
524 (reverse('admin:recrutement_candidatevaluation_change',
525 args=(obj.id,)))
526 return "<a href='%s'>%s</a>" % \
527 (reverse('admin:recrutement_candidatevaluation_change',
528 args=(candidat_evaluation.id,)), obj.note)
529 _note.allow_tags = True
530 _note.short_description = "Note"
531 _note.admin_order_field = 'note'
532
533 ### Lien en lecture seule vers le candidat
534 def _candidat(self, obj):
535 return "<a href='%s'>%s</a>" \
536 % (reverse('admin:recrutement_proxycandidat_change',
537 args=(obj.candidat.id,)), obj.candidat)
538 _candidat.allow_tags = True
539 _candidat.short_description = 'Candidat'
540
541 ### Afficher commentaire
542 def _commentaire(self, obj):
543 """
544 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
545 dans le champ commentaire, Aucun au lieu de (None)
546 Sinon afficher la note.
547 """
548 if obj.commentaire is None:
549 return "Aucun"
550 return obj.commentaire
551 _commentaire.allow_tags = True
552 _commentaire.short_description = "Commentaire"
553
554 ### Afficher offre d'emploi
555 def _offre_emploi(self, obj):
556 return "<a href='%s'>%s</a>" % \
557 (reverse('admin:recrutement_proxyoffreemploi_change',
558 args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
559 _offre_emploi.allow_tags = True
560 _offre_emploi.short_description = "Voir offre d'emploi"
561
562 ### Permissions add, delete, change
563 def has_add_permission(self, request):
564 user_groupes = request.user.groups.all()
565 if grp_drh_recrutement in user_groupes or \
566 grp_directeurs_bureau_recrutement in user_groupes or \
567 grp_administrateurs_recrutement in user_groupes:
568 return True
569 return False
570
571 def has_change_permission(self, request, obj=None):
572 """
573 Permettre la visualisation dans la changelist
574 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
575 le request.user
576 """
577 return obj is None or request.user == obj.evaluateur.user
578
579 ### Queryset
580 def queryset(self, request):
581 """
582 Afficher uniquement les évaluations de l'évaluateur, sauf si
583 l'utilisateur est dans les groupes suivants.
584 """
585 qs = self.model._default_manager.get_query_set()
586 user_groupes = request.user.groups.all()
587 if grp_drh_recrutement in user_groupes or \
588 grp_directeurs_bureau_recrutement in user_groupes or \
589 grp_administrateurs_recrutement in user_groupes:
590 return qs.select_related('offre_emploi')
591
592 try:
593 evaluateur = Evaluateur.objects.get(user=request.user)
594 except Evaluateur.DoesNotExist:
595 return qs.none()
596
597 candidats_evaluations = CandidatEvaluation.objects.\
598 filter(evaluateur=evaluateur)
599 candidats_evaluations_ids = [ce.id for ce in \
600 candidats_evaluations.all()]
601 return qs.select_related('offre_emploi').\
602 filter(id__in=candidats_evaluations_ids)
603
604 class CourrielTemplateAdmin(VersionAdmin):
605 ### Actions à afficher
606 def get_actions(self, request):
607 actions = super(CourrielTemplateAdmin, self).get_actions(request)
608 del actions['delete_selected']
609 return actions
610
611 admin.site.register(OffreEmploi, OffreEmploiAdmin)
612 admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
613 admin.site.register(Candidat, CandidatAdmin)
614 admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
615 admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
616 admin.site.register(Evaluateur, EvaluateurAdmin)
617 admin.site.register(CourrielTemplate, CourrielTemplateAdmin)