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
8 from reversion
.admin
import VersionAdmin
9 from datamaster_modeles
.models
import Region
, Bureau
10 from project
.rh
import models
as rh
12 from project
.dae
.utils
import get_employe_from_user
as get_emp
13 from recrutement
.models
import *
14 from recrutement
.workflow
import grp_evaluateurs_recrutement
, \
15 grp_drh_recrutement
, grp_directeurs_bureau_recrutement
, \
16 grp_administrateurs_recrutement
17 from recrutement
.forms
import *
20 IMPLANTATIONS_CENTRALES
= [15, 19]
22 class ProxyEvaluateur(Evaluateur
.offres_emploi
.through
):
24 Ce proxy sert uniquement dans l'admin à disposer d'un libellé
29 verbose_name
= "évaluateur"
31 class EvaluateurInline(admin
.TabularInline
):
32 model
= ProxyEvaluateur
33 fields
= ('evaluateur',)
36 class OffreEmploiAdmin(VersionAdmin
):
37 date_hierarchy
= 'date_creation'
38 list_display
= ('nom', 'date_limite', 'region', 'statut',
39 'est_affiche', '_candidatsList', )
40 exclude
= ('actif', 'poste_nom', 'resume',)
41 list_filter
= ('statut',)
42 actions
= ['affecter_evaluateurs_offre_emploi', ]
43 form
= OffreEmploiForm
44 inlines
= [EvaluateurInline
, ]
46 ### Actions à afficher
47 def get_actions(self
, request
):
48 actions
= super(OffreEmploiAdmin
, self
).get_actions(request
)
49 del actions
['delete_selected']
52 ### Affecter un évaluateurs à des offres d'emploi
53 def affecter_evaluateurs_offre_emploi(modeladmin
, obj
, candidats
):
54 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
56 return HttpResponseRedirect(reverse('affecter_evaluateurs_offre_emploi')+
57 "?ids=%s" % (",".join(selected
)))
58 affecter_evaluateurs_offre_emploi
.short_description
= u
'Affecter évaluateur(s)'
60 ### Afficher la liste des candidats pour l'offre d'emploi
61 def _candidatsList(self
, obj
):
62 return "<a href='%s?offre_emploi__id__exact=%s'>Voir les candidats \
63 </a>" % (reverse('admin:recrutement_candidat_changelist'), obj
.id)
64 _candidatsList
.allow_tags
= True
65 _candidatsList
.short_description
= "Afficher la liste des candidats"
68 def get_form(self
, request
, obj
=None, **kwargs
):
69 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
70 employe
= get_emp(request
.user
)
71 user_groupes
= request
.user
.groups
.all()
75 if form
.declared_fields
.has_key('region'):
76 region_field
= form
.declared_fields
['region']
78 region_field
= form
.base_fields
['region']
80 if grp_drh_recrutement
in user_groupes
:
81 region_field
.queryset
= Region
.objects
.all()
83 region_field
.queryset
= Region
.objects
.\
84 filter(id=employe
.implantation
.region
.id)
87 if form
.declared_fields
.has_key('poste'):
88 poste_field
= form
.declared_fields
['poste']
90 poste_field
= form
.base_fields
['poste']
92 if grp_drh_recrutement
in user_groupes
:
93 poste_field
.queryset
= rh
.Poste
.objects
.all()
95 poste_field
.queryset
= rh
.Poste
.objects
.\
96 filter(implantation__region
=employe
.implantation
.region
).\
97 exclude(implantation__in
=IMPLANTATIONS_CENTRALES
)
100 if form
.declared_fields
.has_key('bureau'):
101 bureau_field
= form
.declared_fields
['bureau']
103 bureau_field
= form
.base_fields
['bureau']
105 if grp_drh_recrutement
in user_groupes
:
106 bureau_field
.queryset
= Bureau
.objects
.all()
108 bureau_field
.queryset
= Bureau
.objects
.\
109 filter(region
=employe
.implantation
.region
)
114 def queryset(self
, request
):
115 qs
= self
.model
._default_manager
.get_query_set()
116 user_groupes
= request
.user
.groups
.all()
117 if grp_drh_recrutement
in user_groupes
:
118 return qs
.select_related('offre_emploi')
120 if grp_directeurs_bureau_recrutement
in user_groupes
or \
121 grp_administrateurs_recrutement
in user_groupes
:
122 employe
= get_emp(request
.user
)
123 return qs
.select_related('offre_emploi').\
124 filter(region
=employe
.implantation
.region
)
126 if grp_evaluateurs_recrutement
in user_groupes
:
128 user
= Evaluateur
.objects
.get(user
=request
.user
)
129 except Evaluateur
.DoesNotExist
:
132 ids
= [o
.id for o
in user
.offres_emploi
.all()]
133 return qs
.select_related('offre_emploi').filter(id__in
=ids
)
136 ### Permission add, delete, change
137 def has_add_permission(self
, request
):
138 user_groupes
= request
.user
.groups
.all()
139 if request
.user
.is_superuser
is True or \
140 grp_drh_recrutement
in user_groupes
or \
141 grp_directeurs_bureau_recrutement
in user_groupes
or \
142 grp_administrateurs_recrutement
in user_groupes
:
146 def has_delete_permission(self
, request
, obj
=None):
147 user_groupes
= request
.user
.groups
.all()
148 if request
.user
.is_superuser
is True or \
149 grp_drh_recrutement
in user_groupes
or \
150 grp_directeurs_bureau_recrutement
in user_groupes
or \
151 grp_administrateurs_recrutement
in user_groupes
:
155 def has_change_permission(self
, request
, obj
=None):
156 user_groupes
= request
.user
.groups
.all()
157 if request
.user
.is_superuser
is True or \
158 grp_drh_recrutement
in user_groupes
or \
159 grp_directeurs_bureau_recrutement
in user_groupes
or \
160 grp_administrateurs_recrutement
in user_groupes
:
164 class ProxyOffreEmploiAdmin(OffreEmploiAdmin
):
165 list_display
= ('nom', 'date_limite', 'region', 'statut',
167 readonly_fields
= ('description', 'bureau', 'duree_affectation',
168 'renumeration', 'debut_affectation', 'lieu_affectation',
169 'nom', 'resume', 'date_limite', 'region', 'poste')
174 ('Description générale', {
175 'fields': ('description', 'date_limite', )
178 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
181 'fields': ('debut_affectation', 'duree_affectation',
187 ### Actions à afficher
188 def get_actions(self
, request
):
189 actions
= super(ProxyOffreEmploiAdmin
, self
).get_actions(request
)
190 del actions
['affecter_evaluateurs_offre_emploi']
193 ### Lieu de redirection après le change
194 def response_change(self
, request
, obj
):
195 response
= super(ProxyOffreEmploiAdmin
,self
).response_change(request
,obj
)
196 return HttpResponseRedirect(reverse\
197 ('admin:recrutement_proxyoffreemploi_changelist'))
200 def get_form(self
, request
, obj
=None, **kwargs
):
201 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
204 ### Permissions add, delete, change
205 def has_add_permission(self
, request
):
208 def has_delete_permission(self
, request
, obj
=None):
211 def has_change_permission(self
, request
, obj
=None):
212 user_groupes
= request
.user
.groups
.all()
213 if request
.user
.is_superuser
is True or \
214 grp_evaluateurs_recrutement
in user_groupes
or \
215 grp_drh_recrutement
in user_groupes
or \
216 grp_directeurs_bureau_recrutement
in user_groupes
or \
217 grp_administrateurs_recrutement
in user_groupes
:
221 class CandidatPieceInline(admin
.TabularInline
):
222 model
= CandidatPiece
223 fields
= ('candidat', 'nom', 'path',)
227 class CandidatEvaluationInlineFormSet(BaseInlineFormSet
):
229 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
231 def __init__(self
, *args
, **kwargs
):
232 super(CandidatEvaluationInlineFormSet
, self
).__init__(*args
, **kwargs
)
233 self
.can_delete
= False
235 class CandidatEvaluationInline(admin
.TabularInline
):
236 model
= CandidatEvaluation
237 fields
= ('evaluateur', 'note', 'commentaire')
240 formset
= CandidatEvaluationInlineFormSet
243 def get_readonly_fields(self
, request
, obj
=None):
245 Empêche la modification des évaluations
248 return self
.readonly_fields
+('evaluateur', 'note', 'commentaire')
249 return self
.readonly_fields
251 class CandidatAdmin(VersionAdmin
):
252 exclude
= ('actif', )
253 date_hierarchy
= 'date_creation'
254 list_display
= ('nom', 'prenom', 'offre_emploi','statut',
255 'voir_offre_emploi', 'calculer_moyenne',
256 'afficher_candidat',)
257 list_filter
= ('offre_emploi', )
261 'fields': ('offre_emploi', )
263 ('Informations personnelles', {
264 'fields': ('prenom','nom','genre', 'nationalite',
265 'situation_famille', 'nombre_dependant',)
268 'fields': ('telephone', 'email', 'adresse', 'ville',
269 'etat_province', 'code_postal', 'pays', )
271 ('Informations professionnelles', {
272 'fields': ('niveau_diplome','employeur_actuel',
273 'poste_actuel', 'domaine_professionnel',)
276 'fields': ('statut', )
281 CandidatEvaluationInline
,
284 actions
= ['envoyer_courriel_candidats']
286 ### Actions à afficher
287 def get_actions(self
, request
):
288 actions
= super(CandidatAdmin
, self
).get_actions(request
)
289 del actions
['delete_selected']
292 ### Envoyer un courriel à des candidats
293 def envoyer_courriel_candidats(modeladmin
, obj
, candidats
):
294 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
296 return HttpResponseRedirect(reverse('selectionner_template')+
297 "?ids=%s" % (",".join(selected
)))
298 envoyer_courriel_candidats
.short_description
= u
'Envoyer courriel'
300 ### Évaluer un candidat
301 def evaluer_candidat(self
, obj
):
302 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
303 (reverse('admin:recrutement_candidatevaluation_changelist'),
305 evaluer_candidat
.allow_tags
= True
306 evaluer_candidat
.short_description
= 'Évaluation'
308 ### Afficher un candidat
309 def afficher_candidat(self
, obj
):
310 return "<a href='%s'>Voir le candidat</a>" % \
311 (reverse('admin:recrutement_proxycandidat_change', args
=(obj
.id,)))
312 afficher_candidat
.allow_tags
= True
313 afficher_candidat
.short_description
= u
'Détails du candidat'
315 ### Voir l'offre d'emploi
316 def voir_offre_emploi(self
, obj
):
317 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
318 (reverse('admin:recrutement_proxyoffreemploi_change',
319 args
=(obj
.offre_emploi
.id,)))
320 voir_offre_emploi
.allow_tags
= True
321 voir_offre_emploi
.short_description
= "Afficher l'offre d'emploi"
323 ### Calculer la moyenne des notes
324 def calculer_moyenne(self
, obj
):
325 evaluations
= CandidatEvaluation
.objects
.filter(candidat
=obj
)
326 offre_emploi
= obj
.offre_emploi
328 notes
= [evaluation
.note
for evaluation
in evaluations
.all() \
329 if evaluation
.note
is not None]
332 moyenne_votes
= float(sum(notes
)) / len(notes
)
334 moyenne_votes
= "Non disponible"
336 calculer_moyenne
.allow_tags
= True
337 calculer_moyenne
.short_description
= "Moyenne des notes"
339 ### Permissions add, delete, change
340 def has_add_permission(self
, request
):
341 user_groupes
= request
.user
.groups
.all()
342 if request
.user
.is_superuser
is True or \
343 grp_drh_recrutement
in user_groupes
or \
344 grp_directeurs_bureau_recrutement
in user_groupes
or \
345 grp_administrateurs_recrutement
in user_groupes
:
349 def has_delete_permission(self
, request
, obj
=None):
350 user_groupes
= request
.user
.groups
.all()
351 if request
.user
.is_superuser
is True or \
352 grp_drh_recrutement
in user_groupes
or \
353 grp_directeurs_bureau_recrutement
in user_groupes
or \
354 grp_administrateurs_recrutement
in user_groupes
:
358 def has_change_permission(self
, request
, obj
=None):
359 user_groupes
= request
.user
.groups
.all()
360 if request
.user
.is_superuser
is True or \
361 grp_drh_recrutement
in user_groupes
or \
362 grp_directeurs_bureau_recrutement
in user_groupes
or \
363 grp_administrateurs_recrutement
in user_groupes
:
368 def queryset(self
, request
):
370 Spécifie un queryset limité, autrement Django exécute un
371 select_related() sans paramètre, ce qui a pour effet de charger tous
372 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
373 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
374 génération d'une requête infinie.
377 qs
= self
.model
._default_manager
.get_query_set()
378 user_groupes
= request
.user
.groups
.all()
379 if grp_drh_recrutement
in user_groupes
:
380 return qs
.select_related('candidats')
382 if grp_directeurs_bureau_recrutement
in user_groupes
or \
383 grp_administrateurs_recrutement
in user_groupes
:
384 employe
= get_emp(request
.user
)
385 return qs
.select_related('candidats').\
386 filter(offre_emploi__region
=employe
.implantation
.region
)
388 if grp_evaluateurs_recrutement
in user_groupes
:
390 user
= Evaluateur
.objects
.get(user
=request
.user
)
391 except Evaluateur
.DoesNotExist
:
394 offres_emploi
= [o
for o
in user
.offres_emploi
.all()]
395 candidat_ids
= [o
.candidat
.id for o
in offres_emploi
]
396 return qs
.select_related('candidats').filter(id__in
=candidat_ids
)
400 class ProxyCandidatAdmin(CandidatAdmin
):
401 readonly_fields
= ('statut', 'offre_emploi', 'prenom', 'nom',
402 'genre', 'nationalite', 'situation_famille',
403 'nombre_dependant', 'telephone', 'email', 'adresse',
404 'ville', 'etat_province', 'code_postal', 'pays',
405 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
406 'domaine_professionnel', 'pieces_jointes',)
409 'fields': ('offre_emploi', )
411 ('Informations personnelles', {
412 'fields': ('prenom','nom','genre', 'nationalite',
413 'situation_famille', 'nombre_dependant',)
416 'fields': ('telephone', 'email', 'adresse', 'ville',
417 'etat_province', 'code_postal', 'pays', )
419 ('Informations professionnelles', {
420 'fields': ('niveau_diplome','employeur_actuel',
421 'poste_actuel', 'domaine_professionnel',)
426 ### Lieu de redirection après le change
427 def response_change(self
, request
, obj
):
428 response
= super(ProxyCandidatAdmin
, self
).response_change(request
, obj
)
429 user_groupes
= request
.user
.groups
.all()
430 return HttpResponseRedirect(reverse\
431 ('admin:recrutement_proxycandidat_changelist'))
433 ### Permissions add, delete, change
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):
441 user_groupes
= request
.user
.groups
.all()
442 if request
.user
.is_superuser
is True or \
443 grp_drh_recrutement
in user_groupes
or \
444 grp_evaluateurs_recrutement
in user_groupes
or \
445 grp_directeurs_bureau_recrutement
in user_groupes
or \
446 grp_administrateurs_recrutement
in user_groupes
:
450 class CandidatPieceAdmin(admin
.ModelAdmin
):
451 list_display
= ('nom', 'candidat', )
454 def queryset(self
, request
):
456 Spécifie un queryset limité, autrement Django exécute un
457 select_related() sans paramètre, ce qui a pour effet de charger tous
458 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
459 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
460 génération d'une requête infinie.
461 Affiche la liste de candidats que si le user connecté
462 possède un Evaluateur
464 qs
= self
.model
._default_manager
.get_query_set()
465 return qs
.select_related('candidat')
467 class EvaluateurAdmin(VersionAdmin
):
472 ("Offres d'emploi à évaluer", {
473 'fields': ('offres_emploi',)
477 ### Actions à afficher
478 def get_actions(self
, request
):
479 actions
= super(EvaluateurAdmin
, self
).get_actions(request
)
480 del actions
['delete_selected']
483 ### Permissions add, delete, change
484 def has_add_permission(self
, request
):
485 user_groupes
= request
.user
.groups
.all()
486 if request
.user
.is_superuser
is True or \
487 grp_drh_recrutement
in user_groupes
or \
488 grp_directeurs_bureau_recrutement
in user_groupes
or \
489 grp_administrateurs_recrutement
in user_groupes
:
493 def has_delete_permission(self
, request
, obj
=None):
494 user_groupes
= request
.user
.groups
.all()
495 if request
.user
.is_superuser
is True or \
496 grp_drh_recrutement
in user_groupes
or \
497 grp_directeurs_bureau_recrutement
in user_groupes
or \
498 grp_administrateurs_recrutement
in user_groupes
:
502 def has_change_permission(self
, request
, obj
=None):
503 user_groupes
= request
.user
.groups
.all()
504 if request
.user
.is_superuser
is True or \
505 grp_drh_recrutement
in user_groupes
or \
506 grp_directeurs_bureau_recrutement
in user_groupes
or \
507 grp_administrateurs_recrutement
in user_groupes
:
511 class CandidatEvaluationAdmin(VersionAdmin
):
512 list_display
= ('_candidat', '_offre_emploi', 'evaluateur', '_note',
514 _readonly_fields
= ('candidat', 'evaluateur') # voir fonctions de permissions
516 ('Évaluation du candidat', {
517 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
521 ### Actions à afficher
522 def get_actions(self
, request
):
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.
535 return "<a href='%s'>Candidat non évalué</a>" % \
536 (reverse('admin:recrutement_candidatevaluation_change',
538 return "<a href='%s'>%s</a>" % \
539 (reverse('admin:recrutement_candidatevaluation_change',
540 args
=(obj
.id,)), obj
.note
)
541 _note
.allow_tags
= True
542 _note
.short_description
= "Note"
543 _note
.admin_order_field
= 'note'
545 ### Lien en lecture seule vers le candidat
546 def _candidat(self
, obj
):
547 return "<a href='%s'>%s</a>" \
548 % (reverse('admin:recrutement_proxycandidat_change',
549 args
=(obj
.candidat
.id,)), obj
.candidat
)
550 _candidat
.allow_tags
= True
551 _candidat
.short_description
= 'Candidat'
553 ### Afficher commentaire
554 def _commentaire(self
, obj
):
556 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
557 dans le champ commentaire, Aucun au lieu de (None)
558 Sinon afficher la note.
560 if obj
.commentaire
is None:
562 return obj
.commentaire
563 _commentaire
.allow_tags
= True
564 _commentaire
.short_description
= "Commentaire"
566 ### Afficher offre d'emploi
567 def _offre_emploi(self
, obj
):
568 return "<a href='%s'>%s</a>" % \
569 (reverse('admin:recrutement_proxyoffreemploi_change',
570 args
=(obj
.candidat
.offre_emploi
.id,)), obj
.candidat
.offre_emploi
)
571 _offre_emploi
.allow_tags
= True
572 _offre_emploi
.short_description
= "Voir offre d'emploi"
574 ### Permissions add, delete, change
575 def has_add_permission(self
, request
):
576 user_groupes
= request
.user
.groups
.all()
577 if request
.user
.is_superuser
is True or \
578 grp_drh_recrutement
in user_groupes
or \
579 grp_directeurs_bureau_recrutement
in user_groupes
or \
580 grp_administrateurs_recrutement
in user_groupes
:
581 self
.readonly_fields
= ()
583 self
.readonly_fields
= self
._readonly_fields
586 def has_change_permission(self
, request
, obj
=None):
588 Permettre la visualisation dans la changelist
589 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
592 if request
.user
.is_superuser
is True:
594 self
.readonly_fields
= self
._readonly_fields
595 return obj
is None or request
.user
== obj
.evaluateur
.user
598 def queryset(self
, request
):
600 Afficher uniquement les évaluations de l'évaluateur, sauf si
601 l'utilisateur est dans les groupes suivants.
603 qs
= self
.model
._default_manager
.get_query_set()
604 user_groupes
= request
.user
.groups
.all()
605 if grp_drh_recrutement
in user_groupes
or \
606 grp_directeurs_bureau_recrutement
in user_groupes
or \
607 grp_administrateurs_recrutement
in user_groupes
:
608 return qs
.select_related('offre_emploi')
611 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
612 except Evaluateur
.DoesNotExist
:
615 candidats_evaluations
= CandidatEvaluation
.objects
.\
616 filter(evaluateur
=evaluateur
)
617 candidats_evaluations_ids
= [ce
.id for ce
in \
618 candidats_evaluations
.all()]
619 return qs
.select_related('offre_emploi').\
620 filter(id__in
=candidats_evaluations_ids
)
622 class CourrielTemplateAdmin(VersionAdmin
):
623 ### Actions à afficher
624 def get_actions(self
, request
):
625 actions
= super(CourrielTemplateAdmin
, self
).get_actions(request
)
626 del actions
['delete_selected']
629 admin
.site
.register(OffreEmploi
, OffreEmploiAdmin
)
630 admin
.site
.register(ProxyOffreEmploi
, ProxyOffreEmploiAdmin
)
631 admin
.site
.register(Candidat
, CandidatAdmin
)
632 admin
.site
.register(ProxyCandidat
, ProxyCandidatAdmin
)
633 admin
.site
.register(CandidatEvaluation
, CandidatEvaluationAdmin
)
634 admin
.site
.register(Evaluateur
, EvaluateurAdmin
)
635 admin
.site
.register(CourrielTemplate
, CourrielTemplateAdmin
)