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