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