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
8 from django
.conf
import settings
10 from reversion
.admin
import VersionAdmin
11 from datamaster_modeles
.models
import Region
, Bureau
12 from project
.rh
import models
as rh
14 from project
.dae
.utils
import get_employe_from_user
as get_emp
15 from recrutement
.models
import *
16 from recrutement
.workflow
import grp_drh_recrutement
, grp_directeurs_bureau_recrutement
, \
17 grp_administrateurs_recrutement
, \
18 grp_correspondants_rh_recrutement
20 from recrutement
.forms
import *
23 IMPLANTATIONS_CENTRALES
= [15, 19]
25 class OrderedChangeList(admin
.views
.main
.ChangeList
):
27 Surcharge pour appliquer le order_by d'un annotate
29 def get_query_set(self
):
30 qs
= super(OrderedChangeList
, self
).get_query_set()
31 qs
= qs
.order_by('-moyenne')
34 class OffreEmploiAdmin(VersionAdmin
):
35 date_hierarchy
= 'date_creation'
36 list_display
= ('nom', 'date_limite', 'region', 'statut',
37 'est_affiche', '_candidatsList', )
38 exclude
= ('actif', 'poste_nom', 'resume',)
39 list_filter
= ('statut',)
40 actions
= ['affecter_evaluateurs_offre_emploi', ]
41 form
= OffreEmploiForm
43 ### Actions à afficher
44 def get_actions(self
, request
):
45 actions
= super(OffreEmploiAdmin
, self
).get_actions(request
)
46 del actions
['delete_selected']
49 ### Affecter un évaluateurs à des offres d'emploi
50 def affecter_evaluateurs_offre_emploi(modeladmin
, obj
, candidats
):
51 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
53 return HttpResponseRedirect(reverse('affecter_evaluateurs_offre_emploi')+
54 "?ids=%s" % (",".join(selected
)))
55 affecter_evaluateurs_offre_emploi
.short_description
= u
'Affecter évaluateur(s)'
57 ### Afficher la liste des candidats pour l'offre d'emploi
58 def _candidatsList(self
, obj
):
59 return "<a href='%s?offre_emploi__id__exact=%s'>Voir les candidats \
60 </a>" % (reverse('admin:recrutement_candidat_changelist'), obj
.id)
61 _candidatsList
.allow_tags
= True
62 _candidatsList
.short_description
= "Afficher la liste des candidats"
65 def get_form(self
, request
, obj
=None, **kwargs
):
66 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
67 employe
= get_emp(request
.user
)
68 user_groupes
= request
.user
.groups
.all()
72 if form
.declared_fields
.has_key('region'):
73 region_field
= form
.declared_fields
['region']
75 region_field
= form
.base_fields
['region']
77 if grp_drh_recrutement
in user_groupes
:
78 region_field
.queryset
= Region
.objects
.all()
80 region_field
.queryset
= Region
.objects
.\
81 filter(id=employe
.implantation
.region
.id)
84 if form
.declared_fields
.has_key('poste'):
85 poste_field
= form
.declared_fields
['poste']
87 poste_field
= form
.base_fields
['poste']
89 if grp_drh_recrutement
in user_groupes
:
90 poste_field
.queryset
= rh
.Poste
.objects
.all()
92 poste_field
.queryset
= rh
.Poste
.objects
.\
93 filter(implantation__region
=employe
.implantation
.region
).\
94 exclude(implantation__in
=IMPLANTATIONS_CENTRALES
)
97 if form
.declared_fields
.has_key('bureau'):
98 bureau_field
= form
.declared_fields
['bureau']
100 bureau_field
= form
.base_fields
['bureau']
102 if grp_drh_recrutement
in user_groupes
:
103 bureau_field
.queryset
= Bureau
.objects
.all()
105 bureau_field
.queryset
= Bureau
.objects
.\
106 filter(region
=employe
.implantation
.region
)
111 def queryset(self
, request
):
112 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi')
113 user_groupes
= request
.user
.groups
.all()
114 if grp_drh_recrutement
in user_groupes
:
117 if grp_directeurs_bureau_recrutement
in user_groupes
or \
118 grp_correspondants_rh_recrutement
in user_groupes
or \
119 grp_administrateurs_recrutement
in user_groupes
:
120 employe
= get_emp(request
.user
)
121 return qs
.filter(region
=employe
.implantation
.region
)
123 if Evaluateur
.objects
.filter(user
=request
.user
).exists():
124 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
125 offre_ids
= [e
.candidat
.offre_emploi_id
for e
in
126 CandidatEvaluation
.objects
.select_related('candidat').filter(evaluateur
=evaluateur
)]
127 return qs
.filter(id__in
=offre_ids
)
131 ### Permission add, delete, change
132 def has_add_permission(self
, request
):
133 user_groupes
= request
.user
.groups
.all()
134 if request
.user
.is_superuser
is True or \
135 grp_drh_recrutement
in user_groupes
or \
136 grp_directeurs_bureau_recrutement
in user_groupes
or \
137 grp_administrateurs_recrutement
in user_groupes
:
141 def has_delete_permission(self
, request
, obj
=None):
142 user_groupes
= request
.user
.groups
.all()
143 if request
.user
.is_superuser
is True or \
144 grp_drh_recrutement
in user_groupes
or \
145 grp_directeurs_bureau_recrutement
in user_groupes
or \
146 grp_administrateurs_recrutement
in user_groupes
:
150 def has_change_permission(self
, request
, obj
=None):
151 user_groupes
= request
.user
.groups
.all()
152 if request
.user
.is_superuser
is True or \
153 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', 'poste')
169 ('Description générale', {
170 'fields': ('description', 'date_limite', )
173 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
176 'fields': ('debut_affectation', 'duree_affectation',
183 ### Lieu de redirection après le change
184 def response_change(self
, request
, obj
):
185 return HttpResponseRedirect(reverse\
186 ('admin:recrutement_proxyoffreemploi_changelist'))
189 def get_form(self
, request
, obj
=None, **kwargs
):
190 form
= super(OffreEmploiAdmin
, self
).get_form(request
, obj
, **kwargs
)
193 ### Permissions add, delete, change
194 def has_add_permission(self
, request
):
197 def has_delete_permission(self
, request
, obj
=None):
200 def has_change_permission(self
, request
, obj
=None):
203 class CandidatPieceInline(admin
.TabularInline
):
204 model
= CandidatPiece
205 fields
= ('candidat', 'nom', 'path',)
209 class ReadOnlyCandidatPieceInline(CandidatPieceInline
):
210 readonly_fields
= ('candidat', 'nom', 'path', )
214 class CandidatEvaluationInlineFormSet(BaseInlineFormSet
):
216 Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
218 def __init__(self
, *args
, **kwargs
):
219 super(CandidatEvaluationInlineFormSet
, self
).__init__(*args
, **kwargs
)
220 self
.can_delete
= False
222 class CandidatEvaluationInline(admin
.TabularInline
):
223 model
= CandidatEvaluation
224 fields
= ('evaluateur', 'note', 'commentaire')
227 formset
= CandidatEvaluationInlineFormSet
230 def get_readonly_fields(self
, request
, obj
=None):
232 Empêche la modification des évaluations
235 return self
.readonly_fields
+('evaluateur', 'note', 'commentaire')
236 return self
.readonly_fields
238 class CandidatAdmin(VersionAdmin
):
239 search_fields
= ('nom', 'prenom' )
240 exclude
= ('actif', )
241 list_editable
= ('statut', )
242 list_display
= ('nom', 'prenom', 'offre_emploi',
243 'voir_offre_emploi', 'calculer_moyenne',
244 'afficher_candidat', '_date_creation', 'statut', )
245 list_filter
= ('offre_emploi', 'offre_emploi__region', 'statut', )
249 'fields': ('offre_emploi', )
251 ('Informations personnelles', {
252 'fields': ('prenom','nom','genre', 'nationalite',
253 'situation_famille', 'nombre_dependant',)
256 'fields': ('telephone', 'email', 'adresse', 'ville',
257 'etat_province', 'code_postal', 'pays', )
259 ('Informations professionnelles', {
260 'fields': ('niveau_diplome','employeur_actuel',
261 'poste_actuel', 'domaine_professionnel',)
264 'fields': ('statut', )
269 CandidatEvaluationInline
,
272 actions
= ['envoyer_courriel_candidats']
274 def _date_creation(self
, obj
):
275 return obj
.date_creation
276 _date_creation
.order_field
= "date_creation"
277 _date_creation
.short_description
= "Date de création"
279 ### Actions à afficher
280 def get_actions(self
, request
):
281 actions
= super(CandidatAdmin
, self
).get_actions(request
)
282 del actions
['delete_selected']
285 ### Envoyer un courriel à des candidats
286 def envoyer_courriel_candidats(modeladmin
, obj
, candidats
):
287 selected
= obj
.POST
.getlist(admin
.ACTION_CHECKBOX_NAME
)
289 return HttpResponseRedirect(reverse('selectionner_template')+
290 "?ids=%s" % (",".join(selected
)))
291 envoyer_courriel_candidats
.short_description
= u
'Envoyer courriel'
293 ### Évaluer un candidat
294 def evaluer_candidat(self
, obj
):
295 return "<a href='%s?candidat__id__exact=%s'>Évaluer le candidat</a>" % \
296 (reverse('admin:recrutement_candidatevaluation_changelist'),
298 evaluer_candidat
.allow_tags
= True
299 evaluer_candidat
.short_description
= 'Évaluation'
301 ### Afficher un candidat
302 def afficher_candidat(self
, obj
):
303 items
= [u
"<li><a href='%s%s'>%s</li>" % \
304 (settings
.OE_PRIVE_MEDIA_URL
, pj
.path
, pj
.get_nom_display()) \
305 for pj
in obj
.pieces_jointes()]
306 html
= "<a href='%s'>Voir le candidat</a>" % \
307 (reverse('admin:recrutement_proxycandidat_change', args
=(obj
.id,)))
308 return "%s<ul>%s</ul>" % (html
, "\n".join(items
))
309 afficher_candidat
.allow_tags
= True
310 afficher_candidat
.short_description
= u
'Détails du candidat'
312 ### Voir l'offre d'emploi
313 def voir_offre_emploi(self
, obj
):
314 return "<a href='%s'>Voir l'offre d'emploi</a>" % \
315 (reverse('admin:recrutement_proxyoffreemploi_change',
316 args
=(obj
.offre_emploi
.id,)))
317 voir_offre_emploi
.allow_tags
= True
318 voir_offre_emploi
.short_description
= "Afficher l'offre d'emploi"
320 ### Calculer la moyenne des notes
321 def calculer_moyenne(self
, obj
):
322 evaluations
= CandidatEvaluation
.objects
.filter(candidat
=obj
)
324 notes
= [evaluation
.note
for evaluation
in evaluations \
325 if evaluation
.note
is not None]
328 moyenne_votes
= round(float(sum(notes
)) / len(notes
), 2)
330 moyenne_votes
= "Non disponible"
332 totales
= len(evaluations
)
335 if obj
.statut
== 'REC':
336 if totales
== faites
:
338 elif faites
> 0 and float(totales
) / float(faites
) >= 2:
345 return """<span style="color: %s;">%s (%s/%s)</span>""" % (color
, moyenne_votes
, faites
, totales
)
346 calculer_moyenne
.allow_tags
= True
347 calculer_moyenne
.short_description
= "Moyenne"
348 calculer_moyenne
.admin_order_field
= ""
350 ### Permissions add, delete, change
351 def has_add_permission(self
, request
):
352 user_groupes
= request
.user
.groups
.all()
353 if request
.user
.is_superuser
is True or \
354 grp_correspondants_rh_recrutement
in user_groupes
or \
355 grp_drh_recrutement
in user_groupes
or \
356 grp_directeurs_bureau_recrutement
in user_groupes
or \
357 grp_administrateurs_recrutement
in user_groupes
:
361 def has_delete_permission(self
, request
, obj
=None):
362 user_groupes
= request
.user
.groups
.all()
363 if request
.user
.is_superuser
is True or \
364 grp_correspondants_rh_recrutement
in user_groupes
or \
365 grp_drh_recrutement
in user_groupes
or \
366 grp_directeurs_bureau_recrutement
in user_groupes
or \
367 grp_administrateurs_recrutement
in user_groupes
:
371 def has_change_permission(self
, request
, obj
=None):
372 user_groupes
= request
.user
.groups
.all()
373 if request
.user
.is_superuser
is True or \
374 grp_correspondants_rh_recrutement
in user_groupes
or \
375 grp_drh_recrutement
in user_groupes
or \
376 grp_directeurs_bureau_recrutement
in user_groupes
or \
377 grp_administrateurs_recrutement
in user_groupes
:
381 def get_changelist(self
, request
, **kwargs
):
382 return OrderedChangeList
384 def queryset(self
, request
):
386 Spécifie un queryset limité, autrement Django exécute un
387 select_related() sans paramètre, ce qui a pour effet de charger tous
388 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
389 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
390 génération d'une requête infinie.
394 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi').annotate(moyenne
=Avg('evaluations__note'))
396 user_groupes
= request
.user
.groups
.all()
397 if grp_drh_recrutement
in user_groupes
:
400 if grp_directeurs_bureau_recrutement
in user_groupes
or \
401 grp_correspondants_rh_recrutement
in user_groupes
or \
402 grp_administrateurs_recrutement
in user_groupes
:
403 employe
= get_emp(request
.user
)
404 return qs
.filter(offre_emploi__region
=employe
.implantation
.region
)
406 if Evaluateur
.objects
.filter(user
=request
.user
).exists():
407 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
408 candidat_ids
= [e
.candidat
.id for e
in
409 CandidatEvaluation
.objects
.filter(evaluateur
=evaluateur
)]
410 return qs
.filter(id__in
=candidat_ids
)
414 class ProxyCandidatAdmin(CandidatAdmin
):
416 readonly_fields
= ('statut', 'offre_emploi', 'prenom', 'nom',
417 'genre', 'nationalite', 'situation_famille',
418 'nombre_dependant', 'telephone', 'email', 'adresse',
419 'ville', 'etat_province', 'code_postal', 'pays',
420 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
421 'domaine_professionnel', 'pieces_jointes',)
424 'fields': ('offre_emploi', )
426 ('Informations personnelles', {
427 'fields': ('prenom','nom','genre', 'nationalite',
428 'situation_famille', 'nombre_dependant',)
431 'fields': ('telephone', 'email', 'adresse', 'ville',
432 'etat_province', 'code_postal', 'pays', )
434 ('Informations professionnelles', {
435 'fields': ('niveau_diplome','employeur_actuel',
436 'poste_actuel', 'domaine_professionnel',)
439 inlines
= (CandidatEvaluationInline
, )
441 def has_add_permission(self
, request
):
444 def has_delete_permission(self
, request
, obj
=None):
447 def has_change_permission(self
, request
, obj
=None):
450 def get_actions(self
, request
):
453 class CandidatPieceAdmin(admin
.ModelAdmin
):
454 list_display
= ('nom', 'candidat', )
457 def queryset(self
, request
):
459 Spécifie un queryset limité, autrement Django exécute un
460 select_related() sans paramètre, ce qui a pour effet de charger tous
461 les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
462 modèles de Region, il existe plusieurs boucles, ce qui conduit à la
463 génération d'une requête infinie.
464 Affiche la liste de candidats que si le user connecté
465 possède un Evaluateur
467 qs
= self
.model
._default_manager
.get_query_set()
468 return qs
.select_related('candidat')
470 class EvaluateurAdmin(VersionAdmin
):
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
:
491 def has_delete_permission(self
, request
, obj
=None):
492 user_groupes
= request
.user
.groups
.all()
493 if request
.user
.is_superuser
is True or \
494 grp_drh_recrutement
in user_groupes
:
498 def has_change_permission(self
, request
, obj
=None):
499 user_groupes
= request
.user
.groups
.all()
500 if request
.user
.is_superuser
is True or \
501 grp_drh_recrutement
in user_groupes
:
505 class CandidatEvaluationAdmin(admin
.ModelAdmin
):
506 search_fields
= ('candidat__nom', 'candidat__prenom' )
507 list_display
= ('_candidat', '_statut', '_offre_emploi', 'evaluateur', '_note',
509 readonly_fields
= ('candidat', 'evaluateur')
510 list_filter
= ('candidat__statut', 'candidat__offre_emploi',)
512 ('Évaluation du candidat', {
513 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
517 def get_actions(self
, request
):
518 # on stocke l'evaluateur connecté (pas forcément la meilleure place...)
520 self
.evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
522 self
.evaluateur
= None
524 actions
= super(CandidatEvaluationAdmin
, self
).get_actions(request
)
525 del actions
['delete_selected']
529 def _note(self
, obj
):
531 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
532 un lien pour Évaluer le candidat.
533 Sinon afficher la note.
535 page
= self
.model
.__name__
.lower()
536 redirect_url
= 'admin:recrutement_%s_change' % page
539 label
= "Candidat non évalué"
543 if self
.evaluateur
== obj
.evaluateur
:
544 return "<a href='%s'>%s</a>" % (reverse(redirect_url
, args
=(obj
.id,)), label
)
547 _note
.allow_tags
= True
548 _note
.short_description
= "Note"
549 _note
.admin_order_field
= 'note'
551 def _statut(self
, obj
):
552 return obj
.candidat
.get_statut_display()
553 _statut
.order_field
= 'candidat__statut'
554 _statut
.short_description
= 'Statut'
557 ### Lien en lecture seule vers le candidat
558 def _candidat(self
, obj
):
559 return "<a href='%s'>%s</a>" \
560 % (reverse('admin:recrutement_proxycandidat_change',
561 args
=(obj
.candidat
.id,)), obj
.candidat
)
562 _candidat
.allow_tags
= True
563 _candidat
.short_description
= 'Candidat'
565 ### Afficher commentaire
566 def _commentaire(self
, obj
):
568 Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
569 dans le champ commentaire, Aucun au lieu de (None)
570 Sinon afficher la note.
572 if obj
.commentaire
is None:
574 return obj
.commentaire
575 _commentaire
.allow_tags
= True
576 _commentaire
.short_description
= "Commentaire"
578 ### Afficher offre d'emploi
579 def _offre_emploi(self
, obj
):
580 return "<a href='%s'>%s</a>" % \
581 (reverse('admin:recrutement_proxyoffreemploi_change',
582 args
=(obj
.candidat
.offre_emploi
.id,)), obj
.candidat
.offre_emploi
)
583 _offre_emploi
.allow_tags
= True
584 _offre_emploi
.short_description
= "Voir offre d'emploi"
586 def has_add_permission(self
, request
):
589 def has_delete_permission(self
, request
, obj
=None):
592 def has_change_permission(self
, request
, obj
=None):
594 Permettre la visualisation dans la changelist
595 mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
598 user_groupes
= request
.user
.groups
.all()
600 if request
.user
.is_superuser
or \
601 grp_drh_recrutement
in user_groupes
or \
602 grp_correspondants_rh_recrutement
in user_groupes
or \
603 grp_directeurs_bureau_recrutement
in user_groupes
or \
604 grp_administrateurs_recrutement
in user_groupes
:
605 is_recrutement
= True
607 is_recrutement
= False
610 Evaluateur
.objects
.get(user
=request
.user
)
613 is_evaluateur
= False
615 if obj
is None and (is_recrutement
or is_evaluateur
):
618 if request
.user
.is_superuser
is True:
622 return request
.user
== obj
.evaluateur
.user
627 def queryset(self
, request
):
629 Afficher uniquement les évaluations de l'évaluateur, sauf si
630 l'utilisateur est dans les groupes suivants.
632 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi')
633 user_groupes
= request
.user
.groups
.all()
635 if grp_drh_recrutement
in user_groupes
or \
636 grp_correspondants_rh_recrutement
in user_groupes
or \
637 grp_directeurs_bureau_recrutement
in user_groupes
or \
638 grp_administrateurs_recrutement
in user_groupes
:
641 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
642 candidats_evaluations
= \
643 CandidatEvaluation
.objects
.filter(evaluateur
=evaluateur
,
644 candidat__statut__in
=('REC', ))
645 candidats_evaluations_ids
= [ce
.id for ce
in candidats_evaluations
]
646 return qs
.filter(id__in
=candidats_evaluations_ids
)
649 class MesCandidatEvaluationAdmin(CandidatEvaluationAdmin
):
651 def has_change_permission(self
, request
, obj
=None):
653 Evaluateur
.objects
.get(user
=request
.user
)
656 is_evaluateur
= False
658 if obj
is None and is_evaluateur
:
662 return request
.user
== obj
.evaluateur
.user
666 def queryset(self
, request
):
667 qs
= self
.model
._default_manager
.get_query_set().select_related('offre_emploi')
668 evaluateur
= Evaluateur
.objects
.get(user
=request
.user
)
669 candidats_evaluations
= \
670 CandidatEvaluation
.objects
.filter(evaluateur
=evaluateur
,
671 candidat__statut__in
=('REC', ))
672 candidats_evaluations_ids
= [ce
.id for ce
in candidats_evaluations
]
673 return qs
.filter(id__in
=candidats_evaluations_ids
)
676 class CourrielTemplateAdmin(VersionAdmin
):
677 ### Actions à afficher
678 def get_actions(self
, request
):
679 actions
= super(CourrielTemplateAdmin
, self
).get_actions(request
)
680 del actions
['delete_selected']
683 admin
.site
.register(OffreEmploi
, OffreEmploiAdmin
)
684 admin
.site
.register(ProxyOffreEmploi
, ProxyOffreEmploiAdmin
)
685 admin
.site
.register(Candidat
, CandidatAdmin
)
686 admin
.site
.register(ProxyCandidat
, ProxyCandidatAdmin
)
687 admin
.site
.register(CandidatEvaluation
, CandidatEvaluationAdmin
)
688 admin
.site
.register(MesCandidatEvaluation
, MesCandidatEvaluationAdmin
)
689 admin
.site
.register(Evaluateur
, EvaluateurAdmin
)
690 admin
.site
.register(CourrielTemplate
, CourrielTemplateAdmin
)