1 # -*- encoding: utf-8 -*-
3 from django
.core
.urlresolvers
import reverse
4 from django
.http
import HttpResponseRedirect
5 from django
.contrib
import admin
6 from django
.forms
.models
import BaseInlineFormSet
7 from django
.db
.models
import Avg
9 from reversion
.admin
import VersionAdmin
10 from datamaster_modeles
.models
import Region
, Bureau
11 from project
.rh
import models
as rh
13 from project
.dae
.utils
import get_employe_from_user
as get_emp
14 from recrutement
.models
import *
15 from recrutement
.workflow
import grp_drh_recrutement
, grp_directeurs_bureau_recrutement
, \
16 grp_administrateurs_recrutement
, \
17 grp_correspondants_rh_recrutement
19 from recrutement
.forms
import *
22 IMPLANTATIONS_CENTRALES
= [15, 19]
24 class OrderedChangeList(admin
.views
.main
.ChangeList
):
26 Surcharge pour appliquer le order_by d'un annotate
28 def get_query_set(self
):
29 qs
= super(OrderedChangeList
, self
).get_query_set()
30 qs
= qs
.order_by('-moyenne')
33 class OffreEmploiAdmin(VersionAdmin
):
34 date_hierarchy
= 'date_creation'
35 list_display
= ('nom', 'date_limite', 'region', 'statut',
36 'est_affiche', '_candidatsList', )
37 exclude
= ('actif', 'poste_nom', 'resume',)
38 list_filter
= ('statut',)
39 actions
= ['affecter_evaluateurs_offre_emploi', ]
40 form
= OffreEmploiForm
42 ### Actions à afficher
43 def get_actions(self
, request
):
44 actions
= super(OffreEmploiAdmin
, self
).get_actions(request
)
45 del actions
['delete_selected']
48 ### Affecter un évaluateurs à des offres d'emploi
49 def affecter_evaluateurs_offre_emploi(modeladmin
, obj
, candidats
):
50 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
52 return HttpResponseRedirect(reverse('affecter_evaluateurs_offre_emploi')+
53 "?ids=%s" % (",".join(selected
)))
54 affecter_evaluateurs_offre_emploi
.short_description
= u
'Affecter évaluateur(s)'
56 ### Afficher la liste des candidats pour l'offre d'emploi
57 def _candidatsList(self
, obj
):
58 return "<a href='%s?offre_emploi__id__exact=%s'>Voir les candidats \
59 </a>" % (reverse('admin:recrutement_candidat_changelist'), obj
.id)
60 _candidatsList
.allow_tags
= True
61 _candidatsList
.short_description
= "Afficher la liste des candidats"
64 def get_form(self
, request
, obj
=None, **kwargs
):
65 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
66 employe
= get_emp(request
.user
)
67 user_groupes
= request
.user
.groups
.all()
71 if form
.declared_fields
.has_key('region'):
72 region_field
= form
.declared_fields
['region']
74 region_field
= form
.base_fields
['region']
76 if grp_drh_recrutement
in user_groupes
:
77 region_field
.queryset
= Region
.objects
.all()
79 region_field
.queryset
= Region
.objects
.\
80 filter(id=employe
.implantation
.region
.id)
83 if form
.declared_fields
.has_key('poste'):
84 poste_field
= form
.declared_fields
['poste']
86 poste_field
= form
.base_fields
['poste']
88 if grp_drh_recrutement
in user_groupes
:
89 poste_field
.queryset
= rh
.Poste
.objects
.all()
91 poste_field
.queryset
= rh
.Poste
.objects
.\
92 filter(implantation__region
=employe
.implantation
.region
).\
93 exclude(implantation__in
=IMPLANTATIONS_CENTRALES
)
96 if form
.declared_fields
.has_key('bureau'):
97 bureau_field
= form
.declared_fields
['bureau']
99 bureau_field
= form
.base_fields
['bureau']
101 if grp_drh_recrutement
in user_groupes
:
102 bureau_field
.queryset
= Bureau
.objects
.all()
104 bureau_field
.queryset
= Bureau
.objects
.\
105 filter(region
=employe
.implantation
.region
)
110 def queryset(self
, request
):
111 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi')
112 user_groupes
= request
.user
.groups
.all()
113 if grp_drh_recrutement
in user_groupes
:
116 if grp_directeurs_bureau_recrutement
in user_groupes
or \
117 grp_correspondants_rh_recrutement
in user_groupes
or \
118 grp_administrateurs_recrutement
in user_groupes
:
119 employe
= get_emp(request
.user
)
120 return qs
.filter(region
=employe
.implantation
.region
)
122 if Evaluateur
.objects
.filter(user
=request
.user
).exists():
123 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
124 offre_ids
= [e
.candidat
.offre_emploi_id
for e
in
125 CandidatEvaluation
.objects
.select_related('candidat').filter(evaluateur
=evaluateur
)]
126 return qs
.filter(id__in
=offre_ids
)
130 ### Permission add, delete, change
131 def has_add_permission(self
, request
):
132 user_groupes
= request
.user
.groups
.all()
133 if request
.user
.is_superuser
is True or \
134 grp_drh_recrutement
in user_groupes
or \
135 grp_directeurs_bureau_recrutement
in user_groupes
or \
136 grp_administrateurs_recrutement
in user_groupes
:
140 def has_delete_permission(self
, request
, obj
=None):
141 user_groupes
= request
.user
.groups
.all()
142 if request
.user
.is_superuser
is True or \
143 grp_drh_recrutement
in user_groupes
or \
144 grp_directeurs_bureau_recrutement
in user_groupes
or \
145 grp_administrateurs_recrutement
in user_groupes
:
149 def has_change_permission(self
, request
, obj
=None):
150 user_groupes
= request
.user
.groups
.all()
151 if request
.user
.is_superuser
is True or \
152 grp_drh_recrutement
in user_groupes
or \
153 grp_directeurs_bureau_recrutement
in user_groupes
or \
154 grp_administrateurs_recrutement
in user_groupes
:
158 class ProxyOffreEmploiAdmin(OffreEmploiAdmin
):
159 list_display
= ('nom', 'date_limite', 'region', 'statut',
161 readonly_fields
= ('description', 'bureau', 'duree_affectation',
162 'renumeration', 'debut_affectation', 'lieu_affectation',
163 'nom', 'resume', 'date_limite', 'region', 'poste')
168 ('Description générale', {
169 'fields': ('description', 'date_limite', )
172 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
175 'fields': ('debut_affectation', 'duree_affectation',
182 ### Lieu de redirection après le change
183 def response_change(self
, request
, obj
):
184 return HttpResponseRedirect(reverse\
185 ('admin:recrutement_proxyoffreemploi_changelist'))
188 def get_form(self
, request
, obj
=None, **kwargs
):
189 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
192 ### Permissions add, delete, change
193 def has_add_permission(self
, request
):
196 def has_delete_permission(self
, request
, obj
=None):
199 def has_change_permission(self
, request
, obj
=None):
202 class CandidatPieceInline(admin
.TabularInline
):
203 model
= CandidatPiece
204 fields
= ('candidat', 'nom', 'path',)
208 class ReadOnlyCandidatPieceInline(CandidatPieceInline
):
209 readonly_fields
= ('candidat', 'nom', 'path', )
213 class CandidatEvaluationInlineFormSet(BaseInlineFormSet
):
215 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
217 def __init__(self
, *args
, **kwargs
):
218 super(CandidatEvaluationInlineFormSet
, self
).__init__(*args
, **kwargs
)
219 self
.can_delete
= False
221 class CandidatEvaluationInline(admin
.TabularInline
):
222 model
= CandidatEvaluation
223 fields
= ('evaluateur', 'note', 'commentaire')
226 formset
= CandidatEvaluationInlineFormSet
229 def get_readonly_fields(self
, request
, obj
=None):
231 Empêche la modification des évaluations
234 return self
.readonly_fields
+('evaluateur', 'note', 'commentaire')
235 return self
.readonly_fields
237 class CandidatAdmin(VersionAdmin
):
238 search_fields
= ('nom', 'prenom' )
239 exclude
= ('actif', )
240 list_editable
= ('statut', )
241 list_display
= ('nom', 'prenom', 'offre_emploi',
242 'voir_offre_emploi', 'calculer_moyenne',
243 'afficher_candidat', '_date_creation', 'statut', )
244 list_filter
= ('offre_emploi', 'offre_emploi__region', 'statut', )
248 'fields': ('offre_emploi', )
250 ('Informations personnelles', {
251 'fields': ('prenom','nom','genre', 'nationalite',
252 'situation_famille', 'nombre_dependant',)
255 'fields': ('telephone', 'email', 'adresse', 'ville',
256 'etat_province', 'code_postal', 'pays', )
258 ('Informations professionnelles', {
259 'fields': ('niveau_diplome','employeur_actuel',
260 'poste_actuel', 'domaine_professionnel',)
263 'fields': ('statut', )
268 CandidatEvaluationInline
,
271 actions
= ['envoyer_courriel_candidats']
273 def _date_creation(self
, obj
):
274 return obj
.date_creation
275 _date_creation
.order_field
= "date_creation"
276 _date_creation
.short_description
= "Date de création"
278 ### Actions à afficher
279 def get_actions(self
, request
):
280 actions
= super(CandidatAdmin
, self
).get_actions(request
)
281 del actions
['delete_selected']
284 ### Envoyer un courriel à des candidats
285 def envoyer_courriel_candidats(modeladmin
, obj
, candidats
):
286 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
288 return HttpResponseRedirect(reverse('selectionner_template')+
289 "?ids=%s" % (",".join(selected
)))
290 envoyer_courriel_candidats
.short_description
= u
'Envoyer courriel'
292 ### Évaluer un candidat
293 def evaluer_candidat(self
, obj
):
294 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
295 (reverse('admin:recrutement_candidatevaluation_changelist'),
297 evaluer_candidat
.allow_tags
= True
298 evaluer_candidat
.short_description
= 'Évaluation'
300 ### Afficher un candidat
301 def afficher_candidat(self
, obj
):
302 return "<a href='%s'>Voir le candidat</a>" % \
303 (reverse('admin:recrutement_proxycandidat_change', args
=(obj
.id,)))
304 afficher_candidat
.allow_tags
= True
305 afficher_candidat
.short_description
= u
'Détails du candidat'
307 ### Voir l'offre d'emploi
308 def voir_offre_emploi(self
, obj
):
309 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
310 (reverse('admin:recrutement_proxyoffreemploi_change',
311 args
=(obj
.offre_emploi
.id,)))
312 voir_offre_emploi
.allow_tags
= True
313 voir_offre_emploi
.short_description
= "Afficher l'offre d'emploi"
315 ### Calculer la moyenne des notes
316 def calculer_moyenne(self
, obj
):
317 evaluations
= CandidatEvaluation
.objects
.filter(candidat
=obj
)
319 notes
= [evaluation
.note
for evaluation
in evaluations \
320 if evaluation
.note
is not None]
323 moyenne_votes
= round(float(sum(notes
)) / len(notes
), 2)
325 moyenne_votes
= "Non disponible"
327 totales
= len(evaluations
)
330 if obj
.statut
== 'REC':
331 if totales
== faites
:
333 elif faites
> 0 and float(totales
) / float(faites
) >= 2:
340 return """<span style="color: %s;">%s (%s/%s)</span>""" % (color
, moyenne_votes
, faites
, totales
)
341 calculer_moyenne
.allow_tags
= True
342 calculer_moyenne
.short_description
= "Notation"
343 calculer_moyenne
.admin_order_field
= ""
345 ### Permissions add, delete, change
346 def has_add_permission(self
, request
):
347 user_groupes
= request
.user
.groups
.all()
348 if request
.user
.is_superuser
is True or \
349 grp_drh_recrutement
in user_groupes
or \
350 grp_directeurs_bureau_recrutement
in user_groupes
or \
351 grp_administrateurs_recrutement
in user_groupes
:
355 def has_delete_permission(self
, request
, obj
=None):
356 user_groupes
= request
.user
.groups
.all()
357 if request
.user
.is_superuser
is True or \
358 grp_drh_recrutement
in user_groupes
or \
359 grp_directeurs_bureau_recrutement
in user_groupes
or \
360 grp_administrateurs_recrutement
in user_groupes
:
364 def has_change_permission(self
, request
, obj
=None):
365 user_groupes
= request
.user
.groups
.all()
366 if request
.user
.is_superuser
is True or \
367 grp_correspondants_rh_recrutement
in user_groupes
or \
368 grp_drh_recrutement
in user_groupes
or \
369 grp_directeurs_bureau_recrutement
in user_groupes
or \
370 grp_administrateurs_recrutement
in user_groupes
:
374 def get_changelist(self
, request
, **kwargs
):
375 return OrderedChangeList
377 def queryset(self
, request
):
379 Spécifie un queryset limité, autrement Django exécute un
380 select_related() sans paramètre, ce qui a pour effet de charger tous
381 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
382 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
383 génération d'une requête infinie.
387 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi').annotate(moyenne
=Avg('evaluations__note'))
389 user_groupes
= request
.user
.groups
.all()
390 if grp_drh_recrutement
in user_groupes
:
393 if grp_directeurs_bureau_recrutement
in user_groupes
or \
394 grp_correspondants_rh_recrutement
in user_groupes
or \
395 grp_administrateurs_recrutement
in user_groupes
:
396 employe
= get_emp(request
.user
)
397 return qs
.filter(offre_emploi__region
=employe
.implantation
.region
)
399 if Evaluateur
.objects
.filter(user
=request
.user
).exists():
400 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
401 candidat_ids
= [e
.candidat
.id for e
in
402 CandidatEvaluation
.objects
.filter(evaluateur
=evaluateur
)]
403 return qs
.filter(id__in
=candidat_ids
)
407 class ProxyCandidatAdmin(CandidatAdmin
):
409 readonly_fields
= ('statut', 'offre_emploi', 'prenom', 'nom',
410 'genre', 'nationalite', 'situation_famille',
411 'nombre_dependant', 'telephone', 'email', 'adresse',
412 'ville', 'etat_province', 'code_postal', 'pays',
413 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
414 'domaine_professionnel', 'pieces_jointes',)
417 'fields': ('offre_emploi', )
419 ('Informations personnelles', {
420 'fields': ('prenom','nom','genre', 'nationalite',
421 'situation_famille', 'nombre_dependant',)
424 'fields': ('telephone', 'email', 'adresse', 'ville',
425 'etat_province', 'code_postal', 'pays', )
427 ('Informations professionnelles', {
428 'fields': ('niveau_diplome','employeur_actuel',
429 'poste_actuel', 'domaine_professionnel',)
432 inlines
= (CandidatEvaluationInline
, )
434 def has_add_permission(self
, request
):
437 def has_delete_permission(self
, request
, obj
=None):
440 def has_change_permission(self
, request
, obj
=None):
443 def get_actions(self
, request
):
446 class CandidatPieceAdmin(admin
.ModelAdmin
):
447 list_display
= ('nom', 'candidat', )
450 def queryset(self
, request
):
452 Spécifie un queryset limité, autrement Django exécute un
453 select_related() sans paramètre, ce qui a pour effet de charger tous
454 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
455 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
456 génération d'une requête infinie.
457 Affiche la liste de candidats que si le user connecté
458 possède un Evaluateur
460 qs
= self
.model
._default_manager
.get_query_set()
461 return qs
.select_related('candidat')
463 class EvaluateurAdmin(VersionAdmin
):
470 ### Actions à afficher
471 def get_actions(self
, request
):
472 actions
= super(EvaluateurAdmin
, self
).get_actions(request
)
473 del actions
['delete_selected']
476 ### Permissions add, delete, change
477 def has_add_permission(self
, request
):
478 user_groupes
= request
.user
.groups
.all()
479 if request
.user
.is_superuser
is True or \
480 grp_drh_recrutement
in user_groupes
or \
481 grp_directeurs_bureau_recrutement
in user_groupes
or \
482 grp_administrateurs_recrutement
in user_groupes
:
486 def has_delete_permission(self
, request
, obj
=None):
487 user_groupes
= request
.user
.groups
.all()
488 if request
.user
.is_superuser
is True or \
489 grp_drh_recrutement
in user_groupes
or \
490 grp_directeurs_bureau_recrutement
in user_groupes
or \
491 grp_administrateurs_recrutement
in user_groupes
:
495 def has_change_permission(self
, request
, obj
=None):
496 user_groupes
= request
.user
.groups
.all()
497 if request
.user
.is_superuser
is True or \
498 grp_drh_recrutement
in user_groupes
or \
499 grp_directeurs_bureau_recrutement
in user_groupes
or \
500 grp_administrateurs_recrutement
in user_groupes
:
504 class CandidatEvaluationAdmin(admin
.ModelAdmin
):
505 search_fields
= ('candidat__nom', 'candidat__prenom' )
506 list_display
= ('_candidat', '_statut', '_offre_emploi', 'evaluateur', '_note',
508 readonly_fields
= ('candidat', 'evaluateur')
509 list_filter
= ('candidat__statut', 'candidat__offre_emploi',)
511 ('Évaluation du candidat', {
512 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
516 def get_actions(self
, request
):
517 # on stocke l'evaluateur connecté (pas forcément la meilleure place...)
519 self
.evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
521 self
.evaluateur
= None
523 actions
= super(CandidatEvaluationAdmin
, self
).get_actions(request
)
524 del actions
['delete_selected']
528 def _note(self
, obj
):
530 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
531 un lien pour Évaluer le candidat.
532 Sinon afficher la note.
534 page
= self
.model
.__name__
.lower()
535 redirect_url
= 'admin:recrutement_%s_change' % page
538 label
= "Candidat non évalué"
542 if self
.evaluateur
== obj
.evaluateur
:
543 return "<a href='%s'>%s</a>" % (reverse(redirect_url
, args
=(obj
.id,)), label
)
546 _note
.allow_tags
= True
547 _note
.short_description
= "Note"
548 _note
.admin_order_field
= 'note'
550 def _statut(self
, obj
):
551 return obj
.candidat
.get_statut_display()
552 _statut
.order_field
= 'candidat__statut'
553 _statut
.short_description
= 'Statut'
556 ### Lien en lecture seule vers le candidat
557 def _candidat(self
, obj
):
558 return "<a href='%s'>%s</a>" \
559 % (reverse('admin:recrutement_proxycandidat_change',
560 args
=(obj
.candidat
.id,)), obj
.candidat
)
561 _candidat
.allow_tags
= True
562 _candidat
.short_description
= 'Candidat'
564 ### Afficher commentaire
565 def _commentaire(self
, obj
):
567 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
568 dans le champ commentaire, Aucun au lieu de (None)
569 Sinon afficher la note.
571 if obj
.commentaire
is None:
573 return obj
.commentaire
574 _commentaire
.allow_tags
= True
575 _commentaire
.short_description
= "Commentaire"
577 ### Afficher offre d'emploi
578 def _offre_emploi(self
, obj
):
579 return "<a href='%s'>%s</a>" % \
580 (reverse('admin:recrutement_proxyoffreemploi_change',
581 args
=(obj
.candidat
.offre_emploi
.id,)), obj
.candidat
.offre_emploi
)
582 _offre_emploi
.allow_tags
= True
583 _offre_emploi
.short_description
= "Voir offre d'emploi"
585 def has_add_permission(self
, request
):
588 def has_delete_permission(self
, request
, obj
=None):
591 def has_change_permission(self
, request
, obj
=None):
593 Permettre la visualisation dans la changelist
594 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
597 user_groupes
= request
.user
.groups
.all()
599 if request
.user
.is_superuser
or \
600 grp_drh_recrutement
in user_groupes
or \
601 grp_correspondants_rh_recrutement
in user_groupes
or \
602 grp_directeurs_bureau_recrutement
in user_groupes
or \
603 grp_administrateurs_recrutement
in user_groupes
:
604 is_recrutement
= True
606 is_recrutement
= False
609 Evaluateur
.objects
.get(user
=request
.user
)
612 is_evaluateur
= False
614 if obj
is None and (is_recrutement
or is_evaluateur
):
617 if request
.user
.is_superuser
is True:
621 return request
.user
== obj
.evaluateur
.user
626 def queryset(self
, request
):
628 Afficher uniquement les évaluations de l'évaluateur, sauf si
629 l'utilisateur est dans les groupes suivants.
631 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi')
632 user_groupes
= request
.user
.groups
.all()
634 if grp_drh_recrutement
in user_groupes
or \
635 grp_correspondants_rh_recrutement
in user_groupes
or \
636 grp_directeurs_bureau_recrutement
in user_groupes
or \
637 grp_administrateurs_recrutement
in user_groupes
:
640 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
641 candidats_evaluations
= \
642 CandidatEvaluation
.objects
.filter(evaluateur
=evaluateur
,
643 candidat__statut__in
=('REC', ))
644 candidats_evaluations_ids
= [ce
.id for ce
in candidats_evaluations
]
645 return qs
.filter(id__in
=candidats_evaluations_ids
)
648 class MesCandidatEvaluationAdmin(CandidatEvaluationAdmin
):
650 def has_change_permission(self
, request
, obj
=None):
652 Evaluateur
.objects
.get(user
=request
.user
)
655 is_evaluateur
= False
657 if obj
is None and is_evaluateur
:
661 return request
.user
== obj
.evaluateur
.user
665 def queryset(self
, request
):
666 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi')
667 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
668 candidats_evaluations
= \
669 CandidatEvaluation
.objects
.filter(evaluateur
=evaluateur
,
670 candidat__statut__in
=('REC', ))
671 candidats_evaluations_ids
= [ce
.id for ce
in candidats_evaluations
]
672 return qs
.filter(id__in
=candidats_evaluations_ids
)
675 class CourrielTemplateAdmin(VersionAdmin
):
676 ### Actions à afficher
677 def get_actions(self
, request
):
678 actions
= super(CourrielTemplateAdmin
, self
).get_actions(request
)
679 del actions
['delete_selected']
682 admin
.site
.register(OffreEmploi
, OffreEmploiAdmin
)
683 admin
.site
.register(ProxyOffreEmploi
, ProxyOffreEmploiAdmin
)
684 admin
.site
.register(Candidat
, CandidatAdmin
)
685 admin
.site
.register(ProxyCandidat
, ProxyCandidatAdmin
)
686 admin
.site
.register(CandidatEvaluation
, CandidatEvaluationAdmin
)
687 admin
.site
.register(MesCandidatEvaluation
, MesCandidatEvaluationAdmin
)
688 admin
.site
.register(Evaluateur
, EvaluateurAdmin
)
689 admin
.site
.register(CourrielTemplate
, CourrielTemplateAdmin
)