greffe recrutement en PROD
authorOlivier Larchevêque <olivier.larcheveque@auf.org>
Mon, 13 Feb 2012 20:12:02 +0000 (15:12 -0500)
committerOlivier Larchevêque <olivier.larcheveque@auf.org>
Mon, 13 Feb 2012 20:12:02 +0000 (15:12 -0500)
28 files changed:
.gitignore
buildout.cfg
project/recrutement/__init__.py [new file with mode: 0644]
project/recrutement/admin.py [new file with mode: 0644]
project/recrutement/api.py [new file with mode: 0644]
project/recrutement/context_processors.py [new file with mode: 0644]
project/recrutement/forms.py [new file with mode: 0644]
project/recrutement/migrations/0001_initial.py [new file with mode: 0644]
project/recrutement/migrations/0002_tpl_courriel.py [new file with mode: 0644]
project/recrutement/migrations/__init__.py [new file with mode: 0644]
project/recrutement/models.py [new file with mode: 0644]
project/recrutement/permissions.py [new file with mode: 0644]
project/recrutement/templates/admin/recrutement/candidat/change_form.html [new file with mode: 0644]
project/recrutement/templates/admin/recrutement/proxycandidat/change_form.html [new file with mode: 0644]
project/recrutement/templates/admin/recrutement/proxyoffreemploi/change_form.html [new file with mode: 0644]
project/recrutement/templates/recrutement/affecter_evaluateurs.html [new file with mode: 0644]
project/recrutement/templates/recrutement/candidat_pdf.html [new file with mode: 0644]
project/recrutement/templates/recrutement/envoyer_courriel_candidats.html [new file with mode: 0644]
project/recrutement/templates/recrutement/index.html [new file with mode: 0644]
project/recrutement/templates/recrutement/pieces.html [new file with mode: 0644]
project/recrutement/templates/recrutement/postuler_appel_offre.html [new file with mode: 0644]
project/recrutement/templates/recrutement/selectionner_template.html [new file with mode: 0644]
project/recrutement/tests.py [new file with mode: 0644]
project/recrutement/urls.py [new file with mode: 0644]
project/recrutement/views.py [new file with mode: 0644]
project/recrutement/workflow.py [new file with mode: 0644]
project/settings.py
project/urls.py

index 51ce3bd..0e0311b 100644 (file)
@@ -32,3 +32,4 @@ tmp
 
 # extra
 project/media_prive/*
+project/static
index 1454588..d68b28a 100644 (file)
@@ -11,22 +11,27 @@ find-links = http://pypi.auf.org/simple/auf.recipe.django/
     http://pypi.auf.org/simple/auf.django.admingroup/
     http://pypi.auf.org/simple/auf.django.permissions/
     http://pypi.auf.org/simple/auf.django.references/
+    http://pypi.auf.org/simple/auf.django.emploi/
 
 eggs =
     django
     south
     django-admin-tools
+    django-tinymce
     auf.django.skin
     auf.django.workflow
     auf.django.admingroup
     auf.django.auth
     auf.django.permissions
     auf.django.references
+    auf.django.emploi
     auf.recipe.django
     django-reversion
     simplejson
     django-ajax-selects
     django-sendfile
+    django-form-utils
+    django-simple-captcha
 
 [versions]
 django-admin-tools = 0.4.0
@@ -37,6 +42,7 @@ auf.django.permissions = 0.1
 auf.django.references = 0.4
 auf.django.skin = 0.17dev
 auf.django.workflow = 0.14dev
+auf.django.emploi = 0.6dev
 auf.recipe.django = 0.20dev
 django-reversion = 1.3.3
 django-ajax-selects = 1.1.4
diff --git a/project/recrutement/__init__.py b/project/recrutement/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/recrutement/admin.py b/project/recrutement/admin.py
new file mode 100644 (file)
index 0000000..83018c9
--- /dev/null
@@ -0,0 +1,639 @@
+# -*- encoding: utf-8 -*-
+
+from django.core.urlresolvers import reverse
+from django.http import HttpResponseRedirect
+from django.contrib import admin
+from django.forms.models import BaseInlineFormSet
+
+from reversion.admin import VersionAdmin
+from datamaster_modeles.models import Region, Bureau
+from project.rh import models as rh
+
+from project.dae.utils import get_employe_from_user as get_emp
+from recrutement.models import *
+from recrutement.workflow import grp_evaluateurs_recrutement, \
+                    grp_drh_recrutement, grp_directeurs_bureau_recrutement, \
+                    grp_administrateurs_recrutement
+from recrutement.forms import *
+
+### CONSTANTES
+IMPLANTATIONS_CENTRALES = [15, 19]
+
+class ProxyEvaluateur(Evaluateur.offres_emploi.through):
+    """
+    Ce proxy sert uniquement dans l'admin à disposer d'un libellé
+    plus ergonomique.
+    """
+    class Meta:
+        proxy = True
+        verbose_name = "évaluateur"
+
+class EvaluateurInline(admin.TabularInline):
+    model = ProxyEvaluateur
+    fields = ('evaluateur',)
+    extra = 1
+
+class OffreEmploiAdmin(VersionAdmin):
+    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
+    inlines = [EvaluateurInline, ]
+
+    ### Actions à afficher
+    def get_actions(self, request):
+        actions = super(OffreEmploiAdmin, 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_candidat_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(OffreEmploiAdmin, self).get_form(request, obj, **kwargs)
+        employe = get_emp(request.user)
+        user_groupes = request.user.groups.all()
+        
+    
+        # Region
+        if form.declared_fields.has_key('region'):
+            region_field = form.declared_fields['region']
+        else:
+            region_field = form.base_fields['region']
+
+        if grp_drh_recrutement in user_groupes:
+            region_field.queryset = Region.objects.all()
+        else:
+            region_field.queryset = Region.objects.\
+                                    filter(id=employe.implantation.region.id)
+        
+        # Poste
+        if form.declared_fields.has_key('poste'):
+            poste_field = form.declared_fields['poste']
+        else:
+            poste_field = form.base_fields['poste']
+
+        if grp_drh_recrutement 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 form.declared_fields.has_key('bureau'):
+            bureau_field = form.declared_fields['bureau']
+        else:
+            bureau_field = form.base_fields['bureau']
+
+        if grp_drh_recrutement 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()
+        user_groupes = request.user.groups.all()
+        if grp_drh_recrutement in user_groupes:
+            return qs.select_related('offre_emploi')
+
+        if grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            employe = get_emp(request.user)
+            return qs.select_related('offre_emploi').\
+                                    filter(region=employe.implantation.region)
+
+        if grp_evaluateurs_recrutement in user_groupes:
+            try:
+                user = Evaluateur.objects.get(user=request.user)               
+            except Evaluateur.DoesNotExist:       
+                return qs.none()       
+                     
+            ids = [o.id for o in user.offres_emploi.all()]
+            return qs.select_related('offre_emploi').filter(id__in=ids)            
+        return qs.none()
+
+    ### Permission add, delete, change
+    def has_add_permission(self, request):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False  
+
+    def has_delete_permission(self, request, obj=None):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+    def has_change_permission(self, request, obj=None):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
+    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 = []
+
+    ### Actions à afficher
+    def get_actions(self, request):
+        actions = super(ProxyOffreEmploiAdmin, self).get_actions(request)
+        del actions['affecter_evaluateurs_offre_emploi']
+        return actions
+
+    ### Lieu de redirection après le change 
+    def response_change(self, request, obj):
+        response = super(ProxyOffreEmploiAdmin,self).response_change(request,obj)
+        return HttpResponseRedirect(reverse\
+                            ('admin:recrutement_proxyoffreemploi_changelist'))
+
+    ### Formulaire
+    def get_form(self, request, obj=None, **kwargs):
+        form = super(OffreEmploiAdmin, 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 = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_evaluateurs_recrutement in user_groupes or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+class CandidatPieceInline(admin.TabularInline):
+    model = CandidatPiece
+    fields = ('candidat', 'nom', 'path',)
+    extra = 1
+    max_num = 3
+
+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 CandidatAdmin(VersionAdmin):
+    exclude = ('actif', )
+    date_hierarchy = 'date_creation'
+    list_display = ('nom', 'prenom', 'offre_emploi','statut',
+                    'voir_offre_emploi', 'calculer_moyenne', 
+                    'afficher_candidat',)
+    list_filter = ('offre_emploi', )
+
+    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']
+
+    ### Actions à afficher
+    def get_actions(self, request):
+        actions = super(CandidatAdmin, 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):
+        return "<a href='%s'>Voir le candidat</a>" % \
+            (reverse('admin:recrutement_proxycandidat_change', args=(obj.id,)))
+    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)
+        offre_emploi = obj.offre_emploi
+
+        notes = [evaluation.note for evaluation in evaluations.all() \
+                    if evaluation.note is not None]
+        if len(notes) > 0:
+            moyenne_votes = float(sum(notes)) / len(notes)
+        else:
+            moyenne_votes = "Non disponible"
+        return moyenne_votes
+    calculer_moyenne.allow_tags = True
+    calculer_moyenne.short_description = "Moyenne des notes"
+
+    ### Permissions add, delete, change
+    def has_add_permission(self, request):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+    def has_delete_permission(self, request, obj=None):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+    def has_change_permission(self, request, obj=None):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+    ### 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.
+        
+        """
+        qs = self.model._default_manager.get_query_set()      
+        user_groupes = request.user.groups.all()
+        if grp_drh_recrutement in user_groupes:
+            return qs.select_related('candidats')   
+
+        if grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            employe = get_emp(request.user)
+            return qs.select_related('candidats').\
+                        filter(offre_emploi__region=employe.implantation.region)            
+
+        if grp_evaluateurs_recrutement in user_groupes:
+            try:
+                user = Evaluateur.objects.get(user=request.user)
+            except Evaluateur.DoesNotExist:       
+                return qs.none()   
+
+            offres_emploi = [o for o in user.offres_emploi.all()]
+            candidat_ids = [o.candidat.id for o in offres_emploi]
+            return qs.select_related('candidats').filter(id__in=candidat_ids)            
+        return qs.none()    
+
+
+class ProxyCandidatAdmin(CandidatAdmin):
+    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 = ()
+
+    ### Lieu de redirection après le change
+    def response_change(self, request, obj):
+        response = super(ProxyCandidatAdmin, self).response_change(request, obj)
+        user_groupes = request.user.groups.all()
+        return HttpResponseRedirect(reverse\
+                                ('admin:recrutement_proxycandidat_changelist'))
+
+    ### 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 = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_evaluateurs_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+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(VersionAdmin):
+    fieldsets = (
+        ("Utilisateur", {
+            'fields': ('user',)
+        }),
+        ("Offres d'emploi à évaluer", {
+            'fields': ('offres_emploi',)
+        }),
+    )
+
+    ### 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 = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+    def has_delete_permission(self, request, obj=None):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+           grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+    def has_change_permission(self, request, obj=None):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return True
+        return False   
+
+class CandidatEvaluationAdmin(VersionAdmin):
+    list_display = ('_candidat', '_offre_emploi', 'evaluateur', '_note', 
+                    '_commentaire', )
+    _readonly_fields = ('candidat', 'evaluateur') # voir fonctions de permissions
+    fieldsets = (
+        ('Évaluation du candidat', {
+            'fields': ('candidat', 'evaluateur', 'note', 'commentaire', )        
+        }),
+    )
+
+    ### Actions à afficher
+    def get_actions(self, request):
+        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.
+        """
+        if obj.note is None:
+            return "<a href='%s'>Candidat non évalué</a>" % \
+                (reverse('admin:recrutement_candidatevaluation_change', 
+                args=(obj.id,)))
+        return "<a href='%s'>%s</a>" % \
+            (reverse('admin:recrutement_candidatevaluation_change', 
+            args=(obj.id,)), obj.note)
+    _note.allow_tags = True
+    _note.short_description = "Note"    
+    _note.admin_order_field = 'note'    
+
+    ### 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"
+    
+    ### Permissions add, delete, change
+    def has_add_permission(self, request):
+        user_groupes = request.user.groups.all()
+        if request.user.is_superuser is True or \
+            grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            self.readonly_fields = ()
+            return True
+        self.readonly_fields = self._readonly_fields
+        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
+        """
+        if request.user.is_superuser is True:
+            return True
+        self.readonly_fields = self._readonly_fields
+        grant = self.has_add_permission(request)
+        if obj is None:
+            return grant
+        else:
+            return grant and request.user == obj.evaluateur.user
+
+    ### Queryset
+    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()
+        user_groupes = request.user.groups.all()
+        if grp_drh_recrutement in user_groupes or \
+            grp_directeurs_bureau_recrutement in user_groupes or \
+            grp_administrateurs_recrutement in user_groupes:
+            return qs.select_related('offre_emploi')
+
+        try:
+            evaluateur = Evaluateur.objects.get(user=request.user) 
+        except Evaluateur.DoesNotExist:       
+            return qs.none()     
+        
+        candidats_evaluations = CandidatEvaluation.objects.\
+                                filter(evaluateur=evaluateur)
+        candidats_evaluations_ids = [ce.id for ce in \
+                                        candidats_evaluations.all()]
+        return qs.select_related('offre_emploi').\
+                filter(id__in=candidats_evaluations_ids)
+
+class CourrielTemplateAdmin(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(Evaluateur, EvaluateurAdmin)
+admin.site.register(CourrielTemplate, CourrielTemplateAdmin)
diff --git a/project/recrutement/api.py b/project/recrutement/api.py
new file mode 100644 (file)
index 0000000..2965f2e
--- /dev/null
@@ -0,0 +1,111 @@
+# -*- encoding: utf-8 -*
+from django.core import serializers
+from datetime import date
+from django.http import HttpResponse
+from django.template import RequestContext, Template
+from django.shortcuts import render_to_response, redirect, get_object_or_404
+from django.utils import simplejson
+from django.contrib import messages
+
+import datamaster_modeles.models as ref
+from auf.django.emploi import models as emploi
+from auf.django.emploi import forms as emploiForms
+from project.recrutement.models import Evaluateur, CandidatEvaluation, \
+                                CourrielTemplate
+from project.recrutement.views import send_templated_email
+
+STATUS_OK = 200
+
+STATUS_ERROR = 400
+STATUS_ERROR_NOT_FOUND = 404
+STATUS_ERROR_PERMISSIONS = 403
+STATUS_ERROR_BADMETHOD = 405
+
+def api(request, method, *args, **kwargs):
+    # TODO: Sécurité : 
+    #       L'échange d'information doit être possible qu'avec les HOST désirés.
+
+    #if request.method != 'POST':
+    #    return api_return(STATUS_ERROR_BADMETHOD)
+
+    api = API(request)
+    if not hasattr(api, 'api_%s' % method):
+        return api_return(STATUS_ERROR)
+    if kwargs.has_key('offre_id'):
+        offre_id = kwargs['offre_id']
+        return api.api_candidat_add(offre_id)
+    else:
+        return getattr(api, 'api_%s' % method)()
+    
+
+def api_return(status, text='', json=False):
+    content_type = 'text/html'
+    if status == STATUS_OK and json:
+        content_type = 'text/json'
+    if text is None:
+        if status == STATUS_ERROR:
+            text = 'Error'
+        elif status == STATUS_ERROR_NOT_FOUND:
+            text = 'Resource Not Found'
+        elif status == STATUS_ERROR_PERMISSIONS:
+            text = 'Invalid username or password'
+        elif status == STATUS_ERROR_BADMETHOD:
+            text = 'Invalid request method'
+        elif status == STATUS_OK:
+            text = 'OK'
+
+    r = HttpResponse(status=status, content=text, content_type=content_type)
+
+    if status == STATUS_ERROR_BADMETHOD:
+        r.Allow = 'POST'
+
+    return r
+
+
+class API:
+    def __init__(self, request):
+        self.request = request
+
+    def api_candidat_add(self, offre_id):
+        vars = dict()
+        offre = emploi.OffreEmploi.objects.get(id=offre_id)
+
+        if self.request.method == "POST":
+            candidat = emploi.Candidat()
+            candidat.offre_emploi = offre
+            form = emploiForms.NoCaptchaPostulerOffreEmploiForm(self.request.POST, instance=candidat)
+            piecesForm = emploiForms.CandidatPieceForm(self.request.POST, self.request.FILES, instance=candidat)
+            if form.is_valid():
+                candidat = form.save()
+                piecesForm.save()
+                data = serializers.serialize('json', [candidat,])
+
+                evaluateurs = candidat.offre_emploi.evaluateurs.all()
+                for evaluateur in evaluateurs:                
+                    candidat_evaluation = CandidatEvaluation()
+                    candidat_evaluation.candidat = candidat
+                    candidat_evaluation.evaluateur = evaluateur
+                    candidat_evaluation.save()
+
+                courriel_template = CourrielTemplate.objects.get(id=1)
+                send_templated_email(candidat, courriel_template)
+                return api_return(STATUS_OK, data)  
+            else:
+                return api_return(STATUS_ERROR, form.errors)
+
+        
+
+    def api_offre_emploi_liste(self):
+        offres_visibles =  emploi.OffreEmploi.objects.filter(est_affiche=True, statut="AFFI", date_limite__gte=date.today())
+        data = serializers.serialize('json', offres_visibles)
+        return api_return(STATUS_OK, data);
+        
+    def api_offre_emploi(self):
+        try:
+            id = self.request.GET.get('id')
+            offre = emploi.OffreEmploi.objects.get(id=id, statut="AFFI", date_limite__gte=date.today())
+        except emploi.OffreEmploi.DoesNotExist:
+            return api_return(STATUS_ERROR, "ID d'offre d'emploi invalide")
+    
+        data = serializers.serialize('json', [offre])
+        return api_return(STATUS_OK, data)
diff --git a/project/recrutement/context_processors.py b/project/recrutement/context_processors.py
new file mode 100644 (file)
index 0000000..69b8c6e
--- /dev/null
@@ -0,0 +1,8 @@
+# -*- encoding: utf-8 -*-
+from recrutement.permissions import user_in_recrutement_groupes as in_recrutement_groupes
+
+# Ajout de variables accessibles dans les templates (pour tester permissions dans templates)
+   
+def user_in_recrutement_groupes(request):
+    return {'user_in_recrutement_groupes': in_recrutement_groupes(request.user)}
+
diff --git a/project/recrutement/forms.py b/project/recrutement/forms.py
new file mode 100644 (file)
index 0000000..6025a77
--- /dev/null
@@ -0,0 +1,121 @@
+# -*- encoding: utf-8 -*-
+
+import os
+from django import forms
+from django.contrib import admin
+from django.forms.models import inlineformset_factory
+from datetime import timedelta
+from django.forms.widgets import CheckboxSelectMultiple
+from django.contrib.admin import widgets as admin_widgets   
+from form_utils.forms import BetterModelForm
+from django.forms import ModelForm, ModelChoiceField, HiddenInput, CharField
+from django.forms.models import BaseInlineFormSet 
+from django.core.mail import send_mail
+
+from datamaster_modeles.models import Employe, Implantation, Region
+from tinymce.widgets import TinyMCE
+from captcha.fields import CaptchaField
+
+from recrutement import models as recr
+from auf.django.emploi import forms as emploi
+from project.rh import models as rh
+from project.dae.utils import get_employe_from_user as get_emp
+
+################################################################################
+# EVALUATION
+################################################################################
+class CandidatEvaluationForm(ModelForm):
+    def __init__(self, *args, **kwargs):   
+        self.candidat = kwargs.pop('candidat')    
+        self.evaluateur = kwargs.pop('evaluateur') 
+        super(CandidatEvaluationForm, self).__init__(*args, **kwargs)
+
+    def save(self):
+        super(CandidatEvaluationForm, self).save()
+
+    class Meta:
+        fields = ('note', 'commentaire')
+        model = recr.CandidatEvaluation  
+
+class EvaluateurForm(forms.Form):
+    evaluateurs = forms.ModelMultipleChoiceField(queryset=
+                    recr.Evaluateur.objects.all())
+
+    def __init__(self, *args, **kwargs):
+        self.offres_emploi = kwargs.pop('offres_emploi')
+        super(EvaluateurForm, self).__init__(*args, **kwargs)
+
+    def save(self):
+        candidats = recr.Candidat.objects.\
+                            filter(offre_emploi__in=self.offres_emploi)
+        for candidat in candidats:
+            for evaluateur in self.cleaned_data.get('evaluateurs', []):                
+                candidat_evaluation = recr.CandidatEvaluation()
+                candidat_evaluation.candidat = candidat
+                candidat_evaluation.evaluateur = evaluateur
+                candidat_evaluation.save()
+        
+
+################################################################################
+# OFFRE EMPLOI
+################################################################################
+class CandidatPieceForm(emploi.CandidatPieceForm):
+    pass
+
+class PostulerOffreEmploiForm(emploi.PostulerOffreEmploiForm):
+    pass
+
+class OffreEmploiForm(ModelForm):
+    #poste = ModelChoiceField(queryset=rh.Poste.objects.all())
+
+    #class Meta:
+    #    model = recr.OffreEmploi  
+
+    #def __init__(self, *args, **kwargs):
+    #    super(OffreEmploiForm, self).__init__(*args, **kwargs)
+    #
+    #def save(self, *args, **kwargs):
+    #    kwargs2 = kwargs.copy()
+    #    kwargs2['commit'] = False
+    #    offre = super(OffreEmploiForm, self).save(*args, **kwargs2)
+    #    offre.poste = self.cleaned_data.get("poste").id
+    #    offre.poste_nom = self.cleaned_data.get("poste").nom
+    #    if 'commit' not in kwargs or kwargs['commit']:
+    #        offre.save()
+    #    return offre
+
+    def clean(self):
+        cleaned_data = self.cleaned_data
+        date_limite = cleaned_data.get("date_limite")
+        debut_affectation = cleaned_data.get("debut_affectation")
+
+        if date_limite and debut_affectation:
+            if date_limite > debut_affectation:
+                raise forms.ValidationError("La date limite ne peut pas être \
+                        supérieure à la date d'affection.")
+        return cleaned_data
+
+################################################################################
+# TEMPLATE COURRIEL
+################################################################################
+class CandidatCourrielTemplateForm(ModelForm):
+    def get_template(self):
+        return self.data['template']
+
+    class Meta:
+        model = recr.CandidatCourriel
+        fields = ('template', )
+
+class CandidatCourrielForm(ModelForm):
+    def __init__(self, *args, **kwargs):
+        self.candidats = kwargs.pop('candidats')
+        self.template = kwargs.pop('template')
+        super(CandidatCourrielForm, self).__init__(*args, **kwargs)
+
+    def save(self):
+        super(CandidatCourrielForm, self).save()
+
+    class Meta:
+        model = recr.CandidatCourriel
+        fields = ('sujet', 'plain_text', 'html')
+
diff --git a/project/recrutement/migrations/0001_initial.py b/project/recrutement/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..1d0e836
--- /dev/null
@@ -0,0 +1,292 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import SchemaMigration
+from django.db import models
+
+class Migration(SchemaMigration):
+    
+    def forwards(self, orm):
+        
+        # Adding model 'Evaluateur'
+        db.create_table('recrutement_evaluateur', (
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'], unique=True)),
+        ))
+        db.send_create_signal('recrutement', ['Evaluateur'])
+
+        # Adding M2M table for field offres_emploi on 'Evaluateur'
+        db.create_table('recrutement_evaluateur_offres_emploi', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('evaluateur', models.ForeignKey(orm['recrutement.evaluateur'], null=False)),
+            ('offreemploi', models.ForeignKey(orm['emploi.offreemploi'], null=False))
+        ))
+        db.create_unique('recrutement_evaluateur_offres_emploi', ['evaluateur_id', 'offreemploi_id'])
+
+        # Adding model 'CandidatEvaluation'
+        db.create_table('recrutement_candidatevaluation', (
+            ('candidat', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', db_column='candidat', to=orm['emploi.Candidat'])),
+            ('commentaire', self.gf('django.db.models.fields.TextField')(default='Aucun', null=True, blank=True)),
+            ('note', self.gf('django.db.models.fields.IntegerField')(null=True, blank=True)),
+            ('date', self.gf('django.db.models.fields.DateField')(auto_now_add=True, blank=True)),
+            ('evaluateur', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', db_column='evaluateur', to=orm['recrutement.Evaluateur'])),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+        ))
+        db.send_create_signal('recrutement', ['CandidatEvaluation'])
+
+        # Adding model 'CourrielTemplate'
+        db.create_table('recrutement_courrieltemplate', (
+            ('plain_text', self.gf('django.db.models.fields.TextField')()),
+            ('html', self.gf('tinymce.models.HTMLField')()),
+            ('sujet', self.gf('django.db.models.fields.CharField')(max_length=100)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('nom_modele', self.gf('django.db.models.fields.CharField')(max_length=100)),
+        ))
+        db.send_create_signal('recrutement', ['CourrielTemplate'])
+
+        # Adding model 'CandidatCourriel'
+        db.create_table('recrutement_candidatcourriel', (
+            ('plain_text', self.gf('django.db.models.fields.TextField')(blank=True)),
+            ('html', self.gf('tinymce.models.HTMLField')(null=True, blank=True)),
+            ('sujet', self.gf('django.db.models.fields.CharField')(max_length=255, blank=True)),
+            ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)),
+            ('template', self.gf('django.db.models.fields.related.ForeignKey')(related_name='+', db_column='template', to=orm['recrutement.CourrielTemplate'])),
+        ))
+        db.send_create_signal('recrutement', ['CandidatCourriel'])
+
+        # Adding M2M table for field candidats on 'CandidatCourriel'
+        db.create_table('recrutement_candidatcourriel_candidats', (
+            ('id', models.AutoField(verbose_name='ID', primary_key=True, auto_created=True)),
+            ('candidatcourriel', models.ForeignKey(orm['recrutement.candidatcourriel'], null=False)),
+            ('candidat', models.ForeignKey(orm['recrutement.candidat'], null=False))
+        ))
+        db.create_unique('recrutement_candidatcourriel_candidats', ['candidatcourriel_id', 'candidat_id'])
+    
+    
+    def backwards(self, orm):
+        
+        # Deleting model 'Evaluateur'
+        db.delete_table('recrutement_evaluateur')
+
+        # Removing M2M table for field offres_emploi on 'Evaluateur'
+        db.delete_table('recrutement_evaluateur_offres_emploi')
+
+        # Deleting model 'CandidatEvaluation'
+        db.delete_table('recrutement_candidatevaluation')
+
+        # Deleting model 'CourrielTemplate'
+        db.delete_table('recrutement_courrieltemplate')
+
+        # Deleting model 'CandidatCourriel'
+        db.delete_table('recrutement_candidatcourriel')
+
+        # Removing M2M table for field candidats on 'CandidatCourriel'
+        db.delete_table('recrutement_candidatcourriel_candidats')
+    
+    
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'datamaster_modeles.bureau': {
+            'Meta': {'object_name': 'Bureau', 'db_table': "u'ref_bureau'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'implantation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Implantation']", 'db_column': "'implantation'"}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nom_court': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'nom_long': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"})
+        },
+        'datamaster_modeles.implantation': {
+            'Meta': {'object_name': 'Implantation', 'db_table': "u'ref_implantation'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'adresse_physique_bureau': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_code_postal': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'adresse_physique_code_postal_avant_ville': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'adresse_physique_no': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'adresse_physique_pays': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'impl_adresse_physique'", 'to_field': "'code'", 'db_column': "'adresse_physique_pays'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'adresse_physique_precision': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_precision_avant': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_region': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_rue': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_ville': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'adresse_postale_boite_postale': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_bureau': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_code_postal': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_code_postal_avant_ville': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'adresse_postale_no': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_pays': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'impl_adresse_postale'", 'to_field': "'code'", 'db_column': "'adresse_postale_pays'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'adresse_postale_precision': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_precision_avant': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_region': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_rue': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_ville': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'bureau_rattachement': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Implantation']", 'db_column': "'bureau_rattachement'"}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'code_meteo': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'commentaire': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'courriel': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'courriel_interne': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'date_extension': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'date_fermeture': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'date_inauguration': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'date_ouverture': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'fax': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'fax_interne': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'fuseau_horaire': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'hebergement_convention': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'hebergement_convention_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'hebergement_etablissement': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'modif_date': ('django.db.models.fields.DateField', [], {}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nom_court': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'nom_long': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"}),
+            'remarque': ('django.db.models.fields.TextField', [], {}),
+            'responsable_implantation': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'statut': ('django.db.models.fields.IntegerField', [], {}),
+            'telephone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'telephone_interne': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        'datamaster_modeles.pays': {
+            'Meta': {'object_name': 'Pays', 'db_table': "u'ref_pays'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'unique': 'True'}),
+            'code_bureau': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Bureau']", 'to_field': "'code'", 'db_column': "'code_bureau'"}),
+            'code_iso3': ('django.db.models.fields.CharField', [], {'max_length': '3', 'unique': 'True', 'blank': 'True'}),
+            'developpement': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'monnaie': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nord_sud': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"})
+        },
+        'datamaster_modeles.region': {
+            'Meta': {'object_name': 'Region', 'db_table': "u'ref_region'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'implantation_bureau': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'gere_region'", 'db_column': "'implantation_bureau'", 'to': "orm['datamaster_modeles.Implantation']"}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'emploi.candidat': {
+            'Meta': {'object_name': 'Candidat'},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'adresse': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'code_postal': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'date_creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'domaine_professionnel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}),
+            'employeur_actuel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'etat_province': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'genre': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nationalite': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'nationalite'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'niveau_diplome': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nombre_dependant': ('django.db.models.fields.IntegerField', [], {}),
+            'offre_emploi': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'offre_emploi'", 'to': "orm['emploi.OffreEmploi']"}),
+            'pays': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'pays'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'poste_actuel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'prenom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'situation_famille': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'statut': ('django.db.models.fields.CharField', [], {'default': "'NOUV'", 'max_length': '4'}),
+            'telephone': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'ville': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'emploi.offreemploi': {
+            'Meta': {'object_name': 'OffreEmploi'},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'bureau': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Bureau']", 'db_column': "'bureau'"}),
+            'date_creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'date_limite': ('django.db.models.fields.DateField', [], {}),
+            'debut_affectation': ('django.db.models.fields.DateField', [], {}),
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'duree_affectation': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'est_affiche': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'lieu_affectation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Implantation']", 'db_column': "'implantation'"}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'poste': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'poste_nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"}),
+            'renumeration': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'resume': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'statut': ('django.db.models.fields.CharField', [], {'default': "'NOUV'", 'max_length': '4'})
+        },
+        'recrutement.candidat': {
+            'Meta': {'object_name': 'Candidat', 'db_table': "'emploi_candidat'", '_ormbases': ['emploi.Candidat']}
+        },
+        'recrutement.candidatcourriel': {
+            'Meta': {'object_name': 'CandidatCourriel'},
+            'candidats': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['emploi.Candidat']", 'symmetrical': 'False'}),
+            'html': ('tinymce.models.HTMLField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'plain_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'sujet': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'template'", 'to': "orm['recrutement.CourrielTemplate']"})
+        },
+        'recrutement.candidatevaluation': {
+            'Meta': {'object_name': 'CandidatEvaluation'},
+            'candidat': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'candidat'", 'to': "orm['emploi.Candidat']"}),
+            'commentaire': ('django.db.models.fields.TextField', [], {'default': "'Aucun'", 'null': 'True', 'blank': 'True'}),
+            'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'evaluateur': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'evaluateur'", 'to': "orm['recrutement.Evaluateur']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'note': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'recrutement.courrieltemplate': {
+            'Meta': {'object_name': 'CourrielTemplate'},
+            'html': ('tinymce.models.HTMLField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nom_modele': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'plain_text': ('django.db.models.fields.TextField', [], {}),
+            'sujet': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'recrutement.evaluateur': {
+            'Meta': {'object_name': 'Evaluateur'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'offres_emploi': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'evaluateurs'", 'blank': 'True', 'to': "orm['emploi.OffreEmploi']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+        }
+    }
+    
+    complete_apps = ['recrutement']
diff --git a/project/recrutement/migrations/0002_tpl_courriel.py b/project/recrutement/migrations/0002_tpl_courriel.py
new file mode 100644 (file)
index 0000000..34cd58b
--- /dev/null
@@ -0,0 +1,235 @@
+# encoding: utf-8
+import datetime
+from south.db import db
+from south.v2 import DataMigration
+from django.db import models
+
+class Migration(DataMigration):
+    
+    def forwards(self, orm):
+        "Write your forwards methods here."
+
+        # Création du template de courriel par défaut. (Confirmation de postulation à l'offre d'emploi
+        template = orm.CourrielTemplate()
+        template.id = 1
+        template.nom_modele = "Confirmation de postulation pour une offre d'emploi (envoi automatique)"
+        template.sujet = "Confirmation de postulation pour l'offre d'emploi: {{ offre_emploi }}"
+        template.plain_text = "Bonjour {{ genre_candidat }} {{ nom_candidat }},\n\nCe courriel est pour vous confirmer que nous avons bien reçu votre candidature pour l'offre d'emploi {{ offre_emploi }}.\n\n Merci de l'intérêt que vous portez à l'AUF." 
+        template.html = "<p>Bonjour {{ genre_candidat }} {{ nom_candidat }},<br /> \
+                            Ce courriel est pour vous confirmer que nous \
+                            avons bien reçu votre candidature pour l'offre \
+                            d'emploi {{ offre_emploi }}. <br /> <br /> \
+                            Merci de l'intérêt que vous portez à l'AUF.</p>"
+        template.save()
+    
+    
+    def backwards(self, orm):
+        "Write your backwards methods here."
+        template = orm.CourrielTemplate.objects.get(id=1).delete()
+    
+    models = {
+        'auth.group': {
+            'Meta': {'object_name': 'Group'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '80', 'unique': 'True'}),
+            'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'})
+        },
+        'auth.permission': {
+            'Meta': {'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'},
+            'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '50'})
+        },
+        'auth.user': {
+            'Meta': {'object_name': 'User'},
+            'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}),
+            'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}),
+            'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}),
+            'username': ('django.db.models.fields.CharField', [], {'max_length': '30', 'unique': 'True'})
+        },
+        'contenttypes.contenttype': {
+            'Meta': {'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"},
+            'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'name': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'datamaster_modeles.bureau': {
+            'Meta': {'object_name': 'Bureau', 'db_table': "u'ref_bureau'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'implantation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Implantation']", 'db_column': "'implantation'"}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nom_court': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'nom_long': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"})
+        },
+        'datamaster_modeles.implantation': {
+            'Meta': {'object_name': 'Implantation', 'db_table': "u'ref_implantation'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'adresse_physique_bureau': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_code_postal': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'adresse_physique_code_postal_avant_ville': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'adresse_physique_no': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}),
+            'adresse_physique_pays': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'impl_adresse_physique'", 'to_field': "'code'", 'db_column': "'adresse_physique_pays'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'adresse_physique_precision': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_precision_avant': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_region': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_rue': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'adresse_physique_ville': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'adresse_postale_boite_postale': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_bureau': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_code_postal': ('django.db.models.fields.CharField', [], {'max_length': '20', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_code_postal_avant_ville': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'adresse_postale_no': ('django.db.models.fields.CharField', [], {'max_length': '30', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_pays': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'impl_adresse_postale'", 'to_field': "'code'", 'db_column': "'adresse_postale_pays'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'adresse_postale_precision': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_precision_avant': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_region': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_rue': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'adresse_postale_ville': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'bureau_rattachement': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Implantation']", 'db_column': "'bureau_rattachement'"}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'code_meteo': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'commentaire': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'courriel': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'courriel_interne': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}),
+            'date_extension': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'date_fermeture': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'date_inauguration': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'date_ouverture': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'fax': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'fax_interne': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'fuseau_horaire': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'hebergement_convention': ('django.db.models.fields.NullBooleanField', [], {'null': 'True', 'blank': 'True'}),
+            'hebergement_convention_date': ('django.db.models.fields.DateField', [], {'null': 'True', 'blank': 'True'}),
+            'hebergement_etablissement': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'modif_date': ('django.db.models.fields.DateField', [], {}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nom_court': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'nom_long': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"}),
+            'remarque': ('django.db.models.fields.TextField', [], {}),
+            'responsable_implantation': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'}),
+            'statut': ('django.db.models.fields.IntegerField', [], {}),
+            'telephone': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'telephone_interne': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'type': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'url': ('django.db.models.fields.URLField', [], {'max_length': '255', 'blank': 'True'})
+        },
+        'datamaster_modeles.pays': {
+            'Meta': {'object_name': 'Pays', 'db_table': "u'ref_pays'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '2', 'unique': 'True'}),
+            'code_bureau': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Bureau']", 'to_field': "'code'", 'db_column': "'code_bureau'"}),
+            'code_iso3': ('django.db.models.fields.CharField', [], {'max_length': '3', 'unique': 'True', 'blank': 'True'}),
+            'developpement': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'monnaie': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nord_sud': ('django.db.models.fields.CharField', [], {'max_length': '255', 'null': 'True', 'blank': 'True'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"})
+        },
+        'datamaster_modeles.region': {
+            'Meta': {'object_name': 'Region', 'db_table': "u'ref_region'"},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'code': ('django.db.models.fields.CharField', [], {'max_length': '255', 'unique': 'True'}),
+            'id': ('django.db.models.fields.IntegerField', [], {'primary_key': 'True'}),
+            'implantation_bureau': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'gere_region'", 'db_column': "'implantation_bureau'", 'to': "orm['datamaster_modeles.Implantation']"}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'emploi.candidat': {
+            'Meta': {'object_name': 'Candidat'},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'adresse': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'code_postal': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'date_creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'domaine_professionnel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'email': ('django.db.models.fields.EmailField', [], {'max_length': '255'}),
+            'employeur_actuel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'etat_province': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'genre': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nationalite': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'nationalite'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'niveau_diplome': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'nombre_dependant': ('django.db.models.fields.IntegerField', [], {}),
+            'offre_emploi': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'offre_emploi'", 'to': "orm['emploi.OffreEmploi']"}),
+            'pays': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'pays'", 'to': "orm['datamaster_modeles.Pays']"}),
+            'poste_actuel': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'prenom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'situation_famille': ('django.db.models.fields.CharField', [], {'max_length': '1'}),
+            'statut': ('django.db.models.fields.CharField', [], {'default': "'NOUV'", 'max_length': '4'}),
+            'telephone': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'ville': ('django.db.models.fields.CharField', [], {'max_length': '255'})
+        },
+        'emploi.offreemploi': {
+            'Meta': {'object_name': 'OffreEmploi'},
+            'actif': ('django.db.models.fields.BooleanField', [], {'default': 'True', 'blank': 'True'}),
+            'bureau': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Bureau']", 'db_column': "'bureau'"}),
+            'date_creation': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'date_limite': ('django.db.models.fields.DateField', [], {}),
+            'debut_affectation': ('django.db.models.fields.DateField', [], {}),
+            'description': ('django.db.models.fields.TextField', [], {}),
+            'duree_affectation': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'est_affiche': ('django.db.models.fields.BooleanField', [], {'default': 'False', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'lieu_affectation': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Implantation']", 'db_column': "'implantation'"}),
+            'nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'poste': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'poste_nom': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'region': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['datamaster_modeles.Region']", 'db_column': "'region'"}),
+            'renumeration': ('django.db.models.fields.CharField', [], {'max_length': '255'}),
+            'resume': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'statut': ('django.db.models.fields.CharField', [], {'default': "'NOUV'", 'max_length': '4'})
+        },
+        'recrutement.candidat': {
+            'Meta': {'object_name': 'Candidat', 'db_table': "'emploi_candidat'", '_ormbases': ['emploi.Candidat']}
+        },
+        'recrutement.candidatcourriel': {
+            'Meta': {'object_name': 'CandidatCourriel'},
+            'candidats': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['emploi.Candidat']", 'symmetrical': 'False'}),
+            'html': ('tinymce.models.HTMLField', [], {'null': 'True', 'blank': 'True'}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'plain_text': ('django.db.models.fields.TextField', [], {'blank': 'True'}),
+            'sujet': ('django.db.models.fields.CharField', [], {'max_length': '255', 'blank': 'True'}),
+            'template': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'template'", 'to': "orm['recrutement.CourrielTemplate']"})
+        },
+        'recrutement.candidatevaluation': {
+            'Meta': {'object_name': 'CandidatEvaluation'},
+            'candidat': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'candidat'", 'to': "orm['emploi.Candidat']"}),
+            'commentaire': ('django.db.models.fields.TextField', [], {'default': "'Aucun'", 'null': 'True', 'blank': 'True'}),
+            'date': ('django.db.models.fields.DateField', [], {'auto_now_add': 'True', 'blank': 'True'}),
+            'evaluateur': ('django.db.models.fields.related.ForeignKey', [], {'related_name': "'+'", 'db_column': "'evaluateur'", 'to': "orm['recrutement.Evaluateur']"}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'note': ('django.db.models.fields.IntegerField', [], {'null': 'True', 'blank': 'True'})
+        },
+        'recrutement.courrieltemplate': {
+            'Meta': {'object_name': 'CourrielTemplate'},
+            'html': ('tinymce.models.HTMLField', [], {}),
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'nom_modele': ('django.db.models.fields.CharField', [], {'max_length': '100'}),
+            'plain_text': ('django.db.models.fields.TextField', [], {}),
+            'sujet': ('django.db.models.fields.CharField', [], {'max_length': '100'})
+        },
+        'recrutement.evaluateur': {
+            'Meta': {'object_name': 'Evaluateur'},
+            'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}),
+            'offres_emploi': ('django.db.models.fields.related.ManyToManyField', [], {'symmetrical': 'False', 'related_name': "'evaluateurs'", 'blank': 'True', 'to': "orm['emploi.OffreEmploi']"}),
+            'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']", 'unique': 'True'})
+        }
+    }
+    
+    complete_apps = ['recrutement']
diff --git a/project/recrutement/migrations/__init__.py b/project/recrutement/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/project/recrutement/models.py b/project/recrutement/models.py
new file mode 100644 (file)
index 0000000..1e793dc
--- /dev/null
@@ -0,0 +1,170 @@
+# -=- encoding: utf-8 -=-
+
+import datetime
+from django.contrib.auth.models import User
+from django.core.files.storage import FileSystemStorage
+from tinymce import models as tinymce_models
+from django.db import models
+import settings
+
+from south.modelsinspector import add_introspection_rules
+add_introspection_rules([], ["^tinymce.models.HTMLField"])
+import datamaster_modeles.models as ref
+
+from recrutement.workflow import grp_evaluateurs_recrutement
+from auf.django.emploi import models as emploi
+from auf.django.emploi.models import TYPE_PIECE_CHOICES
+
+from recrutement.workflow import grp_evaluateurs_recrutement
+
+### CONSTANTES
+#NOTES
+NOTE_MIN = 1
+NOTE_RANGE = 1
+NOTE_MAX = 11
+NOTES = [(i, i) for i in range(NOTE_MIN, NOTE_MAX, NOTE_RANGE)]
+
+#HELP_TEXT
+HELP_TEXT_NB_DEPENDANT = "Le nombre de personnes à charge"
+HELP_TEXT_TAGS_ACCEPTES = "Pour le texte, les variables disponibles sont : \
+                            {{ nom_candidat }} {{ prenom_candidat }} \
+                            {{ offre_emploi }} et {{ genre_candidat }} \
+                            (Pour Monsieur/Madame). Ces champs seront \
+                            automatiquement remplacés par les informations de \
+                            chaque candidat."
+
+# Abstracts
+class Metadata(models.Model):
+    """Méta-données AUF.
+    Metadata.actif = flag remplaçant la suppression.
+    actif == False : objet réputé supprimé.
+    """
+    actif = models.BooleanField(default=True)
+    date_creation = models.DateField(auto_now_add=True, )
+    
+    class Meta:
+        abstract = True
+
+class Candidat(emploi.Candidat):
+    class Meta:
+        proxy = True
+
+    def moyenne_notes(self):
+        evaluations = CandidatEvaluation.objects.filter(candidat=self)
+        notes = [evaluation.note for evaluation in evaluations.all() \
+                    if evaluation.note is not None]
+
+        if len(notes) > 0:
+            moyenne_votes = float(sum(notes)) / len(notes)
+        else:
+            moyenne_votes = "Non disponible"
+        return moyenne_votes
+
+class OffreEmploi(emploi.OffreEmploi):
+    class Meta:
+        proxy = True
+
+
+class CandidatPiece(emploi.CandidatPiece):
+    class Meta:
+        proxy = True
+
+class OffreEmploiManager(models.Manager):
+    def get_query_set(self):
+        fkeys = ('region',)
+        return super(OffreEmploiManager, self).get_query_set().\
+                    select_related(*fkeys).all()
+
+class ProxyOffreEmploi(emploi.OffreEmploi):
+    class Meta:
+        proxy = True
+        verbose_name = u"Offre d'emploi (visualisation)"
+        verbose_name_plural = u"Offres d'emploi (visualisation)"
+
+    def __unicode__(self):
+        return '%s [%s] - View' % (self.nom, self.id)
+
+class ProxyCandidat(emploi.Candidat):
+    class Meta:
+        proxy = True
+        verbose_name = u"Candidat (visualisation)"
+        verbose_name_plural = u"Candidats (visualisation)"
+
+    def __unicode__(self):
+        return '%s %s [%s]' % (self.prenom, self.nom, self.id)
+
+class Evaluateur(models.Model):
+    user = models.ForeignKey(User, unique=True, verbose_name=u"Évaluateur")
+    offres_emploi = models.ManyToManyField(emploi.OffreEmploi, 
+                related_name="evaluateurs", blank=True)
+
+    def save(self, *args, **kwargs):
+        """
+        Assigner automatiquement l'évaluateurs d'une offre d'emploi à un 
+        nouveau candidat.
+        """
+        self.user.groups.add(grp_evaluateurs_recrutement)
+        super(Evaluateur, self).save(*args, **kwargs)
+    
+    class Meta:
+        verbose_name = u"évaluateur"
+
+    def __unicode__(self):
+        return '%s %s' % (self.user.first_name, self.user.last_name)
+
+class CandidatEvaluation(models.Model):
+    candidat = models.ForeignKey(emploi.Candidat, db_column='candidat', 
+                related_name='+',) 
+    evaluateur = models.ForeignKey(Evaluateur, db_column='evaluateur', 
+                    related_name='+', verbose_name=u'Évaluateur') 
+    note = models.IntegerField(choices=NOTES, blank=True, null=True)
+    commentaire = models.TextField(null=True, blank=True, default='Aucun')
+    date = models.DateField(auto_now_add=True,)  
+
+    class Meta:
+        verbose_name = u'évaluation du candidat'
+        verbose_name_plural = u'évaluations des candidats'
+
+    def __unicode__(self):
+        return u"Évaluation de %s" % self.candidat
+
+#### TEMPLATE COURRIEL
+TEMPLATE_CHOICES = (
+    ('SEL', 'Sélectionné'),
+    ('REF', 'Refusé'),
+)
+
+class CourrielTemplate(models.Model):
+    nom_modele = models.CharField(max_length=100, verbose_name=u'Nom du modèle',)
+    sujet = models.CharField(max_length=100, verbose_name=u'Sujet du courriel')
+    plain_text = models.TextField(verbose_name=u'Texte', 
+                                    help_text=HELP_TEXT_TAGS_ACCEPTES,  )
+    html = tinymce_models.HTMLField(verbose_name=u'Texte en HTML', 
+                                    help_text=HELP_TEXT_TAGS_ACCEPTES,  )
+
+
+    def __unicode__(self):
+        return u'%s' % self.nom_modele
+
+    class Meta:
+        ordering = ['nom_modele',]
+        verbose_name = "Modèle de courriel"
+        verbose_name_plural = "Modèles de courriel"
+
+class CandidatCourriel(models.Model):
+    candidats = models.ManyToManyField(Candidat, verbose_name=u"Candidats", )
+    template = models.ForeignKey(CourrielTemplate, db_column='template', 
+                related_name='+', verbose_name=u"Modèle de courriel", )
+    sujet = models.CharField(max_length=255, blank=True, 
+                                help_text=HELP_TEXT_TAGS_ACCEPTES, )
+    plain_text = models.TextField(verbose_name=u'Texte', blank=True,
+                                    help_text=HELP_TEXT_TAGS_ACCEPTES,  )
+    html = tinymce_models.HTMLField(verbose_name=u'Texte en HTML', null=True, 
+                                blank=True, help_text=HELP_TEXT_TAGS_ACCEPTES, )
+
+    def __unicode__(self):
+        return '%s' % (self.titre)
+
+    class Meta:
+        verbose_name = u"modèle de courriel"
+        verbose_name_plural = u"modèles de courriel"
diff --git a/project/recrutement/permissions.py b/project/recrutement/permissions.py
new file mode 100644 (file)
index 0000000..5e17b03
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*-
+from django.contrib.auth.models import Group
+
+from project.lib import safe_create_groupe
+
+# Logique AUF des permissions
+
+grp_drh = safe_create_groupe(id=4)  # DRH
+grp_evaluateurs = safe_create_groupe(id=13)    # Évaluateurs
+
+recrutement_groupes = (
+    grp_drh,
+    grp_evaluateurs,
+)
+
+def user_in_recrutement_groupes(user):
+    """
+    Teste si un user Django fait parti des groupes prédéfinis de DAE.
+    """
+    if user.is_superuser:
+        return True
+    for g in user.groups.all():
+        if g in recrutement_groupes:
+            return True
+    return False
diff --git a/project/recrutement/templates/admin/recrutement/candidat/change_form.html b/project/recrutement/templates/admin/recrutement/candidat/change_form.html
new file mode 100644 (file)
index 0000000..95f616e
--- /dev/null
@@ -0,0 +1,78 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+
+{% block extrahead %}{{ block.super }}
+{% url admin:jsi18n as jsi18nurl %}
+<script type="text/javascript" src="{{ jsi18nurl|default:"../../../jsi18n/" }}"></script>
+{{ media }}
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/dae.css" />{% endblock %}
+
+{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
+
+{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../../../">{% trans "Home" %}</a> &rsaquo;
+     <a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
+     {% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} &rsaquo;
+     {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+  <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+  {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+    <li><a class="historylink" target="_blank" href="/recrutement/candidat_pdf/?id={{original.id}}">Imprimer la fiche du candidat</a></li>
+  </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
+{% if save_on_top %}{% submit_row %}{% endif %}
+{% if errors %}
+    <p class="errornote">
+    {% blocktrans count errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+    </p>
+    {{ adminform.form.non_field_errors }}
+{% endif %}
+
+{% for fieldset in adminform %}
+  {% include "admin/includes/fieldset.html" %}
+{% endfor %}
+
+{% block after_field_sets %}{% endblock %}
+
+{% for inline_admin_formset in inline_admin_formsets %}
+    {% include inline_admin_formset.opts.template %}
+{% endfor %}
+
+<fieldset class="module aligned ">
+    <h2>Moyenne des notes</h2>
+        <div class="form-row">
+            <label>Moyenne:</label>
+            <p>{{ original.moyenne_notes }}</p>
+        </div>
+</fieldset>
+
+{% block after_related_objects %}{% endblock %}
+
+{% submit_row %}
+
+{% if adminform and add %}
+   <script type="text/javascript">document.getElementById("{{ adminform.first_field.id_for_label }}").focus();</script>
+{% endif %}
+
+{# JavaScript for prepopulated fields #}
+{% prepopulated_fields_js %}
+
+</div>
+
+</form></div>
+{% endblock %}
diff --git a/project/recrutement/templates/admin/recrutement/proxycandidat/change_form.html b/project/recrutement/templates/admin/recrutement/proxycandidat/change_form.html
new file mode 100644 (file)
index 0000000..23a5050
--- /dev/null
@@ -0,0 +1,84 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+
+{% block extrahead %}{{ block.super }}
+{% url admin:jsi18n as jsi18nurl %}
+<script type="text/javascript" src="{{ jsi18nurl|default:"../../../jsi18n/" }}"></script>
+{{ media }}
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />
+<link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/dae.css" />{% endblock %}
+
+{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
+
+{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../../../">{% trans "Home" %}</a> &rsaquo;
+     <a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
+     {% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} &rsaquo;
+     {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+  <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+  {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+    <li><a class="historylink" target="_blank" href="/recrutement/candidat_pdf/?id={{original.id}}">Imprimer la fiche du candidat</a></li>
+  </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="post" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
+{% if save_on_top %}{% submit_row %}{% endif %}
+{% if errors %}
+    <p class="errornote">
+    {% blocktrans count errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+    </p>
+    {{ adminform.form.non_field_errors }}
+{% endif %}
+
+{% for fieldset in adminform %}
+  {% include "admin/includes/fieldset.html" %}
+{% endfor %}
+
+{% block after_field_sets %}{% endblock %}
+
+{% for inline_admin_formset in inline_admin_formsets %}
+    {% include inline_admin_formset.opts.template %}
+{% endfor %}
+
+<fieldset class="module aligned ">
+    <h2>Pièces jointes</h2>
+    {% for p in original.pieces_jointes %}
+        {% if p.path %}
+        <div class="form-row">
+            <label>{{ p.get_nom_display }}:</label>
+            <p><a href="{{ p.path.url }}">{{ p.path.url }}</a></p>
+        </div>
+        {% endif %}
+    {% endfor %}
+</fieldset>
+
+{% block after_related_objects %}{% endblock %}
+
+<div class="submit-row">
+    <a href="../" class="default">Retour</a>
+</div>
+
+{% if adminform and add %}
+   <script type="text/javascript">document.getElementById("{{ adminform.first_field.id_for_label }}").focus();</script>
+{% endif %}
+
+{# JavaScript for prepopulated fields #}
+{% prepopulated_fields_js %}
+
+</div>
+</form>
+</div>
+{% endblock %}
diff --git a/project/recrutement/templates/admin/recrutement/proxyoffreemploi/change_form.html b/project/recrutement/templates/admin/recrutement/proxyoffreemploi/change_form.html
new file mode 100644 (file)
index 0000000..caf772e
--- /dev/null
@@ -0,0 +1,81 @@
+{% extends "admin/base_site.html" %}
+{% load i18n admin_modify adminmedia %}
+
+{% block extrahead %}{{ block.super }}
+{% url admin:jsi18n as jsi18nurl %}
+<script type="text/javascript" src="{{ jsi18nurl|default:"../../../jsi18n/" }}"></script>
+{{ media }}
+{% endblock %}
+
+{% block extrastyle %}{{ block.super }}<link rel="stylesheet" type="text/css" href="{% admin_media_prefix %}css/forms.css" />{% endblock %}
+
+{% block coltype %}{% if ordered_objects %}colMS{% else %}colM{% endif %}{% endblock %}
+
+{% block bodyclass %}{{ opts.app_label }}-{{ opts.object_name.lower }} change-form{% endblock %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../../../">{% trans "Home" %}</a> &rsaquo;
+     <a href="../../">{{ app_label|capfirst|escape }}</a> &rsaquo;
+     {% if has_change_permission %}<a href="../">{{ opts.verbose_name_plural|capfirst }}</a>{% else %}{{ opts.verbose_name_plural|capfirst }}{% endif %} &rsaquo;
+     {% if add %}{% trans "Add" %} {{ opts.verbose_name }}{% else %}{{ original|truncatewords:"18" }}{% endif %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}<div id="content-main">
+{% block object-tools %}
+{% if change %}{% if not is_popup %}
+  <ul class="object-tools"><li><a href="history/" class="historylink">{% trans "History" %}</a></li>
+  {% if has_absolute_url %}<li><a href="../../../r/{{ content_type_id }}/{{ object_id }}/" class="viewsitelink">{% trans "View on site" %}</a></li>{% endif%}
+  </ul>
+{% endif %}{% endif %}
+{% endblock %}
+<form {% if has_file_field %}enctype="multipart/form-data" {% endif %}action="{{ form_url }}" method="" id="{{ opts.module_name }}_form">{% csrf_token %}{% block form_top %}{% endblock %}
+<div>
+{% if is_popup %}<input type="hidden" name="_popup" value="1" />{% endif %}
+{% if save_on_top %}{% submit_row %}{% endif %}
+{% if errors %}
+    <p class="errornote">
+    {% blocktrans count errors|length as counter %}Please correct the error below.{% plural %}Please correct the errors below.{% endblocktrans %}
+    </p>
+    {{ adminform.form.non_field_errors }}
+{% endif %}
+
+{% for fieldset in adminform %}
+  {% include "admin/includes/fieldset.html" %}
+{% endfor %}
+
+{% block after_field_sets %}{% endblock %}
+
+{% for inline_admin_formset in inline_admin_formsets %}
+    {% include inline_admin_formset.opts.template %}
+{% endfor %}
+
+<fieldset class="module aligned ">
+    <h2>Pièces jointes</h2>
+    {% for p in original.pieces_jointes %}
+        <div class="form-row">
+            <label>{{ p.get_nom_display }}:</label>
+            <p><a href="{{ p.path.url }}">{{ p.path.url }}</a></p>
+        </div>
+    {% endfor %}
+</fieldset>
+
+{% block after_related_objects %}{% endblock %}
+
+<div class="submit-row">
+    <a href="../" class="default">Retour</a>
+</div>
+
+
+{% if adminform and add %}
+   <script type="text/javascript">document.getElementById("{{ adminform.first_field.id_for_label }}").focus();</script>
+{% endif %}
+
+{# JavaScript for prepopulated fields #}
+{% prepopulated_fields_js %}
+
+</div>
+</form>
+</div>
+{% endblock %}
diff --git a/project/recrutement/templates/recrutement/affecter_evaluateurs.html b/project/recrutement/templates/recrutement/affecter_evaluateurs.html
new file mode 100644 (file)
index 0000000..308ae8d
--- /dev/null
@@ -0,0 +1,40 @@
+{% extends 'admin/base_site.html' %}
+{% load i18n adminmedia form_utils_tags %}
+
+{% block title %}RH - Recrutement{% endblock %}
+{% block sous_titre %}Affecter évaluateur{% endblock %}
+
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../../">{% trans "Home" %}</a> &rsaquo;
+     <a href="../">{% trans "Recrutement" %}</a> &rsaquo;
+     {% trans "Affecter un évaluateur" %}
+</div>
+{% endif %}{% endblock %}
+
+{% block content %}
+<div id="content-main">
+    {% block object-tools %}{% endblock %}
+
+
+
+    <div class="module">
+        <h2>Affectation évaluateurs</h2>
+
+        <form action="" method="post">
+            <table>
+                <tr>
+                    <td>{{ form.evaluateurs.label }}</td>
+                    <td>{{ form.evaluateurs }}</td>
+                </tr>
+            </table>
+            <div class="submit-row">
+                <input type="submit" name="_save" class="default" value="Enregistrer">
+            </div>
+        </form>
+    </div>
+
+
+</div>
+
+{% endblock %}
diff --git a/project/recrutement/templates/recrutement/candidat_pdf.html b/project/recrutement/templates/recrutement/candidat_pdf.html
new file mode 100644 (file)
index 0000000..ede69a0
--- /dev/null
@@ -0,0 +1,44 @@
+<html>
+    <head>
+        <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/pdf.css" />
+        <link rel="stylesheet" type="text/css" href="{{ STATIC_URL }}css/dae.css" />
+
+    </head>
+
+<body>
+    <h1>Candidat: {{ candidat }}</h1>
+    <div class="visualClear"></div>
+    <h2>Offre d'emploi: {{ candidat.offre_emploi }}</h2>
+    <fieldset>
+        <h2>Informations personnelles</h2>
+        <ul>
+            <li>Nom: {{ candidat.nom }}</li>
+            <li>Prenom: {{ candidat.prenom }}</li>
+            <li>Genre: {{ candidat.genre }}</li>
+            <li>Nationalité: {{ candidat.nationalite }}</li>
+            <li>Situation familiale: {{ candidat.situation_famille }}</li>
+            <li>Nombre de dépendant: {{ candidat.nombre_dependant }}</li>
+        </ul>
+    </fieldset>
+    <fieldset>
+        <h2>Coordonnées</h2>
+        <ul>
+            <li>Téléphone: {{ candidat.telephone }}</li>
+            <li>Courriel: {{ candidat.courriel }}</li>
+            <li>Adresse: {{ candidat.adresse }}</li>
+            <li>Ville: {{ candidat.ville }}</li>
+            <li>État/Province: {{ candidat.etat_province }}</li>
+            <li>Code postal: {{ candidat.code_postal }}</li>
+            <li>Pays: {{ candidat.pays }}</li>
+        </ul>
+    </fieldset>
+    <fieldset>
+        <h2>Informations professionnelles</h2>
+        <ul>
+            <li>Niveau du diplôme: {{ candidat.niveau_diplome }}</li>
+            <li>Employeur actuel: {{ candidat.employeur_actuel }}</li>
+            <li>Poste actuel: {{ candidat.poste_actuel }}</li>
+            <li>Domaine professionnel: {{ candidat.domaine_professionnel }}</li>
+        </ul>
+    </fieldset>
+</body>
diff --git a/project/recrutement/templates/recrutement/envoyer_courriel_candidats.html b/project/recrutement/templates/recrutement/envoyer_courriel_candidats.html
new file mode 100644 (file)
index 0000000..2ffe957
--- /dev/null
@@ -0,0 +1,62 @@
+{% extends 'admin/base_site.html' %}
+{% load i18n adminmedia form_utils_tags %}
+
+{% block title %}RH - Recrutement{% endblock %}
+{% block sous_titre %}Envoyer courriel aux candidats{% endblock %}
+{% block extrahead %}
+
+{{ form.media }}
+{% endblock %}
+{% block content %}
+<div id="content-main">
+    {% block object-tools %}{% endblock %}
+
+    <div class="module">
+        <h2>Envoyer un courriel aux candidats</h2>
+
+        <form action="" method="post">
+            <table>
+                <tr>
+                    <td>Candidats sélectionnés</td>
+                    <td>
+                        <select name="candidats" multiple="True">
+                        {% for c in form.candidats %}
+                            <option selected="selected" value="{{ c.nom }}_{{c.prenom}}_{{c.id}}">{{c}}</option>
+                        {% endfor %}
+                        </select>
+                    </td>
+                </tr>
+                <tr>
+                    <td>Modèle de courriel</td>
+                    <td>
+                        {{ form.template }}
+                    </td>
+                </tr>
+                <tr>
+                    <td>{{ form.sujet.label }}</td>
+                    <td>{{ form.sujet }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.plain_text.label }}</td>
+                    <td>{{ form.plain_text }}
+                        <p class="help">{{ form.plain_text.help_text }}</p>
+                    </td>
+                    
+                </tr>
+                <tr>
+                    <td>{{ form.html.label }}</td>
+                    <td>{{ form.html }}
+                        <p class="help">{{ form.html.help_text }}</p>
+                    </td>                       
+                </tr>
+            </table>
+            <div class="submit-row">
+                <input type="submit" name="_save" class="default" value="Envoyer">
+            </div>
+        </form>
+    </div>
+
+
+</div>
+
+{% endblock %}
diff --git a/project/recrutement/templates/recrutement/index.html b/project/recrutement/templates/recrutement/index.html
new file mode 100644 (file)
index 0000000..5ca20a9
--- /dev/null
@@ -0,0 +1,14 @@
+{% extends 'base.html' %}
+
+{% block title %}RH - Recrutement{% endblock %}
+{% block sous_titre %}Recrutement{% endblock %}
+
+{% block main %}
+<h1>Recrutement</h1>
+
+<p>
+Application de gestion du recrutement.
+</p>
+
+{% endblock %}
+
diff --git a/project/recrutement/templates/recrutement/pieces.html b/project/recrutement/templates/recrutement/pieces.html
new file mode 100644 (file)
index 0000000..d8587d7
--- /dev/null
@@ -0,0 +1,30 @@
+<table>
+    {% for f in piecesForm.management_form %}
+        {{ f }}
+    {% endfor %}
+    <tr>  
+        <th></th> 
+        {% for field in piecesForm.forms.0 %}
+            {% if not field.is_hidden %}
+                <th>{{ field.label }}</th>
+            {% endif %}
+        {% endfor %}
+    </tr>
+    {% for f in piecesForm.forms %}
+    <tr>
+        <td>
+            {{ f.errors }}
+            {% if f.initial.fichier %}
+                <a href="{{ f.initial.fichier.url }}" target="_blank">Télécharger</a>
+            {% endif %}
+        </td>
+        {% for field in f %} 
+            {% if not field.is_hidden %}
+                <td>{{ field }}</td>
+            {% else %}
+                {{ field }}
+            {% endif %}
+        {% endfor %}
+    </tr>
+    {% endfor %}
+</table>
diff --git a/project/recrutement/templates/recrutement/postuler_appel_offre.html b/project/recrutement/templates/recrutement/postuler_appel_offre.html
new file mode 100644 (file)
index 0000000..5227c79
--- /dev/null
@@ -0,0 +1,134 @@
+{% extends 'base.html' %}
+{% load adminmedia %}
+
+{% block title %}RH{% endblock %}
+{% block sous_titre %}Accueil{% endblock %}
+
+{% block main %}
+<div id="content-main">
+    {% block object-tools %}{% endblock %}
+
+
+
+
+    <div class="module">
+    <h2>Poster pour un appel d'offre d'emploi</h2>
+    </div>
+       
+    <form action="" method="post" enctype="multipart/form-data">
+        <fieldset>
+            <h2>Informations personnelles</h2>
+            <table id="informations_personnelles">
+                <tbody>
+                <tr>
+                    <td>{{ form.prenom.label }}</td>
+                    <td>{{ form.prenom }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.nom.label }}</td>
+                    <td>{{ form.nom }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.genre.label }}</td>
+                    <td>{{ form.genre }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.nationalite.label }}</td>
+                    <td>{{ form.nationalite }}</td>
+                <tr>
+                    <td>{{ form.situation_famille.label }}</td>
+                    <td>{{ form.situation_famille }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.nombre_dependant.label }}</td>
+                    <td>{{ form.nombre_dependant }}<br />
+                        <span class="info">{{ form.nombre_dependant.help_text }}
+                        </span>
+                    </td>
+                </tr>
+                </tbody>
+            </table>            
+        </fieldset>
+        <fieldset>
+            <h2>Coordonnées</h2>
+            <table id="coordonnees">
+                <tbody>
+                <tr>
+                    <td>{{ form.telephone.label }}</td>
+                    <td>{{ form.telephone }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.email.label }}</td>
+                    <td>{{ form.email }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.adresse.label }}</td>
+                    <td>{{ form.adresse }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.ville.label }}</td>
+                    <td>{{ form.ville }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.etat_province.label }}</td>
+                    <td>{{ form.etat_province }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.code_postal.label }}</td>
+                    <td>{{ form.code_postal }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.pays.label }}</td>
+                    <td>{{ form.pays }}</td>
+                </tr>
+                </tbody>
+            </table>
+        </fieldset>
+        <fieldset>
+            <h2>Informations professionnelles</h2>
+            <table id="informations_professionnelles">
+                <tbody>
+                <tr>
+                    <td>{{ form.niveau_diplome.label }}</td>
+                    <td>{{ form.niveau_diplome }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.employeur_actuel.label }}</td>
+                    <td>{{ form.employeur_actuel }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.poste_actuel.label }}</td>
+                    <td>{{ form.poste_actuel }}</td>
+                </tr>
+                <tr>
+                    <td>{{ form.domaine_professionnel.label }}</td>
+                    <td>{{ form.domaine_professionnel }}</td>
+                </tr>
+                </tbody>
+            </table>
+        </fieldset>
+        <fieldset>
+            <h2>Pièces jointes</h2>
+            <p class="info">CV, lettre de motivation...</p>
+            {% include "recrutement/pieces.html" %}
+        </fieldset>
+        <fieldset>
+            <h2>Vérification CAPTCHA</h2>
+            <p class="info">Entrez les caractères figurant dans l'image ci-dessous.</p>
+                <tr>
+                    <td>
+                        {{ form.captcha }}
+                        {{ form.captcha.errors }}
+                    </td>
+                </tr>
+        </fieldset>
+        <div class="submit-row">
+            <input type="submit" name="save" value="Enregistrer" />
+        </div>
+    </form>
+
+
+
+</div>
+
+{% endblock %}
diff --git a/project/recrutement/templates/recrutement/selectionner_template.html b/project/recrutement/templates/recrutement/selectionner_template.html
new file mode 100644 (file)
index 0000000..d3f8bbc
--- /dev/null
@@ -0,0 +1,42 @@
+{% extends 'admin/base_site.html' %}
+{% load i18n adminmedia form_utils_tags %}
+
+{% block title %}RH - Recrutement{% endblock %}
+{% block sous_titre %}Envoyer courriel aux candidats{% endblock %}
+{% block extrahead %}
+{{ form.media }}
+{% endblock %}
+{% block breadcrumbs %}{% if not is_popup %}
+<div class="breadcrumbs">
+     <a href="../../">{% trans "Home" %}</a> &rsaquo;
+     <a href="../">{% trans "Recrutement" %}</a> &rsaquo;
+     {% trans "Envoyer courriel - Sélectionner modèle" %}
+</div>
+{% endif %}{% endblock %}
+
+
+{% block content %}
+<div id="content-main">
+    {% block object-tools %}{% endblock %}
+
+    <div class="module">
+        <h2>Sélectionner modèle de courriel</h2>
+
+        <form action="" method="post">
+            <table>
+                <tr>
+                    <td>{{ form.template.label }}</td>
+                    <td>{{ form.template }}</td>
+                </tr>
+                
+            </table>
+            <div class="submit-row">
+                <input type="submit" name="_save" class="default" value="Suivant">
+            </div>
+        </form>
+    </div>
+
+
+</div>
+
+{% endblock %}
diff --git a/project/recrutement/tests.py b/project/recrutement/tests.py
new file mode 100644 (file)
index 0000000..2247054
--- /dev/null
@@ -0,0 +1,23 @@
+"""
+This file demonstrates two different styles of tests (one doctest and one
+unittest). These will both pass when you run "manage.py test".
+
+Replace these with more appropriate tests for your application.
+"""
+
+from django.test import TestCase
+
+class SimpleTest(TestCase):
+    def test_basic_addition(self):
+        """
+        Tests that 1 + 1 always equals 2.
+        """
+        self.failUnlessEqual(1 + 1, 2)
+
+__test__ = {"doctest": """
+Another way to test that 1 + 1 is equal to 2.
+
+>>> 1 + 1 == 2
+True
+"""}
+
diff --git a/project/recrutement/urls.py b/project/recrutement/urls.py
new file mode 100644 (file)
index 0000000..6db004b
--- /dev/null
@@ -0,0 +1,25 @@
+# -*- encoding: utf-8 -*
+
+from django.conf.urls.defaults import patterns, url
+from auf.django.emploi import settings
+
+urlpatterns = patterns('recrutement.views',
+    url(r'^$', 'index', name='recrutement_index'),
+
+    (r'^prive/(?P<path>.*)$', 'mediaserve', {'document_root': settings.OE_PRIVE_MEDIA_ROOT}),
+
+    url(r'^affecter_evaluateurs_offre_emploi/$', 
+        'affecter_evaluateurs_offre_emploi', 
+        name='affecter_evaluateurs_offre_emploi'),
+
+    url(r'^envoyer_courriel_candidats/$', 
+        'envoyer_courriel_candidats', 
+        name='envoyer_courriel_candidats'),
+
+    url(r'^selectionner_template/$', 
+        'selectionner_template', 
+        name='selectionner_template'),
+
+    url(r'candidat_pdf/$', 'candidat_pdf', 
+        name='candidat_pdf'),
+)
diff --git a/project/recrutement/views.py b/project/recrutement/views.py
new file mode 100644 (file)
index 0000000..5acfa7c
--- /dev/null
@@ -0,0 +1,144 @@
+# -*- encoding: utf-8 -*-
+
+from django.core.mail import send_mail
+from django.core.urlresolvers import reverse
+from django.contrib import messages
+from django.contrib.auth.models import User
+from django.views.static import serve
+from django.http import HttpResponse, HttpResponseRedirect
+from django.shortcuts import render_to_response, redirect, get_object_or_404
+from django.template import Context, RequestContext, Template
+from django.core.mail import EmailMultiAlternatives
+
+from forms import *
+from models import *
+from recrutement.workflow import grp_evaluateurs_recrutement, recrutement_groupes
+
+################################################################################
+# MEDIA PRIVE
+################################################################################
+
+def mediaserve(request, path, document_root=None, show_indexes=False):
+    """
+    Sécuriser l'accès aux fichiers uploadés
+    """
+    grant_ok = False
+    user = request.user
+
+    for grp in user.groups.all():
+        if grp in recrutement_groupes:
+            grant_ok = True
+            break
+
+    if not grant_ok:
+        return redirect_interdiction(request)
+
+    return serve(request, path, document_root, show_indexes)
+
+def index(request):
+    return render_to_response('recrutement/index.html', {}, 
+                                RequestContext(request))
+
+def selectionner_template(request):
+    candidat_ids = request.GET.get('ids')
+    if request.method == "POST":
+        form = CandidatCourrielTemplateForm(request.POST)
+        if form.is_valid():
+            form.save()
+            courriel_template_id = form.get_template()
+            
+            return HttpResponseRedirect(reverse('envoyer_courriel_candidats')+
+            "?ids_cand=%s&id_temp=%s" % (candidat_ids, courriel_template_id))
+    else:
+        form = CandidatCourrielTemplateForm()
+
+    c = {'form' : form}   
+    return render_to_response("recrutement/selectionner_template.html", 
+            Context(c), context_instance = RequestContext(request))
+
+def envoyer_courriel_candidats(request):
+    candidat_ids = request.GET.get('ids_cand').split(',')
+    candidats = Candidat.objects.filter(id__in=candidat_ids)
+    template_id = request.GET.get('id_temp')
+    template = CourrielTemplate.objects.get(id=template_id) 
+
+    if request.method == "POST":
+        form = CandidatCourrielForm(request.POST, instance=template,
+                                    candidats=candidats, template=template)
+
+        if form.is_valid():
+            form.save()
+            courriel_template = CourrielTemplate()
+            courriel_template.nom_modele = template.nom_modele
+            courriel_template.sujet = form.data['sujet']
+            courriel_template.plain_text = form.data['plain_text']
+            courriel_template.html = form.data['html']
+            for cand in candidats:     
+                send_templated_email(cand, courriel_template)
+            messages.add_message(request, messages.SUCCESS, 
+                            "Le email a été envoyé aux candidats.")
+            return redirect("admin:recrutement_candidat_changelist")
+    else:
+        form = CandidatCourrielForm(candidats=candidats, template=template,
+                                    initial={'sujet': template.sujet,
+                                    'plain_text': template.plain_text,
+                                    'html': template.html})
+
+    c = {'form' : form}   
+    return render_to_response("recrutement/envoyer_courriel_candidats.html", 
+            Context(c), context_instance = RequestContext(request))
+
+def affecter_evaluateurs_offre_emploi(request):
+    offre_emploi_ids = request.GET.get('ids').split(',')
+    offres_emploi = OffreEmploi.objects.filter(id__in=offre_emploi_ids)
+    if request.method == "POST":
+        form = EvaluateurForm(request.POST, offres_emploi=offres_emploi)
+        if form.is_valid():
+            form.save()
+            messages.add_message(request, messages.SUCCESS, 
+                        "Les évaluateurs ont été affectés aux offres d'emploi.")
+            return redirect("admin:recrutement_offreemploi_changelist")
+    else:
+        form = EvaluateurForm(offres_emploi=offres_emploi)
+
+    c = {'form' : form}   
+    return render_to_response("recrutement/affecter_evaluateurs.html", 
+            Context(c), context_instance = RequestContext(request))
+
+def send_templated_email(candidat, template):
+    from django.conf import settings
+    # Sujet
+    sujet_template = Template(template.sujet)
+    dict_sujet = {"offre_emploi": candidat.offre_emploi.nom,}            
+    sujet = Context(dict_sujet)
+    # Plain text
+    texte_template = Template(template.plain_text)
+    dict_texte = {"nom_candidat": candidat.nom, 
+                    "prenom_candidat": candidat.prenom, 
+                    "offre_emploi": candidat.offre_emploi.nom,
+                    "genre_candidat": "Monsieur" if candidat.genre == "M" \
+                                     else "Madame",
+                    }
+    texte = Context(dict_texte)
+    # HTML text
+    html_template = Template(template.html)
+    texte_html = Context(dict_texte)
+    if settings.DEBUG:
+        dst_emails = ['developpeurs@ca.auf.org', ]
+    else:
+        dst_emails = [candidat.email, ]
+    msg = EmailMultiAlternatives(sujet_template.render(sujet), 
+                                texte_template.render(texte),
+                                'recrutement@auf.org', 
+                                dst_emails,              
+                                )
+    msg.attach_alternative(texte_template.render(texte_html), "text/html")
+    msg.send()
+
+
+def candidat_pdf(request):
+    candidat_id = request.GET.get('id')
+    candidat = Candidat.objects.get(id=candidat_id)
+
+    return render_to_response("recrutement/candidat_pdf.html", 
+            Context({'candidat' : candidat}), context_instance = RequestContext(request))
diff --git a/project/recrutement/workflow.py b/project/recrutement/workflow.py
new file mode 100644 (file)
index 0000000..ab8a409
--- /dev/null
@@ -0,0 +1,27 @@
+# -*- encoding: utf-8 -*-
+
+from django.contrib.auth.models import Group
+
+def safe_create_groupe(name):
+    """
+    Création d'un groupe prédéfini. Retourne None, quand la création
+    ne peut se faire. (C'est le cas au syncdb, quand la table de groupe
+    n'a pas été crée encore).
+    """
+    try:
+        grp, created = Group.objects.get_or_create(name=name)
+    except:
+        return None
+    return grp
+
+grp_evaluateurs_recrutement = safe_create_groupe(name='Évaluateurs')
+grp_drh_recrutement = safe_create_groupe(name='DRH')
+grp_directeurs_bureau_recrutement = safe_create_groupe(name='Directeurs de bureau')
+grp_administrateurs_recrutement = safe_create_groupe(name='Administrateurs')
+
+recrutement_groupes = (
+    grp_evaluateurs_recrutement,
+    grp_drh_recrutement,
+    grp_directeurs_bureau_recrutement,
+    grp_administrateurs_recrutement,
+)
index 647b9c9..83f0e1d 100644 (file)
@@ -28,7 +28,7 @@ PRIVE_MEDIA_ROOT = os.path.join(os.path.dirname(__file__), 'media_prive')
 # Examples: "http://media.lawrence.com", "http://example.com/media/"
 MEDIA_URL = '/media/'
 PRIVE_MEDIA_URL = '/dae/prive/'
-
+OE_PRIVE_MEDIA_URL = '/recrutement/prive/'
 
 # URL prefix for admin media -- CSS, JavaScript and images. Make sure to use a
 # trailing slash.
@@ -70,6 +70,10 @@ INSTALLED_APPS = (
     'auf.django.permissions',
     'project.rh_v1',
     'project.dae',
+    'auf.django.emploi',
+    'project.recrutement',
+    'form_utils',
+    'captcha',
 )
 
 TEMPLATE_CONTEXT_PROCESSORS = (
@@ -82,6 +86,7 @@ TEMPLATE_CONTEXT_PROCESSORS = (
     'auf.django.skin.context_processors.auf',
     'project.context_processors.utilisateur',
     'project.context_processors.user_is_admin',
+    'recrutement.context_processors.user_in_recrutement_groupes',
 )
 
 AUTHENTICATION_BACKENDS = (
index e43461e..5cfa7ce 100644 (file)
@@ -15,6 +15,7 @@ urlpatterns = patterns(
     (r'^$', 'project.views.index'),
     url(r'^admin_tools/', include('admin_tools.urls')),
     (r'^admin/', include(admin.site.urls)),
+    url(r'^api/(?P<method>[a-z_-]+)/$', 'recrutement.api.api', name='recrutement_api'),
     (r'^connexion/$', 'django.contrib.auth.views.login'),
     (r'^deconnexion/$', 'django.contrib.auth.views.logout'),
     (r'^dae/', include('project.dae.urls')),