ajout cache et optimisation historique
[auf_rh_dae.git] / project / recrutement / admin.py
1 # -*- encoding: utf-8 -*-
2
3 import textwrap
4
5 from auf.django.emploi.models import CandidatPiece, Candidat, OffreEmploi
6 from auf.django.references.models import Region, Bureau, Implantation
7 from django.conf import settings
8 from django.contrib import admin
9 from django.core.urlresolvers import reverse
10 from django.db.models import Avg
11 from django.shortcuts import render_to_response
12 from django.template import RequestContext
13
14 from auf.django.export.admin import ExportAdmin
15 from auf.django.emploi.models import STATUT_CHOICES
16 from django.forms.models import BaseInlineFormSet
17 from django.http import HttpResponseRedirect
18 from django.shortcuts import redirect
19 from reversion.admin import VersionAdmin
20
21 from project import groups
22 from project.permissions import get_user_groupnames
23
24 from project.rh import models as rh
25 from project.recrutement.forms import OffreEmploiForm
26 from project.recrutement.models import \
27 Evaluateur, CandidatEvaluation, \
28 ProxyOffreEmploi, ProxyCandidat, MesCandidatEvaluation, \
29 CourrielTemplate, OffreEmploiEvaluateur
30
31
32 ### CONSTANTES
33 IMPLANTATIONS_CENTRALES = [15, 19]
34
35
36 class BaseAdmin(admin.ModelAdmin):
37
38 class Media:
39 css = {'screen': (
40 'css/admin_custom.css',
41 'jquery-autocomplete/jquery.autocomplete.css',
42 )}
43 js = (
44 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',
45 'jquery-autocomplete/jquery.autocomplete.min.js',
46 )
47
48
49 class OrderedChangeList(admin.views.main.ChangeList):
50 """
51 Surcharge pour appliquer le order_by d'un annotate
52 """
53 def get_query_set(self):
54 qs = super(OrderedChangeList, self).get_query_set()
55 qs = qs.order_by('-moyenne')
56 return qs
57
58
59 class OffreEmploiAdminMixin(BaseAdmin):
60 date_hierarchy = 'date_creation'
61 list_display = (
62 'nom', 'date_limite', 'region', 'statut', 'est_affiche',
63 '_candidatsList'
64 )
65 exclude = ('actif', 'poste_nom', 'resume',)
66 list_filter = ('statut',)
67 actions = ['affecter_evaluateurs_offre_emploi', ]
68 form = OffreEmploiForm
69 fieldsets = (
70 (None, {
71 'fields': (
72 'est_affiche',
73 'statut',
74 'date_limite',
75 'nom',
76 'description',
77 'poste',
78 'region',
79 'lieu_affectation',
80 'bureau',
81 'debut_affectation',
82 'duree_affectation',
83 'renumeration',
84 )
85 }),
86 )
87
88 ### Actions à afficher
89 def get_actions(self, request):
90 actions = super(OffreEmploiAdminMixin, self).get_actions(request)
91 del actions['delete_selected']
92 return actions
93
94 ### Affecter un évaluateurs à des offres d'emploi
95 def affecter_evaluateurs_offre_emploi(modeladmin, obj, candidats):
96 selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
97
98 return HttpResponseRedirect(
99 reverse('affecter_evaluateurs_offre_emploi') +
100 "?ids=%s" % (",".join(selected))
101 )
102
103 affecter_evaluateurs_offre_emploi.short_description = \
104 u'Affecter évaluateur(s)'
105
106 ### Afficher la liste des candidats pour l'offre d'emploi
107 def _candidatsList(self, obj):
108 return "<a href='%s?offre_emploi__id__exact=%s'>Voir les candidats \
109 </a>" % (reverse('admin:recrutement_proxycandidat_changelist'), obj.id)
110 _candidatsList.allow_tags = True
111 _candidatsList.short_description = "Afficher la liste des candidats"
112
113 ### Formulaire
114 def get_form(self, request, obj=None, **kwargs):
115 form = super(OffreEmploiAdminMixin, self).get_form(request, obj, **kwargs)
116 employe = groups.get_employe_from_user(request.user)
117 user_groupes = get_user_groupnames(request.user)
118
119 # Region
120 region_field = None
121 if 'region' in form.declared_fields.keys():
122 region_field = form.declared_fields['region']
123 if 'region' in form.base_fields.keys():
124 region_field = form.base_fields['region']
125 if region_field:
126 if groups.DRH_NIVEAU_1 in user_groupes or \
127 groups.DRH_NIVEAU_2 in user_groupes or \
128 groups.HAUTE_DIRECTION in user_groupes:
129 region_field.queryset = Region.objects.all()
130 else:
131 region_field.queryset = Region.objects.\
132 filter(id=employe.implantation.region.id)
133
134 # Poste
135 poste_field = None
136 if 'poste' in form.declared_fields.keys():
137 poste_field = form.declared_fields['poste']
138 if 'poste' in form.base_fields.keys():
139 poste_field = form.base_fields['poste']
140 if poste_field:
141 if groups.DRH_NIVEAU_1 in user_groupes or \
142 groups.DRH_NIVEAU_2 in user_groupes or \
143 groups.HAUTE_DIRECTION in user_groupes:
144 poste_field.queryset = rh.Poste.objects.all()
145 else:
146 poste_field.queryset = rh.Poste.objects.\
147 filter(implantation__region=employe.implantation.region).\
148 exclude(implantation__in=IMPLANTATIONS_CENTRALES)
149
150 # Bureau
151 bureau_field = None
152 if 'bureau' in form.declared_fields.keys():
153 bureau_field = form.declared_fields['bureau']
154 if 'bureau' in form.base_fields.keys():
155 bureau_field = form.base_fields['bureau']
156 if bureau_field:
157 if groups.DRH_NIVEAU_1 in user_groupes or \
158 groups.DRH_NIVEAU_2 in user_groupes or \
159 groups.HAUTE_DIRECTION in user_groupes:
160 bureau_field.queryset = Bureau.objects.all()
161 else:
162 bureau_field.queryset = \
163 Bureau.objects.filter(region=employe.implantation.region)
164
165 return form
166
167 ### Queryset
168
169 def queryset(self, request):
170 qs = self.model._default_manager.get_query_set() \
171 .select_related('offre_emploi')
172 user_groupes = get_user_groupnames(request.user)
173 if groups.DRH_NIVEAU_1 in user_groupes or \
174 groups.DRH_NIVEAU_2 in user_groupes or \
175 groups.HAUTE_DIRECTION in user_groupes:
176 return qs
177
178 if groups.DIRECTEUR_DE_BUREAU in user_groupes or \
179 groups.CORRESPONDANT_RH in user_groupes or \
180 groups.ADMINISTRATEURS in user_groupes:
181 employe = groups.get_employe_from_user(request.user)
182 return qs.filter(region=employe.implantation.region)
183
184 if Evaluateur.objects.filter(user=request.user).exists():
185 evaluateur = Evaluateur.objects.get(user=request.user)
186 offre_ids = [
187 e.candidat.offre_emploi_id
188 for e in CandidatEvaluation.objects
189 .select_related('candidat')
190 .filter(evaluateur=evaluateur)
191 ]
192 return qs.filter(id__in=offre_ids)
193
194 return qs.none()
195
196 ### Permission add, delete, change
197 def has_add_permission(self, request):
198 user_groupes = get_user_groupnames(request.user)
199 if request.user.is_superuser is True or \
200 groups.DRH_NIVEAU_1 in user_groupes or \
201 groups.DRH_NIVEAU_2 in user_groupes or \
202 groups.DIRECTEUR_DE_BUREAU in user_groupes or \
203 groups.ADMINISTRATEURS in user_groupes or \
204 groups.HAUTE_DIRECTION in user_groupes:
205 return True
206 return False
207
208 def has_delete_permission(self, request, obj=None):
209 user_groupes = get_user_groupnames(request.user)
210 if request.user.is_superuser is True or \
211 groups.DRH_NIVEAU_1 in user_groupes or \
212 groups.DRH_NIVEAU_2 in user_groupes or \
213 groups.HAUTE_DIRECTION in user_groupes:
214 return True
215
216 if obj is not None:
217 employe = groups.get_employe_from_user(request.user)
218 if (groups.DIRECTEUR_DE_BUREAU in user_groupes or \
219 groups.ADMINISTRATEURS in user_groupes) and (
220 employe.implantation.region == obj.lieu_affectation.region):
221 return True
222
223 return False
224
225 def has_change_permission(self, request, obj=None):
226 user_groupes = get_user_groupnames(request.user)
227 if request.user.is_superuser is True or \
228 groups.DRH_NIVEAU_1 in user_groupes or \
229 groups.DRH_NIVEAU_2 in user_groupes or \
230 groups.HAUTE_DIRECTION in user_groupes:
231 return True
232
233 if obj is not None:
234 employe = groups.get_employe_from_user(request.user)
235 if (groups.DIRECTEUR_DE_BUREAU in user_groupes or \
236 groups.ADMINISTRATEURS in user_groupes) and (
237 employe.implantation.region == obj.lieu_affectation.region):
238 return True
239 else:
240 if groups.DIRECTEUR_DE_BUREAU in user_groupes or \
241 groups.ADMINISTRATEURS in user_groupes:
242 return True
243
244
245 return False
246
247 def formfield_for_foreignkey(self, db_field, request, **kwargs):
248 if db_field.name == 'lieu_affectation':
249 user_groupes = [g.name for g in request.user.groups.all()]
250 if not (request.user.is_superuser is True or \
251 groups.DRH_NIVEAU_1 in user_groupes or \
252 groups.DRH_NIVEAU_2 in user_groupes):
253 employe = groups.get_employe_from_user(request.user)
254 kwargs["queryset"] = Implantation.objects.filter(region=employe.implantation.region)
255 return db_field.formfield(**kwargs)
256 return super(OffreEmploiAdminMixin, self).formfield_for_foreignkey(db_field, request, **kwargs)
257
258
259 class OffreEmploiAdmin(VersionAdmin, OffreEmploiAdminMixin):
260 pass
261
262
263 class ProxyOffreEmploiAdmin(OffreEmploiAdminMixin):
264 list_display = (
265 'nom', 'date_limite', 'region', 'statut', 'est_affiche'
266 )
267 readonly_fields = (
268 'description', 'bureau', 'duree_affectation', 'renumeration',
269 'debut_affectation', 'lieu_affectation', 'nom', 'resume',
270 'date_limite', 'region', 'poste'
271 )
272 fieldsets = (
273 ('Nom', {
274 'fields': ('nom',)
275 }),
276 ('Description générale', {
277 'fields': ('description', 'date_limite',)
278 }),
279 ('Coordonnées', {
280 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
281 }),
282 ('Autre', {
283 'fields': (
284 'debut_affectation', 'duree_affectation', 'renumeration',
285 )
286 }),
287 )
288 inlines = []
289
290 ### Lieu de redirection après le change
291 def response_change(self, request, obj):
292 return redirect('admin:recrutement_proxyoffreemploi_changelist')
293
294 ### Permissions add, delete, change
295 def has_add_permission(self, request):
296 return False
297
298 def has_delete_permission(self, request, obj=None):
299 return False
300
301 def has_change_permission(self, request, obj=None):
302 if obj is not None:
303 return True
304
305 return not super(ProxyOffreEmploiAdmin, self).has_change_permission(request, obj)
306
307
308 class CandidatPieceInline(admin.TabularInline):
309 model = CandidatPiece
310 fields = ('candidat', 'nom', 'path',)
311 extra = 1
312 max_num = 3
313
314
315 class ReadOnlyCandidatPieceInline(CandidatPieceInline):
316 readonly_fields = ('candidat', 'nom', 'path', )
317 cand_delete = False
318
319
320 class CandidatEvaluationInlineFormSet(BaseInlineFormSet):
321 """
322 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
323 """
324 def __init__(self, *args, **kwargs):
325 super(CandidatEvaluationInlineFormSet, self).__init__(*args, **kwargs)
326 self.can_delete = False
327
328
329 class CandidatEvaluationInline(admin.TabularInline):
330 model = CandidatEvaluation
331 fields = ('evaluateur', 'note', 'commentaire')
332 max_num = 0
333 extra = 0
334 formset = CandidatEvaluationInlineFormSet
335
336 ### Fields readonly
337 def get_readonly_fields(self, request, obj=None):
338 """
339 Empêche la modification des évaluations
340 """
341 if obj:
342 return self.readonly_fields + ('evaluateur', 'note', 'commentaire')
343 return self.readonly_fields
344
345
346 class CandidatAdminMixin(BaseAdmin, ExportAdmin):
347 search_fields = ('nom', 'prenom')
348 exclude = ('actif', )
349 list_editable = ('statut', )
350 list_display = ('_candidat', 'offre_emploi',
351 'voir_offre_emploi', 'calculer_moyenne',
352 'afficher_candidat', '_date_creation', 'statut', )
353 list_filter = ('offre_emploi__nom', 'offre_emploi__region', 'statut', )
354
355 fieldsets = (
356 ("Offre d'emploi", {
357 'fields': ('offre_emploi', )
358 }),
359 ('Informations personnelles', {
360 'fields': (
361 'nom', 'prenom', 'genre', 'nationalite',
362 'situation_famille', 'nombre_dependant'
363 )
364 }),
365 ('Coordonnées', {
366 'fields': (
367 'telephone', 'email', 'adresse', 'ville', 'etat_province',
368 'code_postal', 'pays'
369 )
370 }),
371 ('Informations professionnelles', {
372 'fields': (
373 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
374 'domaine_professionnel'
375 )
376 }),
377 ('Traitement', {
378 'fields': ('statut', )
379 }),
380 )
381 inlines = [
382 CandidatPieceInline,
383 CandidatEvaluationInline,
384 ]
385 actions = ['envoyer_courriel_candidats', 'changer_statut']
386
387 export_fields = ['statut', 'offre_emploi', 'prenom', 'nom', 'genre',
388 'nationalite', 'situation_famille', 'nombre_dependant',
389 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
390 'domaine_professionnel', 'telephone', 'email', 'adresse',
391 'ville', 'etat_province', 'code_postal', 'pays']
392
393 def _candidat(self, obj):
394 txt = u"%s %s (%s)" % (obj.nom.upper(), obj.prenom, obj.genre)
395 txt = textwrap.wrap(txt, 30)
396 return "<br/>".join(txt)
397 _candidat.short_description = "Candidat"
398 _candidat.admin_order_field = "nom"
399 _candidat.allow_tags = True
400
401 def _date_creation(self, obj):
402 return obj.date_creation
403 _date_creation.admin_order_field = "date_creation"
404 _date_creation.short_description = "Date de réception"
405
406 ### Actions à afficher
407 def get_actions(self, request):
408 actions = super(CandidatAdminMixin, self).get_actions(request)
409 del actions['delete_selected']
410 return actions
411
412 ### Envoyer un courriel à des candidats
413 def envoyer_courriel_candidats(modeladmin, obj, candidats):
414 selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
415
416 return HttpResponseRedirect(
417 reverse('selectionner_template') + "?ids=%s" % (",".join(selected))
418 )
419 envoyer_courriel_candidats.short_description = u'Envoyer courriel'
420
421 ### Changer le statut à des candidats
422 def changer_statut(modeladmin, request, queryset):
423 if request.POST.get('post'):
424 queryset.update(statut=request.POST.get('statut'))
425 return None
426
427 context = {
428 'action_checkbox_name': admin.helpers.ACTION_CHECKBOX_NAME,
429 'queryset': queryset,
430 'status': STATUT_CHOICES,
431 }
432
433 return render_to_response("recrutement/selectionner_statut.html",
434 context, context_instance = RequestContext(request))
435
436 changer_statut.short_description = u'Changer statut'
437
438 ### Évaluer un candidat
439 def evaluer_candidat(self, obj):
440 return "<a href='%s?candidat__id__exact=%s'>" \
441 "Évaluer le candidat</a>" % (
442 reverse('admin:recrutement_candidatevaluation_changelist'),
443 obj.id
444 )
445 evaluer_candidat.allow_tags = True
446 evaluer_candidat.short_description = 'Évaluation'
447
448 ### Afficher un candidat
449 def afficher_candidat(self, obj):
450 items = [u"<li><a href='%s%s'>%s</li>" % \
451 (settings.OE_PRIVE_MEDIA_URL, pj.path, pj.get_nom_display()) \
452 for pj in obj.pieces_jointes()]
453 html = "<a href='%s'>Candidature</a>" % (
454 reverse('admin:recrutement_proxycandidat_change', args=(obj.id,))
455 )
456 return "%s<ul>%s</ul>" % (html, "\n".join(items))
457 afficher_candidat.allow_tags = True
458 afficher_candidat.short_description = u'Détails du candidat'
459
460 ### Voir l'offre d'emploi
461 def voir_offre_emploi(self, obj):
462 return "<a href='%s'>Voir l'offre d'emploi</a>" % (reverse(
463 'admin:recrutement_proxyoffreemploi_change',
464 args=(obj.offre_emploi.id,)
465 ))
466 voir_offre_emploi.allow_tags = True
467 voir_offre_emploi.short_description = "Afficher l'offre d'emploi"
468
469 ### Calculer la moyenne des notes
470 def calculer_moyenne(self, obj):
471 evaluations = CandidatEvaluation.objects.filter(candidat=obj)
472
473 notes = [evaluation.note for evaluation in evaluations \
474 if evaluation.note is not None]
475
476 if len(notes) > 0:
477 moyenne_votes = round(float(sum(notes)) / len(notes), 2)
478 else:
479 moyenne_votes = "Non disponible"
480
481 totales = len(evaluations)
482 faites = len(notes)
483
484 if obj.statut == 'REC':
485 if totales == faites:
486 color = "green"
487 elif faites > 0 and float(totales) / float(faites) >= 2:
488 color = "orange"
489 else:
490 color = "red"
491 else:
492 color = "black"
493
494 return """<span style="color: %s;">%s (%s/%s)</span>""" % (
495 color, moyenne_votes, faites, totales
496 )
497 calculer_moyenne.allow_tags = True
498 calculer_moyenne.short_description = "Moyenne"
499 calculer_moyenne.admin_order_field = ""
500
501 ### Permissions add, delete, change
502 def has_add_permission(self, request):
503 user_groupes = get_user_groupnames(request.user)
504 if request.user.is_superuser is True or \
505 groups.CORRESPONDANT_RH in user_groupes or \
506 groups.DRH_NIVEAU_1 in user_groupes or \
507 groups.DRH_NIVEAU_2 in user_groupes or \
508 groups.DIRECTEUR_DE_BUREAU in user_groupes or \
509 groups.ADMINISTRATEURS in user_groupes or \
510 groups.HAUTE_DIRECTION in user_groupes:
511 return True
512 return False
513
514 def has_delete_permission(self, request, obj=None):
515 user_groupes = get_user_groupnames(request.user)
516 if request.user.is_superuser is True or \
517 groups.CORRESPONDANT_RH in user_groupes or \
518 groups.DRH_NIVEAU_1 in user_groupes or \
519 groups.DRH_NIVEAU_2 in user_groupes or \
520 groups.DIRECTEUR_DE_BUREAU in user_groupes or \
521 groups.ADMINISTRATEURS in user_groupes or \
522 groups.HAUTE_DIRECTION in user_groupes:
523 return True
524 return False
525
526 def has_change_permission(self, request, obj=None):
527 user_groupes = get_user_groupnames(request.user)
528 if request.user.is_superuser is True or \
529 groups.CORRESPONDANT_RH in user_groupes or \
530 groups.DRH_NIVEAU_1 in user_groupes or \
531 groups.DRH_NIVEAU_2 in user_groupes or \
532 groups.DIRECTEUR_DE_BUREAU in user_groupes or \
533 groups.ADMINISTRATEURS in user_groupes or \
534 groups.HAUTE_DIRECTION in user_groupes:
535 return True
536 return False
537
538 def formfield_for_foreignkey(self, db_field, request, **kwargs):
539 if db_field.name == 'offre_emploi':
540 employe = groups.get_employe_from_user(request.user)
541 user_groupes = [g.name for g in request.user.groups.all()]
542 if request.user.is_superuser is True or \
543 groups.CORRESPONDANT_RH in user_groupes or \
544 groups.DRH_NIVEAU_1 in user_groupes or \
545 groups.DRH_NIVEAU_2 in user_groupes:
546 qs_offres = OffreEmploi.objects.all()
547 else:
548 qs_offres =OffreEmploi.objects.filter(region=employe.implantation.region)
549 kwargs["queryset"] = qs_offres
550 return db_field.formfield(**kwargs)
551 return super(CandidatAdminMixin, self).formfield_for_foreignkey(db_field, request, **kwargs)
552
553 def get_changelist(self, request, **kwargs):
554 return OrderedChangeList
555
556 def queryset(self, request):
557 """
558 Spécifie un queryset limité, autrement Django exécute un
559 select_related() sans paramètre, ce qui a pour effet de charger tous
560 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
561 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
562 génération d'une requête infinie.
563 """
564 qs = self.model._default_manager.get_query_set() \
565 .select_related('offre_emploi') \
566 .annotate(moyenne=Avg('evaluations__note'))
567
568 user_groupes = get_user_groupnames(request.user)
569 if groups.DRH_NIVEAU_1 in user_groupes or \
570 groups.DRH_NIVEAU_2 in user_groupes or \
571 groups.HAUTE_DIRECTION in user_groupes:
572 return qs
573
574 if groups.DIRECTEUR_DE_BUREAU in user_groupes or \
575 groups.CORRESPONDANT_RH in user_groupes or \
576 groups.ADMINISTRATEURS in user_groupes:
577 employe = groups.get_employe_from_user(request.user)
578 return qs.filter(offre_emploi__region=employe.implantation.region)
579
580 if Evaluateur.objects.filter(user=request.user).exists():
581 evaluateur = Evaluateur.objects.get(user=request.user)
582 candidat_ids = [e.candidat.id for e in
583 CandidatEvaluation.objects.filter(evaluateur=evaluateur)]
584 return qs.filter(id__in=candidat_ids)
585 return qs.none()
586
587
588 class CandidatAdmin(VersionAdmin, CandidatAdminMixin):
589 change_list_template = 'admin/recrutement/candidat/change_list.html'
590 pass
591
592
593 class ProxyCandidatAdmin(CandidatAdminMixin):
594 change_list_template = 'admin/recrutement/candidat/change_list.html'
595 list_editable = ()
596 readonly_fields = (
597 'statut', 'offre_emploi', 'prenom', 'nom', 'genre', 'nationalite',
598 'situation_famille', 'nombre_dependant', 'telephone', 'email',
599 'adresse', 'ville', 'etat_province', 'code_postal', 'pays',
600 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
601 'domaine_professionnel', 'pieces_jointes'
602 )
603 fieldsets = (
604 ("Offre d'emploi", {
605 'fields': ('offre_emploi', )
606 }),
607 ('Informations personnelles', {
608 'fields': (
609 'prenom', 'nom', 'genre', 'nationalite', 'situation_famille',
610 'nombre_dependant'
611 )
612 }),
613 ('Coordonnées', {
614 'fields': (
615 'telephone', 'email', 'adresse', 'ville', 'etat_province',
616 'code_postal', 'pays'
617 )
618 }),
619 ('Informations professionnelles', {
620 'fields': (
621 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
622 'domaine_professionnel'
623 )
624 }),
625 )
626 inlines = (CandidatEvaluationInline, )
627
628 def has_add_permission(self, request):
629 return False
630
631 def has_delete_permission(self, request, obj=None):
632 return False
633
634 def has_change_permission(self, request, obj=None):
635 if obj is not None:
636 return obj in self.queryset(request)
637 #try:
638 # evaluateur = Evaluateur.objects.get(user=request.user)
639 # for e in obj.evaluations.all():
640 # if e.evaluateur == evaluateur:
641 # return True
642 # return False
643 #except:
644 # pass
645 return super(ProxyCandidatAdmin, self).has_change_permission(request, obj)
646
647 def get_actions(self, request):
648 return None
649
650
651 class CandidatPieceAdmin(admin.ModelAdmin):
652 list_display = ('nom', 'candidat', )
653
654 ### Queryset
655 def queryset(self, request):
656 """
657 Spécifie un queryset limité, autrement Django exécute un
658 select_related() sans paramètre, ce qui a pour effet de charger tous
659 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
660 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
661 génération d'une requête infinie. Affiche la liste de candidats que
662 si le user connecté possède un Evaluateur
663 """
664 qs = self.model._default_manager.get_query_set()
665 return qs.select_related('candidat')
666
667
668 class EvaluateurAdmin(BaseAdmin, VersionAdmin):
669 fieldsets = (
670 ("Utilisateur", {
671 'fields': ('user',)
672 }),
673 )
674
675 ### Actions à afficher
676 def get_actions(self, request):
677 actions = super(EvaluateurAdmin, self).get_actions(request)
678 del actions['delete_selected']
679 return actions
680
681 ### Permissions add, delete, change
682 def has_add_permission(self, request):
683 user_groupes = get_user_groupnames(request.user)
684 if request.user.is_superuser is True or \
685 groups.DRH_NIVEAU_1 in user_groupes or \
686 groups.DRH_NIVEAU_2 in user_groupes or \
687 groups.HAUTE_DIRECTION in user_groupes:
688 return True
689 return False
690
691 def has_delete_permission(self, request, obj=None):
692 user_groupes = get_user_groupnames(request.user)
693 if request.user.is_superuser is True or \
694 groups.DRH_NIVEAU_1 in user_groupes or \
695 groups.DRH_NIVEAU_2 in user_groupes or \
696 groups.HAUTE_DIRECTION in user_groupes:
697 return True
698 return False
699
700 def has_change_permission(self, request, obj=None):
701 user_groupes = get_user_groupnames(request.user)
702 if request.user.is_superuser is True or \
703 groups.DRH_NIVEAU_1 in user_groupes or \
704 groups.DRH_NIVEAU_2 in user_groupes or \
705 groups.HAUTE_DIRECTION in user_groupes:
706 return True
707 return False
708
709
710 class CandidatEvaluationAdmin(BaseAdmin):
711 search_fields = ('candidat__nom', 'candidat__prenom')
712 list_display = (
713 '_candidat', '_statut', '_offre_emploi', 'evaluateur', '_note',
714 '_commentaire'
715 )
716 readonly_fields = ('candidat', 'evaluateur')
717 list_filter = ('candidat__statut', 'candidat__offre_emploi',)
718 fieldsets = (
719 ('Évaluation du candidat', {
720 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
721 }),
722 )
723
724 def get_actions(self, request):
725 # on stocke l'evaluateur connecté (pas forcément la meilleure place...)
726 try:
727 self.evaluateur = Evaluateur.objects.get(user=request.user)
728 except:
729 self.evaluateur = None
730
731 actions = super(CandidatEvaluationAdmin, self).get_actions(request)
732 del actions['delete_selected']
733 return actions
734
735 ### Afficher la note
736 def _note(self, obj):
737 """
738 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
739 un lien pour Évaluer le candidat.
740 Sinon afficher la note.
741 """
742 page = self.model.__name__.lower()
743 redirect_url = 'admin:recrutement_%s_change' % page
744
745 if obj.note is None:
746 label = "Candidat non évalué"
747 else:
748 label = obj.note
749
750 if self.evaluateur == obj.evaluateur:
751 return "<a href='%s'>%s</a>" % (
752 reverse(redirect_url, args=(obj.id,)), label
753 )
754 else:
755 return label
756 _note.allow_tags = True
757 _note.short_description = "Note"
758 _note.admin_order_field = 'note'
759
760 def _statut(self, obj):
761 return obj.candidat.get_statut_display()
762 _statut.order_field = 'candidat__statut'
763 _statut.short_description = 'Statut'
764
765 ### Lien en lecture seule vers le candidat
766 def _candidat(self, obj):
767 return "<a href='%s'>%s</a>" \
768 % (reverse('admin:recrutement_proxycandidat_change',
769 args=(obj.candidat.id,)), obj.candidat)
770 _candidat.allow_tags = True
771 _candidat.short_description = 'Candidat'
772
773 ### Afficher commentaire
774 def _commentaire(self, obj):
775 """
776 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
777 dans le champ commentaire, Aucun au lieu de (None)
778 Sinon afficher la note.
779 """
780 if obj.commentaire is None:
781 return "Aucun"
782 return obj.commentaire
783 _commentaire.allow_tags = True
784 _commentaire.short_description = "Commentaire"
785
786 ### Afficher offre d'emploi
787 def _offre_emploi(self, obj):
788 return "<a href='%s'>%s</a>" % \
789 (reverse('admin:recrutement_proxyoffreemploi_change',
790 args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
791 _offre_emploi.allow_tags = True
792 _offre_emploi.short_description = "Voir offre d'emploi"
793
794 def has_add_permission(self, request):
795 return False
796
797 def has_delete_permission(self, request, obj=None):
798 return False
799
800 def has_change_permission(self, request, obj=None):
801 """
802 Permettre la visualisation dans la changelist
803 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
804 le request.user
805 """
806 user_groupes = get_user_groupnames(request.user)
807
808 if request.user.is_superuser or \
809 groups.CORRESPONDANT_RH in user_groupes or \
810 groups.DRH_NIVEAU_1 in user_groupes or \
811 groups.DRH_NIVEAU_2 in user_groupes or \
812 groups.DIRECTEUR_DE_BUREAU in user_groupes or \
813 groups.ADMINISTRATEURS in user_groupes or \
814 groups.HAUTE_DIRECTION in user_groupes:
815 is_recrutement = True
816 else:
817 is_recrutement = False
818
819 return is_recrutement
820
821 def queryset(self, request):
822 """
823 Afficher uniquement les évaluations de l'évaluateur, sauf si
824 l'utilisateur est dans les groupes suivants.
825 """
826 qs = self.model._default_manager.get_query_set() \
827 .select_related('offre_emploi')
828 user_groupes = get_user_groupnames(request.user)
829
830 if request.user.is_superuser or \
831 groups.CORRESPONDANT_RH in user_groupes or \
832 groups.DRH_NIVEAU_1 in user_groupes or \
833 groups.DRH_NIVEAU_2 in user_groupes or \
834 groups.DIRECTEUR_DE_BUREAU in user_groupes or \
835 groups.ADMINISTRATEURS in user_groupes or \
836 groups.HAUTE_DIRECTION in user_groupes:
837 return qs.filter(candidat__statut__in=('REC', 'SEL'))
838
839 evaluateur = Evaluateur.objects.get(user=request.user)
840 candidats_evaluations = \
841 CandidatEvaluation.objects.filter(evaluateur=evaluateur,
842 candidat__statut__in=('REC', ))
843 candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
844 return qs.filter(id__in=candidats_evaluations_ids)
845
846
847 class MesCandidatEvaluationAdmin(CandidatEvaluationAdmin):
848 list_filter = []
849
850 def has_change_permission(self, request, obj=None):
851 try:
852 Evaluateur.objects.get(user=request.user)
853 is_evaluateur = True
854 except:
855 is_evaluateur = False
856
857 if obj is None and is_evaluateur:
858 return True
859
860 try:
861 return request.user == obj.evaluateur.user
862 except:
863 return False
864
865 def queryset(self, request):
866 qs = self.model._default_manager.get_query_set() \
867 .select_related('offre_emploi')
868 evaluateur = Evaluateur.objects.get(user=request.user)
869 candidats_evaluations = \
870 CandidatEvaluation.objects.filter(evaluateur=evaluateur,
871 candidat__statut__in=('REC', ))
872 candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
873 return qs.filter(id__in=candidats_evaluations_ids)
874
875
876 class OffreEmploiEvaluateurAdmin(BaseAdmin):
877 pass
878
879
880 class CourrielTemplateAdmin(BaseAdmin, VersionAdmin):
881 ### Actions à afficher
882 def get_actions(self, request):
883 actions = super(CourrielTemplateAdmin, self).get_actions(request)
884 del actions['delete_selected']
885 return actions
886
887 admin.site.register(OffreEmploi, OffreEmploiAdmin)
888 admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
889 admin.site.register(Candidat, CandidatAdmin)
890 admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
891 admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
892 admin.site.register(MesCandidatEvaluation, MesCandidatEvaluationAdmin)
893 admin.site.register(Evaluateur, EvaluateurAdmin)
894 admin.site.register(CourrielTemplate, CourrielTemplateAdmin)
895 admin.site.register(OffreEmploiEvaluateur, OffreEmploiEvaluateurAdmin)