--- /dev/null
+# -*- encoding: utf-8 -*-
+
+import textwrap
+
+from auf.django.emploi.models import OffreEmploi, Candidat, CandidatPiece
+from auf.django.references.models import Region, Bureau
+from django.conf import settings
+from django.contrib import admin
+from django.core.urlresolvers import reverse
+from django.db.models import Avg
+from django.forms.models import BaseInlineFormSet
+from django.http import HttpResponseRedirect
+from django.shortcuts import redirect
+from reversion.admin import VersionAdmin
+
+from project import groups
+from project.rh import models as rh
+from project.recrutement.forms import OffreEmploiForm
+from project.recrutement.models import \
+ Evaluateur, CandidatEvaluation, \
+ ProxyOffreEmploi, ProxyCandidat, MesCandidatEvaluation, \
+ CourrielTemplate
+
+### CONSTANTES
+IMPLANTATIONS_CENTRALES = [15, 19]
+
+
+class BaseAdmin(admin.ModelAdmin):
+
+ class Media:
+ css = {'screen': (
+ 'css/admin_custom.css',
+ 'jquery-autocomplete/jquery.autocomplete.css',
+ )}
+ js = (
+ 'https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js',
+ 'jquery-autocomplete/jquery.autocomplete.min.js',
+ )
+
+
+class OrderedChangeList(admin.views.main.ChangeList):
+ """
+ Surcharge pour appliquer le order_by d'un annotate
+ """
+ def get_query_set(self):
+ qs = super(OrderedChangeList, self).get_query_set()
+ qs = qs.order_by('-moyenne')
+ return qs
+
+
+class OffreEmploiAdminMixin(BaseAdmin):
+ date_hierarchy = 'date_creation'
+ list_display = (
+ 'nom', 'date_limite', 'region', 'statut', 'est_affiche',
+ '_candidatsList'
+ )
+ exclude = ('actif', 'poste_nom', 'resume',)
+ list_filter = ('statut',)
+ actions = ['affecter_evaluateurs_offre_emploi', ]
+ form = OffreEmploiForm
+
+ ### Actions à afficher
+ def get_actions(self, request):
+ actions = super(OffreEmploiAdminMixin, self).get_actions(request)
+ del actions['delete_selected']
+ return actions
+
+ ### Affecter un évaluateurs à des offres d'emploi
+ def affecter_evaluateurs_offre_emploi(modeladmin, obj, candidats):
+ selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
+
+ return HttpResponseRedirect(
+ reverse('affecter_evaluateurs_offre_emploi') +
+ "?ids=%s" % (",".join(selected))
+ )
+
+ affecter_evaluateurs_offre_emploi.short_description = \
+ u'Affecter évaluateur(s)'
+
+ ### Afficher la liste des candidats pour l'offre d'emploi
+ def _candidatsList(self, obj):
+ return "<a href='%s?offre_emploi__id__exact=%s'>Voir les candidats \
+ </a>" % (reverse('admin:recrutement_proxycandidat_changelist'), obj.id)
+ _candidatsList.allow_tags = True
+ _candidatsList.short_description = "Afficher la liste des candidats"
+
+ ### Formulaire
+ def get_form(self, request, obj=None, **kwargs):
+ form = super(OffreEmploiAdminMixin, self).get_form(request, obj, **kwargs)
+ employe = groups.get_employe_from_user(request.user)
+ user_groupes = [g.name for g in request.user.groups.all()]
+
+ # Region
+
+ if 'region' in form.declared_fields:
+ region_field = form.declared_fields['region']
+ read_only = False
+ elif 'region' in form.base_fields:
+ region_field = form.base_fields['region']
+ read_only = False
+ else:
+ read_only = True
+
+ if not read_only:
+ if groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ region_field.queryset = Region.objects.all()
+ else:
+ region_field.queryset = Region.objects.\
+ filter(id=employe.implantation.region.id)
+
+ # Poste
+ if 'poste' in form.declared_fields:
+ poste_field = form.declared_fields['poste']
+ read_only = False
+ elif 'poste' in form.base_fields:
+ poste_field = form.base_fields['poste']
+ read_only = False
+ else:
+ read_only = True
+
+ if not read_only:
+ if groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ poste_field.queryset = rh.Poste.objects.all()
+ else:
+ poste_field.queryset = rh.Poste.objects.\
+ filter(implantation__region=employe.implantation.region).\
+ exclude(implantation__in=IMPLANTATIONS_CENTRALES)
+
+ # Bureau
+ if 'bureau' in form.declared_fields:
+ bureau_field = form.declared_fields['bureau']
+ read_only = False
+ elif 'bureau' in form.base_fields:
+ bureau_field = form.base_fields['bureau']
+ read_only = False
+ else:
+ read_only = True
+ if not read_only:
+ if groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ bureau_field.queryset = Bureau.objects.all()
+ else:
+ bureau_field.queryset = \
+ Bureau.objects.filter(region=employe.implantation.region)
+
+ return form
+
+ ### Queryset
+
+ def queryset(self, request):
+ qs = self.model._default_manager.get_query_set() \
+ .select_related('offre_emploi')
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return qs
+
+ if groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes:
+ employe = groups.get_employe_from_user(request.user)
+ return qs.filter(region=employe.implantation.region)
+
+ if Evaluateur.objects.filter(user=request.user).exists():
+ evaluateur = Evaluateur.objects.get(user=request.user)
+ offre_ids = [
+ e.candidat.offre_emploi_id
+ for e in CandidatEvaluation.objects
+ .select_related('candidat')
+ .filter(evaluateur=evaluateur)
+ ]
+ return qs.filter(id__in=offre_ids)
+
+ return qs.none()
+
+ ### Permission add, delete, change
+ def has_add_permission(self, request):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+
+ if obj is not None:
+ employe = groups.get_employe_from_user(request.user)
+ if (groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes) and (
+ employe.implantation.region == obj.lieu_affectation.region):
+ return True
+
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+
+ if obj is not None:
+ employe = groups.get_employe_from_user(request.user)
+ if (groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes) and (
+ employe.implantation.region == obj.lieu_affectation.region):
+ return True
+ else:
+ if groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes:
+ return True
+
+
+ return False
+
+
+class OffreEmploiAdmin(VersionAdmin, OffreEmploiAdminMixin):
+ pass
+
+
+class ProxyOffreEmploiAdmin(OffreEmploiAdminMixin):
+ list_display = (
+ 'nom', 'date_limite', 'region', 'statut', 'est_affiche'
+ )
+ readonly_fields = (
+ 'description', 'bureau', 'duree_affectation', 'renumeration',
+ 'debut_affectation', 'lieu_affectation', 'nom', 'resume',
+ 'date_limite', 'region', 'poste'
+ )
+ fieldsets = (
+ ('Nom', {
+ 'fields': ('nom',)
+ }),
+ ('Description générale', {
+ 'fields': ('description', 'date_limite',)
+ }),
+ ('Coordonnées', {
+ 'fields': ('lieu_affectation', 'bureau', 'region', 'poste',)
+ }),
+ ('Autre', {
+ 'fields': (
+ 'debut_affectation', 'duree_affectation', 'renumeration',
+ )
+ }),
+ )
+ inlines = []
+
+ ### Lieu de redirection après le change
+ def response_change(self, request, obj):
+ return redirect('admin:recrutement_proxyoffreemploi_changelist')
+
+ ### Formulaire
+ def get_form(self, request, obj=None, **kwargs):
+ form = super(ProxyOffreEmploiAdmin, self).get_form(request, obj, **kwargs)
+ return form
+
+ ### Permissions add, delete, change
+ def has_add_permission(self, request):
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+
+ if obj is not None:
+ return True
+
+ return False
+
+
+class CandidatPieceInline(admin.TabularInline):
+ model = CandidatPiece
+ fields = ('candidat', 'nom', 'path',)
+ extra = 1
+ max_num = 3
+
+
+class ReadOnlyCandidatPieceInline(CandidatPieceInline):
+ readonly_fields = ('candidat', 'nom', 'path', )
+ cand_delete = False
+
+
+class CandidatEvaluationInlineFormSet(BaseInlineFormSet):
+ """
+ Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
+ """
+ def __init__(self, *args, **kwargs):
+ super(CandidatEvaluationInlineFormSet, self).__init__(*args, **kwargs)
+ self.can_delete = False
+
+
+class CandidatEvaluationInline(admin.TabularInline):
+ model = CandidatEvaluation
+ fields = ('evaluateur', 'note', 'commentaire')
+ max_num = 0
+ extra = 0
+ formset = CandidatEvaluationInlineFormSet
+
+ ### Fields readonly
+ def get_readonly_fields(self, request, obj=None):
+ """
+ Empêche la modification des évaluations
+ """
+ if obj:
+ return self.readonly_fields + ('evaluateur', 'note', 'commentaire')
+ return self.readonly_fields
+
+
+class CandidatAdminMixin(BaseAdmin):
+ search_fields = ('nom', 'prenom')
+ exclude = ('actif', )
+ list_editable = ('statut', )
+ list_display = ('_candidat', 'offre_emploi',
+ 'voir_offre_emploi', 'calculer_moyenne',
+ 'afficher_candidat', '_date_creation', 'statut', )
+ list_filter = ('offre_emploi', 'offre_emploi__region', 'statut', )
+
+ fieldsets = (
+ ("Offre d'emploi", {
+ 'fields': ('offre_emploi', )
+ }),
+ ('Informations personnelles', {
+ 'fields': (
+ 'prenom', 'nom', 'genre', 'nationalite',
+ 'situation_famille', 'nombre_dependant'
+ )
+ }),
+ ('Coordonnées', {
+ 'fields': (
+ 'telephone', 'email', 'adresse', 'ville', 'etat_province',
+ 'code_postal', 'pays'
+ )
+ }),
+ ('Informations professionnelles', {
+ 'fields': (
+ 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
+ 'domaine_professionnel'
+ )
+ }),
+ ('Traitement', {
+ 'fields': ('statut', )
+ }),
+ )
+ inlines = [
+ CandidatPieceInline,
+ CandidatEvaluationInline,
+ ]
+ actions = ['envoyer_courriel_candidats']
+
+ def _candidat(self, obj):
+ txt = u"%s %s (%s)" % (obj.nom.upper(), obj.prenom, obj.genre)
+ txt = textwrap.wrap(txt, 30)
+ return "<br/>".join(txt)
+ _candidat.short_description = "Candidat"
+ _candidat.admin_order_field = "nom"
+ _candidat.allow_tags = True
+
+ def _date_creation(self, obj):
+ return obj.date_creation
+ _date_creation.admin_order_field = "date_creation"
+ _date_creation.short_description = "Date de réception"
+
+ ### Actions à afficher
+ def get_actions(self, request):
+ actions = super(CandidatAdminMixin, self).get_actions(request)
+ del actions['delete_selected']
+ return actions
+
+ ### Envoyer un courriel à des candidats
+ def envoyer_courriel_candidats(modeladmin, obj, candidats):
+ selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
+
+ return HttpResponseRedirect(
+ reverse('selectionner_template') + "?ids=%s" % (",".join(selected))
+ )
+ envoyer_courriel_candidats.short_description = u'Envoyer courriel'
+
+ ### Évaluer un candidat
+ def evaluer_candidat(self, obj):
+ return "<a href='%s?candidat__id__exact=%s'>" \
+ "Évaluer le candidat</a>" % (
+ reverse('admin:recrutement_candidatevaluation_changelist'),
+ obj.id
+ )
+ evaluer_candidat.allow_tags = True
+ evaluer_candidat.short_description = 'Évaluation'
+
+ ### Afficher un candidat
+ def afficher_candidat(self, obj):
+ items = [u"<li><a href='%s%s'>%s</li>" % \
+ (settings.OE_PRIVE_MEDIA_URL, pj.path, pj.get_nom_display()) \
+ for pj in obj.pieces_jointes()]
+ html = "<a href='%s'>Voir le candidat</a>" % (
+ reverse('admin:recrutement_proxycandidat_change', args=(obj.id,))
+ )
+ return "%s<ul>%s</ul>" % (html, "\n".join(items))
+ afficher_candidat.allow_tags = True
+ afficher_candidat.short_description = u'Détails du candidat'
+
+ ### Voir l'offre d'emploi
+ def voir_offre_emploi(self, obj):
+ return "<a href='%s'>Voir l'offre d'emploi</a>" % (reverse(
+ 'admin:recrutement_proxyoffreemploi_change',
+ args=(obj.offre_emploi.id,)
+ ))
+ voir_offre_emploi.allow_tags = True
+ voir_offre_emploi.short_description = "Afficher l'offre d'emploi"
+
+ ### Calculer la moyenne des notes
+ def calculer_moyenne(self, obj):
+ evaluations = CandidatEvaluation.objects.filter(candidat=obj)
+
+ notes = [evaluation.note for evaluation in evaluations \
+ if evaluation.note is not None]
+
+ if len(notes) > 0:
+ moyenne_votes = round(float(sum(notes)) / len(notes), 2)
+ else:
+ moyenne_votes = "Non disponible"
+
+ totales = len(evaluations)
+ faites = len(notes)
+
+ if obj.statut == 'REC':
+ if totales == faites:
+ color = "green"
+ elif faites > 0 and float(totales) / float(faites) >= 2:
+ color = "orange"
+ else:
+ color = "red"
+ else:
+ color = "black"
+
+ return """<span style="color: %s;">%s (%s/%s)</span>""" % (
+ color, moyenne_votes, faites, totales
+ )
+ calculer_moyenne.allow_tags = True
+ calculer_moyenne.short_description = "Moyenne"
+ calculer_moyenne.admin_order_field = ""
+
+ ### Permissions add, delete, change
+ def has_add_permission(self, request):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+ def get_changelist(self, request, **kwargs):
+ return OrderedChangeList
+
+ def queryset(self, request):
+ """
+ Spécifie un queryset limité, autrement Django exécute un
+ select_related() sans paramètre, ce qui a pour effet de charger tous
+ les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
+ modèles de Region, il existe plusieurs boucles, ce qui conduit à la
+ génération d'une requête infinie.
+ """
+ qs = self.model._default_manager.get_query_set() \
+ .select_related('offre_emploi') \
+ .annotate(moyenne=Avg('evaluations__note'))
+
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return qs
+
+ if groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes:
+ employe = groups.get_employe_from_user(request.user)
+ return qs.filter(offre_emploi__region=employe.implantation.region)
+
+ if Evaluateur.objects.filter(user=request.user).exists():
+ evaluateur = Evaluateur.objects.get(user=request.user)
+ candidat_ids = [e.candidat.id for e in
+ CandidatEvaluation.objects.filter(evaluateur=evaluateur)]
+ return qs.filter(id__in=candidat_ids)
+ return qs.none()
+
+
+class CandidatAdmin(VersionAdmin, CandidatAdminMixin):
+ pass
+
+
+class ProxyCandidatAdmin(CandidatAdminMixin):
+ list_editable = ()
+ readonly_fields = (
+ 'statut', 'offre_emploi', 'prenom', 'nom', 'genre', 'nationalite',
+ 'situation_famille', 'nombre_dependant', 'telephone', 'email',
+ 'adresse', 'ville', 'etat_province', 'code_postal', 'pays',
+ 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
+ 'domaine_professionnel', 'pieces_jointes'
+ )
+ fieldsets = (
+ ("Offre d'emploi", {
+ 'fields': ('offre_emploi', )
+ }),
+ ('Informations personnelles', {
+ 'fields': (
+ 'prenom', 'nom', 'genre', 'nationalite', 'situation_famille',
+ 'nombre_dependant'
+ )
+ }),
+ ('Coordonnées', {
+ 'fields': (
+ 'telephone', 'email', 'adresse', 'ville', 'etat_province',
+ 'code_postal', 'pays'
+ )
+ }),
+ ('Informations professionnelles', {
+ 'fields': (
+ 'niveau_diplome', 'employeur_actuel', 'poste_actuel',
+ 'domaine_professionnel'
+ )
+ }),
+ )
+ inlines = (CandidatEvaluationInline, )
+
+ def has_add_permission(self, request):
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+
+ if obj is not None:
+ evaluateur = Evaluateur.objects.get(user=request.user)
+ for e in obj.evaluations.all():
+ if e.evaluateur == evaluateur:
+ return True
+
+ return False
+
+ def get_actions(self, request):
+ return None
+
+
+class CandidatPieceAdmin(admin.ModelAdmin):
+ list_display = ('nom', 'candidat', )
+
+ ### Queryset
+ def queryset(self, request):
+ """
+ Spécifie un queryset limité, autrement Django exécute un
+ select_related() sans paramètre, ce qui a pour effet de charger tous
+ les objets FK, sans limite de profondeur. Dès qu'on arrive, dans les
+ modèles de Region, il existe plusieurs boucles, ce qui conduit à la
+ génération d'une requête infinie. Affiche la liste de candidats que
+ si le user connecté possède un Evaluateur
+ """
+ qs = self.model._default_manager.get_query_set()
+ return qs.select_related('candidat')
+
+
+class EvaluateurAdmin(BaseAdmin, VersionAdmin):
+ fieldsets = (
+ ("Utilisateur", {
+ 'fields': ('user',)
+ }),
+ )
+
+ ### Actions à afficher
+ def get_actions(self, request):
+ actions = super(EvaluateurAdmin, self).get_actions(request)
+ del actions['delete_selected']
+ return actions
+
+ ### Permissions add, delete, change
+ def has_add_permission(self, request):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ user_groupes = [g.name for g in request.user.groups.all()]
+ if request.user.is_superuser is True or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return True
+ return False
+
+
+class CandidatEvaluationAdmin(BaseAdmin):
+ search_fields = ('candidat__nom', 'candidat__prenom')
+ list_display = (
+ '_candidat', '_statut', '_offre_emploi', 'evaluateur', '_note',
+ '_commentaire'
+ )
+ readonly_fields = ('candidat', 'evaluateur')
+ list_filter = ('candidat__statut', 'candidat__offre_emploi',)
+ fieldsets = (
+ ('Évaluation du candidat', {
+ 'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )
+ }),
+ )
+
+ def get_actions(self, request):
+ # on stocke l'evaluateur connecté (pas forcément la meilleure place...)
+ try:
+ self.evaluateur = Evaluateur.objects.get(user=request.user)
+ except:
+ self.evaluateur = None
+
+ actions = super(CandidatEvaluationAdmin, self).get_actions(request)
+ del actions['delete_selected']
+ return actions
+
+ ### Afficher la note
+ def _note(self, obj):
+ """
+ Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
+ un lien pour Évaluer le candidat.
+ Sinon afficher la note.
+ """
+ page = self.model.__name__.lower()
+ redirect_url = 'admin:recrutement_%s_change' % page
+
+ if obj.note is None:
+ label = "Candidat non évalué"
+ else:
+ label = obj.note
+
+ if self.evaluateur == obj.evaluateur:
+ return "<a href='%s'>%s</a>" % (
+ reverse(redirect_url, args=(obj.id,)), label
+ )
+ else:
+ return label
+ _note.allow_tags = True
+ _note.short_description = "Note"
+ _note.admin_order_field = 'note'
+
+ def _statut(self, obj):
+ return obj.candidat.get_statut_display()
+ _statut.order_field = 'candidat__statut'
+ _statut.short_description = 'Statut'
+
+ ### Lien en lecture seule vers le candidat
+ def _candidat(self, obj):
+ return "<a href='%s'>%s</a>" \
+ % (reverse('admin:recrutement_proxycandidat_change',
+ args=(obj.candidat.id,)), obj.candidat)
+ _candidat.allow_tags = True
+ _candidat.short_description = 'Candidat'
+
+ ### Afficher commentaire
+ def _commentaire(self, obj):
+ """
+ Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
+ dans le champ commentaire, Aucun au lieu de (None)
+ Sinon afficher la note.
+ """
+ if obj.commentaire is None:
+ return "Aucun"
+ return obj.commentaire
+ _commentaire.allow_tags = True
+ _commentaire.short_description = "Commentaire"
+
+ ### Afficher offre d'emploi
+ def _offre_emploi(self, obj):
+ return "<a href='%s'>%s</a>" % \
+ (reverse('admin:recrutement_proxyoffreemploi_change',
+ args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
+ _offre_emploi.allow_tags = True
+ _offre_emploi.short_description = "Voir offre d'emploi"
+
+ def has_add_permission(self, request):
+ return False
+
+ def has_delete_permission(self, request, obj=None):
+ return False
+
+ def has_change_permission(self, request, obj=None):
+ """
+ Permettre la visualisation dans la changelist
+ mais interdire l'accès à modifier l'objet si l'évaluateur n'est pas
+ le request.user
+ """
+ user_groupes = [g.name for g in request.user.groups.all()]
+
+ if request.user.is_superuser or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ is_recrutement = True
+ else:
+ is_recrutement = False
+
+ return is_recrutement
+
+ def queryset(self, request):
+ """
+ Afficher uniquement les évaluations de l'évaluateur, sauf si
+ l'utilisateur est dans les groupes suivants.
+ """
+ qs = self.model._default_manager.get_query_set() \
+ .select_related('offre_emploi')
+ user_groupes = request.user.groups.all()
+ user_groupes = [g.name for g in request.user.groups.all()]
+
+ if request.user.is_superuser or \
+ groups.CORRESPONDANT_RH in user_groupes or \
+ groups.DRH_NIVEAU_1 in user_groupes or \
+ groups.DRH_NIVEAU_2 in user_groupes or \
+ groups.DIRECTEUR_DE_BUREAU in user_groupes or \
+ groups.ADMINISTRATEURS in user_groupes or \
+ groups.HAUTE_DIRECTION in user_groupes:
+ return qs
+
+ evaluateur = Evaluateur.objects.get(user=request.user)
+ candidats_evaluations = \
+ CandidatEvaluation.objects.filter(evaluateur=evaluateur,
+ candidat__statut__in=('REC', ))
+ candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
+ return qs.filter(id__in=candidats_evaluations_ids)
+
+
+class MesCandidatEvaluationAdmin(CandidatEvaluationAdmin):
+
+ def has_change_permission(self, request, obj=None):
+ try:
+ Evaluateur.objects.get(user=request.user)
+ is_evaluateur = True
+ except:
+ is_evaluateur = False
+
+ if obj is None and is_evaluateur:
+ return True
+
+ try:
+ return request.user == obj.evaluateur.user
+ except:
+ return False
+
+ def queryset(self, request):
+ qs = self.model._default_manager.get_query_set() \
+ .select_related('offre_emploi')
+ evaluateur = Evaluateur.objects.get(user=request.user)
+ candidats_evaluations = \
+ CandidatEvaluation.objects.filter(evaluateur=evaluateur,
+ candidat__statut__in=('REC', ))
+ candidats_evaluations_ids = [ce.id for ce in candidats_evaluations]
+ return qs.filter(id__in=candidats_evaluations_ids)
+
+
+class CourrielTemplateAdmin(BaseAdmin, VersionAdmin):
+ ### Actions à afficher
+ def get_actions(self, request):
+ actions = super(CourrielTemplateAdmin, self).get_actions(request)
+ del actions['delete_selected']
+ return actions
+
+admin.site.register(OffreEmploi, OffreEmploiAdmin)
+admin.site.register(ProxyOffreEmploi, ProxyOffreEmploiAdmin)
+admin.site.register(Candidat, CandidatAdmin)
+admin.site.register(ProxyCandidat, ProxyCandidatAdmin)
+admin.site.register(CandidatEvaluation, CandidatEvaluationAdmin)
+admin.site.register(MesCandidatEvaluation, MesCandidatEvaluationAdmin)
+admin.site.register(Evaluateur, EvaluateurAdmin)
+admin.site.register(CourrielTemplate, CourrielTemplateAdmin)
--- /dev/null
+
+# -*- coding: utf-8 -*-
+
+from django.core.urlresolvers import reverse
+from project.recrutement.test.common import RecrutementTest
+
+class CandidatAddTest(RecrutementTest):
+ """
+ Test l'ajout d'un candidat
+ """
+ url = reverse('admin:emploi_candidat_add')
+
+ def test_anonyme(self):
+ """
+ Un anonyme ne peut pas ajouter un candidat
+ """
+ self._test_anonyme()
+ self._test_acces_ko(self.url)
+
+ def test_correspondant_rh(self):
+ """
+ Un correspondant RH peut ajouter un candidat
+ """
+ self._test_correspondant_rh()
+ self._test_acces_ok(self.url)
+
+ def test_administrateur_regional(self):
+ """
+ Un administrateur peut ajouter un candidat
+ """
+ self._test_administrateur_regional()
+ self._test_acces_ok(self.url)
+
+ def test_directeur_bureau(self):
+ """
+ Un directeur de bureau peut ajouter un candidat
+ """
+ self._test_directeur_bureau()
+ self._test_acces_ok(self.url)
+
+ def test_drh(self):
+ """
+ Un DRH peut ajouter un candidat
+ """
+ self._test_drh()
+ self._test_acces_ok(self.url)
+
+ def test_drh2(self):
+ """
+ Un DRH (2ieme niveau) peut ajouter un candidat
+ """
+ self._test_drh2()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_accior(self):
+ """
+ Un membre de l'ACCIOR ne peut ajouter un candidat
+ """
+ self._test_grp_accior()
+ self._test_acces_ko(self.url)
+
+ def _test_grp_abf(self):
+ """
+ Un membre de l'ABF ne peut ajouter un candidat
+ """
+ self._test_grp_abf(self)
+ self._test_acces_ko(self.url)
+
+ def _test_grp_haute_direction(self):
+ """
+ Un membre de la haute direction peut ajouter un candidat
+ """
+ self._test_grp_haute_direction()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_service_utilisateurs(self):
+ """
+ Un membre du groupe service utilisateur ne peut ajouter un candidat
+ """
+ self._test_grp_service_utilisateurs()
+ self._test_acces_ko(self.url)
+
+
+class CandidatDeleteTest(RecrutementTest):
+ """
+ Test la suppression d'un candidat
+ """
+
+ def setUp(self):
+ super(CandidatDeleteTest, self).setUp()
+ self.url = reverse('admin:emploi_candidat_delete',
+ args=[self.candidat_cnf_ngaoundere.id])
+
+ def test_anonyme(self):
+ """
+ Un anonyme ne peut pas supprimer un candidat
+ """
+ self._test_anonyme()
+ self._test_acces_ko(self.url)
+
+ def test_correspondant_rh(self):
+ """
+ Un correspondant RH peut supprimer un candidat
+ """
+ self._test_correspondant_rh()
+ self._test_acces_ok(self.url)
+
+ def test_administrateur_regional(self):
+ """
+ Un administrateur peut supprimer un candidat
+ """
+ self._test_administrateur_regional()
+ self._test_acces_ok(self.url)
+ self._test_directeur_bureau(email="2@test.auf")
+ self._test_acces_ko(self.url)
+
+ def test_directeur_bureau(self):
+ """
+ Un directeur de bureau peut supprimer un candidat
+ """
+ self._test_directeur_bureau()
+ self._test_acces_ok(self.url)
+ self._test_directeur_bureau(email="2@test.auf")
+ self._test_acces_ko(self.url)
+
+ def test_drh(self):
+ """
+ Un DRH peut supprimer un candidat
+ """
+ self._test_drh()
+ self._test_acces_ok(self.url)
+
+ def test_drh2(self):
+ """
+ Un DRH (2ieme niveau) peut supprimer un candidat
+ """
+ self._test_drh2()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_accior(self):
+ """
+ Un membre de l'ACCIOR ne peut supprimer un candidat
+ """
+ self._test_grp_accior()
+ self._test_acces_ko(self.url)
+
+ def _test_grp_abf(self):
+ """
+ Un membre de l'ABF ne peut supprimer un candidat
+ """
+ self._test_grp_abf(self)
+ self._test_acces_ko(self.url)
+
+ def _test_grp_haute_direction(self):
+ """
+ Un membre de la haute direction peut supprimer un candidat
+ """
+ self._test_grp_haute_direction()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_service_utilisateurs(self):
+ """
+ Un membre du groupe service utilisateur ne peut supprimer un candidat
+ """
+ self._test_grp_service_utilisateurs()
+ self._test_acces_ko(self.url)
+
+
+class CandidatChangeTest(RecrutementTest):
+ """
+ Test la modification d'un candidat
+ """
+
+ def setUp(self):
+ super(CandidatChangeTest, self).setUp()
+ self.url = reverse('admin:emploi_candidat_change',
+ args=[self.candidat_cnf_ngaoundere.id])
+
+ def test_anonyme(self):
+ """
+ Un anonyme ne peut pas modifier un candidat
+ """
+ self._test_anonyme()
+ self._test_acces_ko(self.url)
+
+ def test_correspondant_rh(self):
+ """
+ Un correspondant RH peut modifier un candidat
+ """
+ self._test_correspondant_rh()
+ self._test_acces_ok(self.url)
+
+ def test_administrateur_regional(self):
+ """
+ Un administrateur peut modifier un candidat
+ """
+ self._test_administrateur_regional()
+ self._test_acces_ok(self.url)
+ self._test_administrateur_regional(email="2@test.auf")
+ self._test_acces_ko(self.url)
+
+ def test_directeur_bureau(self):
+ """
+ Un directeur de bureau peut modifier un candidat
+ """
+ self._test_directeur_bureau()
+ self._test_acces_ok(self.url)
+ self._test_directeur_bureau(email="2@test.auf")
+ self._test_acces_ko(self.url)
+
+ def test_drh(self):
+ """
+ Un DRH peut modifier un candidat
+ """
+ self._test_drh()
+ self._test_acces_ok(self.url)
+
+ def test_drh2(self):
+ """
+ Un DRH (2ieme niveau) peut modifier un candidat
+ """
+ self._test_drh2()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_accior(self):
+ """
+ Un membre de l'ACCIOR ne peut modifier un candidat
+ """
+ self._test_grp_accior()
+ self._test_acces_ko(self.url)
+
+ def _test_grp_abf(self):
+ """
+ Un membre de l'ABF ne peut modifier un candidat
+ """
+ self._test_grp_abf(self)
+ self._test_acces_ko(self.url)
+
+ def _test_grp_haute_direction(self):
+ """
+ Un membre de la haute direction peut modifier un candidat
+ """
+ self._test_grp_haute_direction()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_service_utilisateurs(self):
+ """
+ Un membre du groupe service utilisateur ne peut modifier un candidat
+ """
+ self._test_grp_service_utilisateurs()
+ self._test_acces_ko(self.url)
+
+
+class CandidatChangeListTest(RecrutementTest):
+ """
+ Test l'acces à la liste des candidats
+ """
+
+ def setUp(self):
+ super(CandidatChangeListTest, self).setUp()
+ self.url = reverse('admin:emploi_candidat_changelist')
+
+ def test_anonyme(self):
+ """
+ Un anonyme ne peut pas lister les candidats
+ """
+ self._test_anonyme()
+ self._test_acces_ko(self.url)
+
+ def test_correspondant_rh(self):
+ """
+ Un correspondant RH peut lister les candidats
+ """
+ self._test_correspondant_rh()
+ self._test_acces_ok(self.url)
+
+ def test_administrateur_regional(self):
+ """
+ Un administrateur peut lister les candidats
+ """
+ self._test_administrateur_regional()
+ self._test_acces_ok(self.url)
+ qs = self.client.get(self.url).context['cl'].query_set
+ self.assertEqual(len(qs), 1)
+ self._test_administrateur_regional(email="2@test.auf")
+ self._test_acces_ok(self.url)
+ qs = self.client.get(self.url).context['cl'].query_set
+ self.assertEqual(len(qs), 0)
+
+ def test_directeur_bureau(self):
+ """
+ Un directeur de bureau peut lister les candidats
+ """
+ self._test_directeur_bureau()
+ self._test_acces_ok(self.url)
+ qs = self.client.get(self.url).context['cl'].query_set
+ self.assertEqual(len(qs), 1)
+ self._test_directeur_bureau(email="2@test.auf")
+ self._test_acces_ok(self.url)
+ qs = self.client.get(self.url).context['cl'].query_set
+ self.assertEqual(len(qs), 0)
+
+ def test_drh(self):
+ """
+ Un DRH peut lister les candidats
+ """
+ self._test_drh()
+ self._test_acces_ok(self.url)
+
+ def test_drh2(self):
+ """
+ Un DRH (2ieme niveau) peut lister les candidats
+ """
+ self._test_drh2()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_accior(self):
+ """
+ Un membre de l'ACCIOR ne peut lister les candidats
+ """
+ self._test_grp_accior()
+ self._test_acces_ko(self.url)
+
+ def _test_grp_abf(self):
+ """
+ Un membre de l'ABF ne peut lister les candidats
+ """
+ self._test_grp_abf(self)
+ self._test_acces_ko(self.url)
+
+ def _test_grp_haute_direction(self):
+ """
+ Un membre de la haute direction peut lister les candidats
+ """
+ self._test_grp_haute_direction()
+ self._test_acces_ok(self.url)
+
+ def _test_grp_service_utilisateurs(self):
+ """
+ Un membre du groupe service utilisateur ne peut lister les candidats
+ """
+ self._test_grp_service_utilisateurs()
+ self._test_acces_ko(self.url)