1835: Un CandidatEvaluation doit se créer si l'évaluateur a déjà été assigné à une...
[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 from recrutement.forms import *
17
18 """
19 class MetaAdmin(VersionAdmin):
20 def get_actions(self, request):
21
22 Pour refactoring
23 """
24
25 class ProxyEvaluateur(Evaluateur.offres_emploi.through):
26 """
27 Ce proxy sert uniquement dans l'admin à disposer d'un libellé
28 plus ergonomique.
29 """
30 class Meta:
31 proxy = True
32 verbose_name = "évaluateur"
33
34 class EvaluateurInline(admin.TabularInline):
35 model = ProxyEvaluateur
36 fields = ('evaluateur',)
37 extra = 1
38
39 class OffreEmploiAdmin(VersionAdmin):
40 date_hierarchy = 'date_creation'
41 list_display = ('nom', 'resume', 'date_limite', 'region', 'statut',
42 'est_affiche', '_candidatsList')
43 list_filter = ('statut', 'est_affiche', )
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
69 def queryset(self, request):
70 qs = self.model._default_manager.get_query_set()
71 # Si user est superuser afficher toutes les offres d'emploi
72 user_groupes = request.user.groups.all()
73 if not grp_drh_recrutement in user_groupes and \
74 not request.user.is_superuser:
75 """
76 Si le user n'est ni un évaluateur ni un administrateur régional,
77 retourner none
78 Vérifier groupes
79 """
80 if grp_evaluateurs_recrutement in user_groupes:
81 try:
82 user = Evaluateur.objects.get(user=request.user)
83 except Evaluateur.DoesNotExist:
84 return qs.none()
85 elif grp_administrateurs_recrutement in user_groupes:
86 try:
87 user = AdministrateurRegional.objects.get(user=request.user)
88 except AdministrateurRegional.DoesNotExist:
89 return qs.none()
90 else:
91 return qs.none()
92
93 if type(user) is AdministrateurRegional:
94 region_ids = [g.id for g in user.regions.all()]
95 return qs.select_related('offre_emploi').\
96 filter(region__in=region_ids)
97 if type(user) is Evaluateur:
98 candidats = [g for g in user.candidats.all()]
99 offre_emploi_ids = [c.offre_emploi.id for c in candidats]
100 return qs.select_related('offre_emploi').\
101 filter(id__in=offre_emploi_ids)
102 return qs.none()
103 return qs.select_related('offre_emploi')
104
105 def has_change_permission(self, request, obj=None):
106 user_groupes = request.user.groups.all()
107 if grp_drh_recrutement in user_groupes or \
108 grp_administrateurs_recrutement in user_groupes or \
109 request.user.is_superuser:
110 return True
111 return False
112
113 class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
114 list_display = ('nom', 'resume', 'date_limite', 'region', 'statut',
115 'est_affiche')
116 readonly_fields = ('description', 'bureau',
117 'duree_affectation', 'renumeration',
118 'debut_affectation', 'lieu_affectation', 'nom',
119 'resume', 'date_limite', 'region')
120 fieldsets = (
121 ('Nom', {
122 'fields': ('nom', )
123 }),
124 ('Description générale', {
125 'fields': ('resume','description', 'date_limite', )
126 }),
127 ('Coordonnées', {
128 'fields': ('lieu_affectation', 'bureau', 'region', )
129 }),
130 ('Autre', {
131 'fields': ('debut_affectation', 'duree_affectation',
132 'renumeration', )
133 }),
134 )
135
136 def get_actions(self, request):
137 actions = super(ProxyOffreEmploiAdmin, self).get_actions(request)
138 del actions['affecter_evaluateurs_offre_emploi']
139 return actions
140
141 def response_change(self, request, obj):
142 response = super(ProxyOffreEmploiAdmin, self).response_change(request, obj)
143 user_groupes = request.user.groups.all()
144 if grp_drh_recrutement in user_groupes or \
145 request.user.is_superuser:
146 return HttpResponseRedirect(reverse('admin:recrutement_offreemploi_changelist'))
147 return HttpResponseRedirect(reverse('admin:recrutement_proxyoffreemploi_changelist'))
148
149 def has_add_permission(self, request):
150 return False
151
152 def has_delete_permission(self, request, obj=None):
153 return False
154
155 def has_change_permission(self, request, obj=None):
156 user_groupes = request.user.groups.all()
157 if grp_evaluateurs_recrutement in user_groupes or \
158 grp_drh_recrutement in user_groupes or \
159 request.user.is_superuser:
160 return True
161 return False
162
163 class CandidatPieceInline(admin.TabularInline):
164 model = CandidatPiece
165 fields = ('candidat', 'nom', 'path',)
166 extra = 1
167 max_num = 3
168
169 class CandidatEvaluationInlineFormSet(BaseInlineFormSet):
170 """
171 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
172 """
173 def __init__(self, *args, **kwargs):
174 super(CandidatEvaluationInlineFormSet, self).__init__(*args, **kwargs)
175 self.can_delete = False
176
177 class CandidatEvaluationInline(admin.TabularInline):
178 model = CandidatEvaluation
179 fields = ('evaluateur', 'note', 'commentaire')
180 max_num = 0
181 extra = 0
182 formset = CandidatEvaluationInlineFormSet
183
184 def get_readonly_fields(self, request, obj=None):
185 """
186 Empêche la modification des évaluations
187 """
188 if obj:
189 return self.readonly_fields+('evaluateur', 'note', 'commentaire')
190 return self.readonly_fields
191
192 class CandidatAdmin(VersionAdmin):
193 date_hierarchy = 'date_creation'
194 list_display = ('nom', 'prenom', 'offre_emploi','statut',
195 'voir_offre_emploi', 'calculer_moyenne',
196 'afficher_candidat',)
197 list_filter = ('offre_emploi', )
198 fieldsets = (
199 ("Offre d'emploi", {
200 'fields': ('offre_emploi', )
201 }),
202 ('Informations personnelles', {
203 'fields': ('prenom','nom','genre', 'nationalite',
204 'situation_famille', 'nombre_dependant',)
205 }),
206 ('Coordonnées', {
207 'fields': ('telephone', 'email', 'adresse', 'ville',
208 'etat_province', 'code_postal', 'pays', )
209 }),
210 ('Informations professionnelles', {
211 'fields': ('niveau_diplome','employeur_actuel',
212 'poste_actuel', 'domaine_professionnel',)
213 }),
214 ('Traitement', {
215 'fields': ('statut', )
216 }),
217 )
218 inlines = [
219 CandidatPieceInline,
220 CandidatEvaluationInline,
221 ]
222
223 actions = ['envoyer_courriel_candidats']
224
225 def get_actions(self, request):
226 actions = super(CandidatAdmin, self).get_actions(request)
227 del actions['delete_selected']
228 return actions
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 ("Utilisateur", {
403 'fields': ('user',)
404 }),
405 ("Offres d'emploi à évaluer", {
406 'fields': ('offres_emploi',)
407 }),
408 )
409
410 def get_actions(self, request):
411 actions = super(EvaluateurAdmin, self).get_actions(request)
412 del actions['delete_selected']
413 return actions
414
415 class AdministrateurRegionalAdmin(VersionAdmin):
416 pass
417
418 class CandidatEvaluationAdmin(VersionAdmin):
419 list_display = ('_candidat', '_offre_emploi', 'evaluateur', '_note',
420 '_commentaire', )
421 readonly_fields = ('candidat', 'evaluateur')
422 fieldsets = (
423 ('Évaluation du candidat', {
424 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
425 }),
426 )
427
428 def get_actions(self, request):
429 actions = super(CandidatEvaluationAdmin, self).get_actions(request)
430 del actions['delete_selected']
431 return actions
432
433 def _note(self, obj):
434 """
435 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
436 un lien pour Évaluer le candidat.
437 Sinon afficher la note.
438 """
439 evaluateur = obj.evaluateur
440 candidat = obj.candidat
441 candidat_evaluation = CandidatEvaluation.objects.\
442 get(candidat=candidat, evaluateur=evaluateur)
443 if obj.note is None:
444 return "<a href='%s'>Candidat non évalué</a>" % \
445 (reverse('admin:recrutement_candidatevaluation_change',
446 args=(candidat_evaluation.id,)))
447 return "<a href='%s'>%s</a>" % \
448 (reverse('admin:recrutement_candidatevaluation_change',
449 args=(candidat_evaluation.id,)), obj.note)
450 return
451 _note.allow_tags = True
452 _note.short_description = "Votre note"
453 _note.admin_order_field = 'note'
454
455 def _candidat(self, obj):
456 return "<a href='%s'>%s</a>" \
457 % (reverse('admin:recrutement_proxycandidat_change',
458 args=(obj.candidat.id,)), obj.candidat)
459 _candidat.allow_tags = True
460 _candidat.short_description = 'Candidat'
461
462 def _commentaire(self, obj):
463 """
464 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
465 dans le champ commentaire, Aucun au lieu de (None)
466 Sinon afficher la note.
467 """
468 if obj.commentaire is None:
469 return "Aucun"
470 return obj.commentaire
471 _commentaire.allow_tags = True
472 _commentaire.short_description = "Commentaire"
473
474
475 def _offre_emploi(self, obj):
476 return "<a href='%s'>%s</a>" % \
477 (reverse('admin:recrutement_proxyoffreemploi_change',
478 args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
479 _offre_emploi.allow_tags = True
480 _offre_emploi.short_description = "Voir offre d'emploi"
481
482 def has_change_permission(self, request, obj=None):
483 """
484 Permettre la visualisation dans la changelist
485 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
486 le request.user
487 """
488 return obj is None or request.user == obj.evaluateur.user
489
490 def queryset(self, request):
491 """
492 Afficher uniquement les évaluations de l'évaluateur, sauf si
493 l'utilisateur est super admin.
494 """
495 qs = self.model._default_manager.get_query_set()
496 user_groupes = request.user.groups.all()
497 if grp_drh_recrutement in user_groupes or \
498 request.user.is_superuser:
499 return qs.select_related('offre_emploi')
500
501 try:
502 evaluateur = Evaluateur.objects.get(user=request.user)
503 except Evaluateur.DoesNotExist:
504 return qs.none()
505
506 candidats_evaluations = CandidatEvaluation.objects.\
507 filter(evaluateur=evaluateur)
508 candidats_evaluations_ids = [ce.id for ce in \
509 candidats_evaluations.all()]
510 return qs.select_related('offre_emploi').\
511 filter(id__in=candidats_evaluations_ids)
512
513 class CourrielTemplateAdmin(VersionAdmin):
514 def get_actions(self, request):
515 actions = super(CourrielTemplateAdmin, self).get_actions(request)
516 del actions['delete_selected']
517 return actions
518
519 admin.site.register(OffreEmploi, OffreEmploiAdmin)
520 admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
521 admin.site.register(Candidat, CandidatAdmin)
522 admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
523 admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
524 admin.site.register(Evaluateur, EvaluateurAdmin)
525 #admin.site.register(AdministrateurRegional, AdministrateurRegionalAdmin)
526 admin.site.register(CourrielTemplate, CourrielTemplateAdmin)