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