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()
74 if form
.declared_fields
.has_key('region'):
75 region_field
= form
.declared_fields
['region']
77 region_field
= form
.base_fields
['region']
79 if grp_drh_recrutement
in user_groupes
:
80 region_field
.queryset
= Region
.objects
.all()
82 region_field
.queryset
= Region
.objects
.\
83 filter(id=employe
.implantation
.region
.id)
86 if form
.declared_fields
.has_key('poste'):
87 poste_field
= form
.declared_fields
['poste']
89 poste_field
= form
.base_fields
['poste']
91 if grp_drh_recrutement
in user_groupes
:
92 poste_field
.queryset
= rh
.Poste
.objects
.all()
94 poste_field
.queryset
= rh
.Poste
.objects
.\
95 filter(implantation__region
=employe
.implantation
.region
).\
96 exclude(implantation__in
=IMPLANTATIONS_CENTRALES
)
99 if form
.declared_fields
.has_key('bureau'):
100 bureau_field
= form
.declared_fields
['bureau']
102 bureau_field
= form
.base_fields
['bureau']
104 if grp_drh_recrutement
in user_groupes
:
105 bureau_field
.queryset
= Bureau
.objects
.all()
107 bureau_field
.queryset
= Bureau
.objects
.\
108 filter(region
=employe
.implantation
.region
)
112 def queryset(self
, request
):
113 qs
= self
.model
._default_manager
.get_query_set()
114 user_groupes
= request
.user
.groups
.all()
115 if grp_drh_recrutement
in user_groupes
:
116 return qs
.select_related('offre_emploi')
118 if grp_directeurs_bureau_recrutement
in user_groupes
or \
119 grp_administrateurs_recrutement
in user_groupes
:
120 employe
= get_emp(request
.user
)
121 return qs
.select_related('offre_emploi').\
122 filter(region
=employe
.implantation
.region
)
124 if grp_evaluateurs_recrutement
in user_groupes
:
126 user
= Evaluateur
.objects
.get(user
=request
.user
)
127 except Evaluateur
.DoesNotExist
:
130 ids
= [o
.id for o
in user
.offres_emploi
.all()]
131 return qs
.select_related('offre_emploi').filter(id__in
=ids
)
134 ### Permission add, delete, change
135 def has_add_permission(self
, request
):
136 user_groupes
= request
.user
.groups
.all()
137 if grp_drh_recrutement
in user_groupes
or \
138 grp_directeurs_bureau_recrutement
in user_groupes
or \
139 grp_administrateurs_recrutement
in user_groupes
:
143 def has_delete_permission(self
, request
, obj
=None):
144 user_groupes
= request
.user
.groups
.all()
145 if grp_drh_recrutement
in user_groupes
or \
146 grp_directeurs_bureau_recrutement
in user_groupes
or \
147 grp_administrateurs_recrutement
in user_groupes
:
151 def has_change_permission(self
, request
, obj
=None):
152 user_groupes
= request
.user
.groups
.all()
153 if grp_drh_recrutement
in user_groupes
or \
154 grp_directeurs_bureau_recrutement
in user_groupes
or \
155 grp_administrateurs_recrutement
in user_groupes
:
159 class ProxyOffreEmploiAdmin(OffreEmploiAdmin
):
160 list_display
= ('nom', 'date_limite', 'region', 'statut',
162 readonly_fields
= ('description', 'bureau', 'duree_affectation',
163 'renumeration', 'debut_affectation', 'lieu_affectation',
164 'nom', 'resume', 'date_limite', 'region')
169 ('Description générale', {
170 'fields': ('resume','description', 'date_limite', )
173 'fields': ('lieu_affectation', 'bureau', 'region', )
176 'fields': ('debut_affectation', 'duree_affectation',
182 ### Actions à afficher
183 def get_actions(self
, request
):
184 actions
= super(ProxyOffreEmploiAdmin
, self
).get_actions(request
)
185 del actions
['affecter_evaluateurs_offre_emploi']
188 ### Lieu de redirection après le change
189 def response_change(self
, request
, obj
):
190 response
= super(ProxyOffreEmploiAdmin
, self
).response_change(request
,
192 user_groupes
= request
.user
.groups
.all()
193 if grp_drh_recrutement
in user_groupes
or \
194 grp_directeurs_bureau_recrutement
in user_groupes
or \
195 grp_administrateurs_recrutement
in user_groupes
:
196 return HttpResponseRedirect(reverse\
197 ('admin:recrutement_offreemploi_changelist'))
198 return HttpResponseRedirect(reverse\
199 ('admin:recrutement_proxyoffreemploi_changelist'))
202 def get_form(self
, request
, obj
=None, **kwargs
):
203 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
206 ### Permissions add, delete, change
207 def has_add_permission(self
, request
):
210 def has_delete_permission(self
, request
, obj
=None):
213 def has_change_permission(self
, request
, obj
=None):
214 user_groupes
= request
.user
.groups
.all()
215 if grp_evaluateurs_recrutement
in user_groupes
or \
216 grp_drh_recrutement
in user_groupes
or \
217 grp_directeurs_bureau_recrutement
in user_groupes
or \
218 grp_administrateurs_recrutement
in user_groupes
:
222 class CandidatPieceInline(admin
.TabularInline
):
223 model
= CandidatPiece
224 fields
= ('candidat', 'nom', 'path',)
228 class CandidatEvaluationInlineFormSet(BaseInlineFormSet
):
230 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
232 def __init__(self
, *args
, **kwargs
):
233 super(CandidatEvaluationInlineFormSet
, self
).__init__(*args
, **kwargs
)
234 self
.can_delete
= False
236 class CandidatEvaluationInline(admin
.TabularInline
):
237 model
= CandidatEvaluation
238 fields
= ('evaluateur', 'note', 'commentaire')
241 formset
= CandidatEvaluationInlineFormSet
244 def get_readonly_fields(self
, request
, obj
=None):
246 Empêche la modification des évaluations
249 return self
.readonly_fields
+('evaluateur', 'note', 'commentaire')
250 return self
.readonly_fields
252 class CandidatAdmin(VersionAdmin
):
253 exclude
= ('actif', )
254 date_hierarchy
= 'date_creation'
255 list_display
= ('nom', 'prenom', 'offre_emploi','statut',
256 'voir_offre_emploi', 'calculer_moyenne',
257 'afficher_candidat',)
258 list_filter
= ('offre_emploi', )
262 'fields': ('offre_emploi', )
264 ('Informations personnelles', {
265 'fields': ('prenom','nom','genre', 'nationalite',
266 'situation_famille', 'nombre_dependant',)
269 'fields': ('telephone', 'email', 'adresse', 'ville',
270 'etat_province', 'code_postal', 'pays', )
272 ('Informations professionnelles', {
273 'fields': ('niveau_diplome','employeur_actuel',
274 'poste_actuel', 'domaine_professionnel',)
277 'fields': ('statut', )
282 CandidatEvaluationInline
,
285 actions
= ['envoyer_courriel_candidats']
287 ### Actions à afficher
288 def get_actions(self
, request
):
289 actions
= super(CandidatAdmin
, self
).get_actions(request
)
290 del actions
['delete_selected']
293 ### Envoyer un courriel à des candidats
294 def envoyer_courriel_candidats(modeladmin
, obj
, candidats
):
295 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
297 return HttpResponseRedirect(reverse('selectionner_template')+
298 "?ids=%s" % (",".join(selected
)))
299 envoyer_courriel_candidats
.short_description
= u
'Envoyer courriel'
301 ### Évaluer un candidat
302 def evaluer_candidat(self
, obj
):
303 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
304 (reverse('admin:recrutement_candidatevaluation_changelist'),
306 evaluer_candidat
.allow_tags
= True
307 evaluer_candidat
.short_description
= 'Évaluation'
309 ### Afficher un candidat
310 def afficher_candidat(self
, obj
):
311 return "<a href='%s'>Voir le candidat</a>" % \
312 (reverse('admin:recrutement_proxycandidat_change', args
=(obj
.id,)))
313 afficher_candidat
.allow_tags
= True
314 afficher_candidat
.short_description
= u
'Détails du candidat'
316 ### Voir l'offre d'emploi
317 def voir_offre_emploi(self
, obj
):
318 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
319 (reverse('admin:recrutement_proxyoffreemploi_change',
320 args
=(obj
.offre_emploi
.id,)))
321 voir_offre_emploi
.allow_tags
= True
322 voir_offre_emploi
.short_description
= "Afficher l'offre d'emploi"
324 ### Calculer la moyenne des notes
325 def calculer_moyenne(self
, obj
):
326 evaluations
= CandidatEvaluation
.objects
.filter(candidat
=obj
)
327 offre_emploi
= obj
.offre_emploi
329 notes
= [evaluation
.note
for evaluation
in evaluations
.all() \
330 if evaluation
.note
is not None]
333 moyenne_votes
= float(sum(notes
)) / len(notes
)
335 moyenne_votes
= "Non disponible"
337 calculer_moyenne
.allow_tags
= True
338 calculer_moyenne
.short_description
= "Moyenne des notes"
340 ### Permissions add, delete, change
341 def has_add_permission(self
, request
):
342 user_groupes
= request
.user
.groups
.all()
343 if 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 grp_drh_recrutement
in user_groupes
or \
352 grp_directeurs_bureau_recrutement
in user_groupes
or \
353 grp_administrateurs_recrutement
in user_groupes
:
357 def has_change_permission(self
, request
, obj
=None):
358 user_groupes
= request
.user
.groups
.all()
359 if grp_drh_recrutement
in user_groupes
or \
360 grp_directeurs_bureau_recrutement
in user_groupes
or \
361 grp_administrateurs_recrutement
in user_groupes
:
366 def queryset(self
, request
):
368 Spécifie un queryset limité, autrement Django exécute un
369 select_related() sans paramètre, ce qui a pour effet de charger tous
370 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
371 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
372 génération d'une requête infinie.
375 qs
= self
.model
._default_manager
.get_query_set()
376 user_groupes
= request
.user
.groups
.all()
377 if grp_drh_recrutement
in user_groupes
:
378 return qs
.select_related('candidats')
380 if grp_directeurs_bureau_recrutement
in user_groupes
or \
381 grp_administrateurs_recrutement
in user_groupes
:
382 employe
= get_emp(request
.user
)
383 return qs
.select_related('candidats').\
384 filter(offre_emploi__region
=employe
.implantation
.region
)
386 if grp_evaluateurs_recrutement
in user_groupes
:
388 user
= Evaluateur
.objects
.get(user
=request
.user
)
389 except Evaluateur
.DoesNotExist
:
392 offres_emploi
= [o
for o
in user
.offres_emploi
.all()]
393 candidat_ids
= [o
.candidat
.id for o
in offres_emploi
]
394 return qs
.select_related('candidats').filter(id__in
=candidat_ids
)
398 class CandidatPieceROInline(CandidatPieceInline
):
401 template
= "admin/recrutement/proxycandidat/piece_jointe.html"
403 class ProxyCandidatAdmin(CandidatAdmin
):
404 readonly_fields
= ('statut', 'offre_emploi', 'prenom', 'nom',
405 'genre', 'nationalite', 'situation_famille',
406 'nombre_dependant', 'telephone', 'email', 'adresse',
407 'ville', 'etat_province', 'code_postal', 'pays',
408 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
409 'domaine_professionnel', 'pieces_jointes',)
412 'fields': ('offre_emploi', )
414 ('Informations personnelles', {
415 'fields': ('prenom','nom','genre', 'nationalite',
416 'situation_famille', 'nombre_dependant',)
419 'fields': ('telephone', 'email', 'adresse', 'ville',
420 'etat_province', 'code_postal', 'pays', )
422 ('Informations professionnelles', {
423 'fields': ('niveau_diplome','employeur_actuel',
424 'poste_actuel', 'domaine_professionnel',)
428 CandidatPieceROInline
,
430 ### Lieu de redirection après le change
431 def response_change(self
, request
, obj
):
432 response
= super(ProxyCandidatAdmin
, self
).response_change(request
, obj
)
433 user_groupes
= request
.user
.groups
.all()
434 if grp_drh_recrutement
in user_groupes
or \
435 grp_directeurs_bureau_recrutement
in user_groupes
or \
436 grp_administrateurs_recrutement
in user_groupes
:
437 return HttpResponseRedirect(reverse\
438 ('admin:recrutement_candidat_changelist'))
439 return HttpResponseRedirect(reverse\
440 ('admin:recrutement_proxycandidat_changelist'))
442 ### Permissions add, delete, change
443 def has_add_permission(self
, request
):
446 def has_delete_permission(self
, request
, obj
=None):
449 def has_change_permission(self
, request
, obj
=None):
450 user_groupes
= request
.user
.groups
.all()
451 if grp_drh_recrutement
in user_groupes
or \
452 grp_evaluateurs_recrutement
in user_groupes
or \
453 grp_directeurs_bureau_recrutement
in user_groupes
or \
454 grp_administrateurs_recrutement
in user_groupes
:
458 class CandidatPieceAdmin(admin
.ModelAdmin
):
459 list_display
= ('nom', 'candidat', )
462 def queryset(self
, request
):
464 Spécifie un queryset limité, autrement Django exécute un
465 select_related() sans paramètre, ce qui a pour effet de charger tous
466 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
467 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
468 génération d'une requête infinie.
469 Affiche la liste de candidats que si le user connecté
470 possède un Evaluateur
472 qs
= self
.model
._default_manager
.get_query_set()
473 return qs
.select_related('candidat')
475 class EvaluateurAdmin(VersionAdmin
):
480 ("Offres d'emploi à évaluer", {
481 'fields': ('offres_emploi',)
485 ### Actions à afficher
486 def get_actions(self
, request
):
487 actions
= super(EvaluateurAdmin
, self
).get_actions(request
)
488 del actions
['delete_selected']
491 ### Permissions add, delete, change
492 def has_add_permission(self
, request
):
493 user_groupes
= request
.user
.groups
.all()
494 if grp_drh_recrutement
in user_groupes
or \
495 grp_directeurs_bureau_recrutement
in user_groupes
or \
496 grp_administrateurs_recrutement
in user_groupes
:
500 def has_delete_permission(self
, request
, obj
=None):
501 user_groupes
= request
.user
.groups
.all()
502 if grp_drh_recrutement
in user_groupes
or \
503 grp_directeurs_bureau_recrutement
in user_groupes
or \
504 grp_administrateurs_recrutement
in user_groupes
:
508 def has_change_permission(self
, request
, obj
=None):
509 user_groupes
= request
.user
.groups
.all()
510 if grp_drh_recrutement
in user_groupes
or \
511 grp_directeurs_bureau_recrutement
in user_groupes
or \
512 grp_administrateurs_recrutement
in user_groupes
:
516 class CandidatEvaluationAdmin(VersionAdmin
):
517 list_display
= ('_candidat', '_offre_emploi', 'evaluateur', '_note',
519 readonly_fields
= ('candidat', 'evaluateur')
521 ('Évaluation du candidat', {
522 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
526 ### Actions à afficher
527 def get_actions(self
, request
):
528 actions
= super(CandidatEvaluationAdmin
, self
).get_actions(request
)
529 del actions
['delete_selected']
533 def _note(self
, obj
):
535 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
536 un lien pour Évaluer le candidat.
537 Sinon afficher la note.
540 return "<a href='%s'>Candidat non évalué</a>" % \
541 (reverse('admin:recrutement_candidatevaluation_change',
543 return "<a href='%s'>%s</a>" % \
544 (reverse('admin:recrutement_candidatevaluation_change',
545 args
=(candidat_evaluation
.id,)), obj
.note
)
546 _note
.allow_tags
= True
547 _note
.short_description
= "Note"
548 _note
.admin_order_field
= 'note'
550 ### Lien en lecture seule vers le candidat
551 def _candidat(self
, obj
):
552 return "<a href='%s'>%s</a>" \
553 % (reverse('admin:recrutement_proxycandidat_change',
554 args
=(obj
.candidat
.id,)), obj
.candidat
)
555 _candidat
.allow_tags
= True
556 _candidat
.short_description
= 'Candidat'
558 ### Afficher commentaire
559 def _commentaire(self
, obj
):
561 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
562 dans le champ commentaire, Aucun au lieu de (None)
563 Sinon afficher la note.
565 if obj
.commentaire
is None:
567 return obj
.commentaire
568 _commentaire
.allow_tags
= True
569 _commentaire
.short_description
= "Commentaire"
571 ### Afficher offre d'emploi
572 def _offre_emploi(self
, obj
):
573 return "<a href='%s'>%s</a>" % \
574 (reverse('admin:recrutement_proxyoffreemploi_change',
575 args
=(obj
.candidat
.offre_emploi
.id,)), obj
.candidat
.offre_emploi
)
576 _offre_emploi
.allow_tags
= True
577 _offre_emploi
.short_description
= "Voir offre d'emploi"
579 ### Permissions add, delete, change
580 def has_add_permission(self
, request
):
581 user_groupes
= request
.user
.groups
.all()
582 if grp_drh_recrutement
in user_groupes
or \
583 grp_directeurs_bureau_recrutement
in user_groupes
or \
584 grp_administrateurs_recrutement
in user_groupes
:
588 def has_change_permission(self
, request
, obj
=None):
590 Permettre la visualisation dans la changelist
591 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
594 return obj
is None or request
.user
== obj
.evaluateur
.user
597 def queryset(self
, request
):
599 Afficher uniquement les évaluations de l'évaluateur, sauf si
600 l'utilisateur est dans les groupes suivants.
602 qs
= self
.model
._default_manager
.get_query_set()
603 user_groupes
= request
.user
.groups
.all()
604 if grp_drh_recrutement
in user_groupes
or \
605 grp_directeurs_bureau_recrutement
in user_groupes
or \
606 grp_administrateurs_recrutement
in user_groupes
:
607 return qs
.select_related('offre_emploi')
610 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
611 except Evaluateur
.DoesNotExist
:
614 candidats_evaluations
= CandidatEvaluation
.objects
.\
615 filter(evaluateur
=evaluateur
)
616 candidats_evaluations_ids
= [ce
.id for ce
in \
617 candidats_evaluations
.all()]
618 return qs
.select_related('offre_emploi').\
619 filter(id__in
=candidats_evaluations_ids
)
621 class CourrielTemplateAdmin(VersionAdmin
):
622 ### Actions à afficher
623 def get_actions(self
, request
):
624 actions
= super(CourrielTemplateAdmin
, self
).get_actions(request
)
625 del actions
['delete_selected']
628 admin
.site
.register(OffreEmploi
, OffreEmploiAdmin
)
629 admin
.site
.register(ProxyOffreEmploi
, ProxyOffreEmploiAdmin
)
630 admin
.site
.register(Candidat
, CandidatAdmin
)
631 admin
.site
.register(ProxyCandidat
, ProxyCandidatAdmin
)
632 admin
.site
.register(CandidatEvaluation
, CandidatEvaluationAdmin
)
633 admin
.site
.register(Evaluateur
, EvaluateurAdmin
)
634 admin
.site
.register(CourrielTemplate
, CourrielTemplateAdmin
)