From: Olivier Larchevêque Date: Thu, 26 Jul 2012 19:54:40 +0000 (-0400) Subject: fix candidat X-Git-Tag: 1.6.5~27 X-Git-Url: http://git.auf.org/?p=auf_rh_dae.git;a=commitdiff_plain;h=8fb8868b7cb564df85337bf99c3acb25c385c66f;hp=83a22aff19f243e8aad0a3c88da7517dc6911524 fix candidat --- diff --git a/project/recrutement/admin.py b/project/recrutement/admin.py index 8db7f58..4bc35f3 100644 --- a/project/recrutement/admin.py +++ b/project/recrutement/admin.py @@ -389,7 +389,7 @@ class CandidatAdminMixin(BaseAdmin): ### Actions à afficher def get_actions(self, request): - actions = super(CandidatAdmin, self).get_actions(request) + actions = super(CandidatAdminMixin, self).get_actions(request) del actions['delete_selected'] return actions diff --git a/project/recrutement/admin.py_ b/project/recrutement/admin.py_ new file mode 100644 index 0000000..4bc35f3 --- /dev/null +++ b/project/recrutement/admin.py_ @@ -0,0 +1,846 @@ +# -*- 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 "Voir les candidats \ + " % (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 "
".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 "" \ + "Évaluer le candidat" % ( + 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"
  • %s
  • " % \ + (settings.OE_PRIVE_MEDIA_URL, pj.path, pj.get_nom_display()) \ + for pj in obj.pieces_jointes()] + html = "Voir le candidat" % ( + reverse('admin:recrutement_proxycandidat_change', args=(obj.id,)) + ) + return "%s" % (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 "Voir l'offre d'emploi" % (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 """%s (%s/%s)""" % ( + 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 "%s" % ( + 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 "%s" \ + % (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 "%s" % \ + (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) diff --git a/project/recrutement/test/candidat.py b/project/recrutement/test/candidat.py new file mode 100644 index 0000000..4c46036 --- /dev/null +++ b/project/recrutement/test/candidat.py @@ -0,0 +1,343 @@ + +# -*- 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) diff --git a/project/recrutement/test/common.py b/project/recrutement/test/common.py index 06f8053..c4b3887 100644 --- a/project/recrutement/test/common.py +++ b/project/recrutement/test/common.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- from auf.django.references import models as ref -from auf.django.emploi.models import OffreEmploi +from auf.django.emploi.models import OffreEmploi, Candidat from project.rh.test.common import RhTest class RecrutementTest(RhTest): @@ -11,6 +11,10 @@ class RecrutementTest(RhTest): OFFRE_EMPLOIS ============= self.offre_cnf_ngaoundere + + CANDIDATS + ========= + self.canditat_cnf_ngaoundere """ super(RecrutementTest, self).setUp() @@ -23,3 +27,14 @@ class RecrutementTest(RhTest): ) self.offre_cnf_ngaoundere.save() + self.candidat_cnf_ngaoundere = Candidat( + prenom="olivier", + nom="larchevêque", + genre='M', + nationalite=ref.Pays.objects.get(code='FR'), + pays=ref.Pays.objects.get(code='CA'), + offre_emploi=self.offre_cnf_ngaoundere, + nombre_dependant=3, + ) + self.candidat_cnf_ngaoundere.save() + diff --git a/project/recrutement/tests.py b/project/recrutement/tests.py index c9bcec4..0877a46 100644 --- a/project/recrutement/tests.py +++ b/project/recrutement/tests.py @@ -1,7 +1,2 @@ from project.recrutement.test.offre_emploi import * -#from project.recrutement.test.offre_emploi_visualisation import * -#from project.recrutement.test.candidat import * -#from project.recrutement.test.candidat_visualisation import * -#from project.recrutement.test.candidatevaluation import * -#from project.recrutement.test.evaluateur import * -#from project.recrutement.test.modele_courriel import * +from project.recrutement.test.candidat import *