refact ergo + page perso (vide)
authordavin baragiotta <davin.baragiotta@u-db.(none)>
Tue, 2 Aug 2011 19:35:30 +0000 (15:35 -0400)
committerDavin BARAGIOTTA <davin.baragiotta@auf.org>
Tue, 2 Aug 2011 19:35:30 +0000 (15:35 -0400)
26 files changed:
.gitignore
buildout.cfg
project/recrutement/admin.py
project/recrutement/api.py [new file with mode: 0644]
project/recrutement/forms.py
project/recrutement/models.py
project/recrutement/templates/admin/recrutement/proxycandidat/change_form.html [new file with mode: 0644]
project/recrutement/urls.py
project/recrutement/views.py
project/settings.py
project/urls.py
src/auf.django.emploi/.gitignore [new file with mode: 0644]
src/auf.django.emploi/CHANGES [new file with mode: 0644]
src/auf.django.emploi/MANIFEST.in [new file with mode: 0644]
src/auf.django.emploi/README [new file with mode: 0644]
src/auf.django.emploi/auf/__init__.py [new file with mode: 0644]
src/auf.django.emploi/auf/django/__init__.py [new file with mode: 0644]
src/auf.django.emploi/auf/django/emploi/__init__.py [new file with mode: 0755]
src/auf.django.emploi/auf/django/emploi/api.py [new file with mode: 0644]
src/auf.django.emploi/auf/django/emploi/forms.py [new file with mode: 0644]
src/auf.django.emploi/auf/django/emploi/models.py [new file with mode: 0755]
src/auf.django.emploi/auf/django/emploi/settings.py [new file with mode: 0644]
src/auf.django.emploi/auf/django/emploi/templates/recrutement/pieces.html [new file with mode: 0644]
src/auf.django.emploi/auf/django/emploi/templates/recrutement/postuler_appel_offre.html [new file with mode: 0644]
src/auf.django.emploi/setup.cfg [new file with mode: 0644]
src/auf.django.emploi/setup.py [new file with mode: 0644]

index 51ce3bd..ec755d5 100644 (file)
@@ -6,7 +6,7 @@
 .*.swp
 *~
 \#*#
-src/*
+# src/*
 
 # DB de dev
 *.db
@@ -32,3 +32,4 @@ tmp
 
 # extra
 project/media_prive/*
+src/auf.django.emploi/auf/django/emploi/media_prive/*
index 36f727b..7588e54 100644 (file)
@@ -12,6 +12,8 @@ find-links = http://pypi.auf.org/simple/auf.recipe.django/
     http://pypi.auf.org/simple/auf.django.metadata/
     http://pypi.auf.org/django-alphafilter/
 
+develop = src/auf.django.emploi
+
 eggs =
     django
     south
@@ -19,6 +21,7 @@ eggs =
     auf.django.skin
     auf.django.workflow
     auf.django.admingroup
+    auf.django.emploi
     datamaster_modeles
     auf.django.auth
     django-reversion
index c7fc9b6..b817bd5 100644 (file)
@@ -4,20 +4,54 @@ from django.core.urlresolvers import reverse
 from django.http import HttpResponseRedirect
 from django.contrib import admin
 from django.shortcuts import get_object_or_404
+from django.core.files.storage import default_storage
 
 from reversion.admin import VersionAdmin
 from datamaster_modeles.models import Employe, Implantation, Region
+from django.forms.models import BaseInlineFormSet
 
+from project.rh import models as rh
 from recrutement.models import *
 from recrutement.workflow import grp_administrateurs_recrutement,\
                             grp_evaluateurs_recrutement, grp_drh_recrutement
+from recrutement.forms import *
+
+"""
+class MetaAdmin(VersionAdmin):
+    def get_actions(self, request):
+    
+Pour refactoring
+"""
+
+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', 'resume', 'date_limite', 'region',  'statut', 
-                    'est_affiche', '_candidatsList')
+                    'est_affiche', '_candidatsList', )
+    exclude = ('poste_nom',)
     list_filter = ('statut', 'est_affiche', )
     actions = ['affecter_evaluateurs_offre_emploi', ]
+    form = OffreEmploiForm
+    inlines = [EvaluateurInline, ]
+
+    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)
@@ -33,12 +67,12 @@ class OffreEmploiAdmin(VersionAdmin):
     _candidatsList.allow_tags = True 
     _candidatsList.short_description = "Afficher la liste des candidats"
 
-
     def queryset(self, request):
         qs = self.model._default_manager.get_query_set()
         # Si user est superuser afficher toutes les offres d'emploi  
         user_groupes = request.user.groups.all()
-        if not grp_drh_recrutement in user_groupes:
+        if not grp_drh_recrutement in user_groupes and \
+            not request.user.is_superuser:
             """
             Si le user n'est ni un évaluateur ni un administrateur régional,
             retourner none
@@ -72,14 +106,15 @@ class OffreEmploiAdmin(VersionAdmin):
     def has_change_permission(self, request, obj=None):
         user_groupes = request.user.groups.all()
         if grp_drh_recrutement in user_groupes or \
-            grp_administrateurs_recrutement in user_groupes:
+            grp_administrateurs_recrutement in user_groupes or \
+            request.user.is_superuser:
             return True
         return False   
 
 class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
     list_display = ('nom', 'resume', 'date_limite', 'region', 'statut', 
                     'est_affiche')
-    readonly_fields = ('description', 'poste', 'bureau',
+    readonly_fields = ('description', 'bureau',
                         'duree_affectation', 'renumeration',
                         'debut_affectation', 'lieu_affectation', 'nom',
                         'resume', 'date_limite', 'region')
@@ -88,7 +123,7 @@ class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
             'fields': ('nom', )        
         }),
         ('Description générale', {
-            'fields': ('poste', 'resume','description', 'date_limite', )        
+            'fields': ('resume','description', 'date_limite', )        
         }),
         ('Coordonnées', {
             'fields': ('lieu_affectation', 'bureau', 'region', )
@@ -98,6 +133,20 @@ class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
                         'renumeration', )
         }),
     )        
+
+    def get_actions(self, request):
+        actions = super(ProxyOffreEmploiAdmin, self).get_actions(request)
+        del actions['affecter_evaluateurs_offre_emploi']
+        return actions
+
+    def response_change(self, request, obj):
+        response = super(ProxyOffreEmploiAdmin, self).response_change(request, obj)
+        user_groupes = request.user.groups.all()
+        if grp_drh_recrutement in user_groupes or \
+            request.user.is_superuser:
+            return HttpResponseRedirect(reverse('admin:recrutement_offreemploi_changelist'))
+        return HttpResponseRedirect(reverse('admin:recrutement_proxyoffreemploi_changelist'))
+
     def has_add_permission(self, request):
         return False
 
@@ -107,38 +156,39 @@ class ProxyOffreEmploiAdmin(OffreEmploiAdmin):
     def has_change_permission(self, request, obj=None):
         user_groupes = request.user.groups.all()
         if grp_evaluateurs_recrutement in user_groupes or \
-            grp_drh_recrutement in user_groupes:
+            grp_drh_recrutement in user_groupes or \
+            request.user.is_superuser:
             return True
         return False   
 
-class ProxyCandidatPiece(CandidatPiece):
-    """
-    Ce proxy sert uniquement dans l'admin à disposer d'un libellé
-    plus ergonomique.
-    """
-    class Meta:
-        proxy = True
-        verbose_name = "pièce jointe"
-        verbose_name_plural = "pièces jointes"
-
 class CandidatPieceInline(admin.TabularInline):
-    model = ProxyCandidatPiece
-    fields = ('candidat', 'nom', 'path', )
+    model = CandidatPiece
+    fields = ('candidat', 'nom', 'path',)
     extra = 1
+    max_num = 3
 
-class ProxyEvaluateur(Evaluateur.candidats.through):
+class CandidatEvaluationInlineFormSet(BaseInlineFormSet):
     """
-    Ce proxy sert uniquement dans l'admin à disposer d'un libellé
-    plus ergonomique.
+    Empêche la suppression d'une évaluation pour le CandidatEvaluationInline
     """
-    class Meta:
-        proxy = True
-        verbose_name = "évaluateur"
-
-class EvaluateurInline(admin.TabularInline):
-    model = ProxyEvaluateur
-    fields = ('evaluateur',)
-    extra = 1
+    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
+    
+    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):
     date_hierarchy = 'date_creation'
@@ -168,17 +218,15 @@ class CandidatAdmin(VersionAdmin):
     )
     inlines = [
         CandidatPieceInline,
-        EvaluateurInline,
+        CandidatEvaluationInline,
     ]
 
-    actions = ['affecter_candidats_evaluateur', 'envoyer_courriel_candidats']
-    # Affecter un évaluateurs à des candidats
-    def affecter_candidats_evaluateur(modeladmin, obj, candidats):   
-        selected = obj.POST.getlist(admin.ACTION_CHECKBOX_NAME)
+    actions = ['envoyer_courriel_candidats']
 
-        return HttpResponseRedirect(reverse('affecter_evaluateurs_candidats')+
-                "?ids=%s" % (",".join(selected)))
-    affecter_candidats_evaluateur.short_description = u'Affecter évaluateur(s)'
+    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):   
@@ -201,7 +249,7 @@ class CandidatAdmin(VersionAdmin):
         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'Afficher les détails du candidat'
+    afficher_candidat.short_description = u'Détails du candidat'
 
     # Voir l'offre d'emploi
     def voir_offre_emploi(self, obj):
@@ -230,7 +278,8 @@ class CandidatAdmin(VersionAdmin):
     def add_delete_permission(self, request, obj=None) :
         user_groupes = request.user.groups.all()
         if grp_drh_recrutement in user_groupes or \
-            grp_administrateurs_recrutement in user_groupes:
+            grp_administrateurs_recrutement in user_groupes or \
+            request.user.is_superuser:
             return True
         return False    
 
@@ -243,11 +292,12 @@ class CandidatAdmin(VersionAdmin):
     def has_change_permission(self, request, obj=None):
         user_groupes = request.user.groups.all()
         if grp_drh_recrutement in user_groupes or \
-            grp_administrateurs_recrutement in user_groupes:
+            grp_administrateurs_recrutement in user_groupes or \
+            request.user.is_superuser:
             return True
         return False   
 
-    def queryset(self, obj):
+    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 
@@ -258,15 +308,16 @@ class CandidatAdmin(VersionAdmin):
         """
         qs = self.model._default_manager.get_query_set() 
         # Si user est superuser afficher tous les candidats       
-        user_groupes = obj.user.groups.all()
-        if not grp_drh_recrutement in user_groupes:
+        user_groupes = request.user.groups.all()
+        if not grp_drh_recrutement in user_groupes and \
+            not request.user.is_superuser:
             # Si le user n'est ni un évaluateur ni un administrateur régional,
             # retourner none
 
             # Vérifier groupes
             if grp_evaluateurs_recrutement in user_groupes:
                 try:
-                    user = Evaluateur.objects.get(user=obj.user)
+                    user = Evaluateur.objects.get(user=request.user)
                 except Evaluateur.DoesNotExist:       
                     return qs.none()       
                 """
@@ -288,7 +339,7 @@ class ProxyCandidatAdmin(CandidatAdmin):
                         'nombre_dependant', 'telephone', 'email', 'adresse', 
                         'ville', 'etat_province', 'code_postal', 'pays', 
                         'niveau_diplome', 'employeur_actuel', 'poste_actuel',
-                        'domaine_professionnel',)
+                        'domaine_professionnel', 'pieces_jointes',)
     fieldsets = (
         ("Offre d'emploi", {
             'fields': ('offre_emploi', )
@@ -304,10 +355,18 @@ class ProxyCandidatAdmin(CandidatAdmin):
         ('Informations professionnelles', {
             'fields': ('niveau_diplome','employeur_actuel', 
                         'poste_actuel', 'domaine_professionnel',)
-        }),  
+        }),   
     )
     inlines = []
 
+    def response_change(self, request, obj):
+        response = super(ProxyCandidatAdmin, self).response_change(request, obj)
+        user_groupes = request.user.groups.all()
+        if grp_drh_recrutement in user_groupes or \
+            request.user.is_superuser:
+            return HttpResponseRedirect(reverse('admin:recrutement_candidat_changelist'))
+        return HttpResponseRedirect(reverse('admin:recrutement_proxycandidat_changelist'))
+
     def has_add_permission(self, request):
         return False
 
@@ -318,7 +377,8 @@ class ProxyCandidatAdmin(CandidatAdmin):
         user_groupes = request.user.groups.all()
         if grp_drh_recrutement in user_groupes or \
             grp_administrateurs_recrutement in user_groupes or \
-            grp_evaluateurs_recrutement in user_groupes:
+            grp_evaluateurs_recrutement in user_groupes or \
+            request.user.is_superuser:
             return True
         return False   
 
@@ -340,15 +400,24 @@ class CandidatPieceAdmin(admin.ModelAdmin):
 
 class EvaluateurAdmin(VersionAdmin):
     fieldsets = (
-        (None, {'fields': ('user', )}),
-        #(None, {'fields': ('candidats',)}),
+        ("Utilisateur", {
+            'fields': ('user',)
+        }),
+        ("Offres d'emploi à évaluer", {
+            'fields': ('offres_emploi',)
+        }),
     )
 
+    def get_actions(self, request):
+        actions = super(EvaluateurAdmin, self).get_actions(request)
+        del actions['delete_selected']
+        return actions
+
 class AdministrateurRegionalAdmin(VersionAdmin):
     pass
 
 class CandidatEvaluationAdmin(VersionAdmin):
-    list_display = ('_offre_emploi', '_candidat', 'evaluateur', '_note', 
+    list_display = ('_candidat', '_offre_emploi', 'evaluateur', '_note', 
                     '_commentaire', )
     readonly_fields = ('candidat', 'evaluateur')
     fieldsets = (
@@ -357,6 +426,11 @@ class CandidatEvaluationAdmin(VersionAdmin):
         }),
     )
 
+    def get_actions(self, request):
+        actions = super(CandidatEvaluationAdmin, self).get_actions(request)
+        del actions['delete_selected']
+        return actions
+
     def _note(self, obj):
         """
         Si l'évaluateur n'a pas encore donné de note au candidat, indiquer
@@ -405,7 +479,6 @@ class CandidatEvaluationAdmin(VersionAdmin):
             args=(obj.candidat.offre_emploi.id,)), obj.candidat.offre_emploi)
     _offre_emploi.allow_tags = True
     _offre_emploi.short_description = "Voir offre d'emploi"
-    _offre_emploi.admin_order_field = 'offre_emploi'
     
     def has_change_permission(self, request, obj=None):
         """
@@ -422,7 +495,8 @@ class CandidatEvaluationAdmin(VersionAdmin):
         """
         qs = self.model._default_manager.get_query_set()
         user_groupes = request.user.groups.all()
-        if grp_drh_recrutement in user_groupes:
+        if grp_drh_recrutement in user_groupes  or \
+            request.user.is_superuser:
             return qs.select_related('offre_emploi')
 
         try:
@@ -438,7 +512,10 @@ class CandidatEvaluationAdmin(VersionAdmin):
                 filter(id__in=candidats_evaluations_ids)
 
 class CourrielTemplateAdmin(VersionAdmin):
-    pass
+    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)
diff --git a/project/recrutement/api.py b/project/recrutement/api.py
new file mode 100644 (file)
index 0000000..e8ca95a
--- /dev/null
@@ -0,0 +1,143 @@
+# -*- encoding: utf-8 -*
+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
+
+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 *
+
+STATUS_OK = 200
+
+STATUS_ERROR = 400
+STATUS_ERROR_NOT_FOUND = 404
+STATUS_ERROR_PERMISSIONS = 403
+STATUS_ERROR_BADMETHOD = 405
+
+def api(request, method):
+    # 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 hasattr(api, 'api_%s' % method):
+        return getattr(api, 'api_%s' % method)()
+    
+    return api_return(STATUS_ERROR)
+
+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):
+        vars = dict()
+        offre_emploi = get_object_or_404(emploi.OffreEmploi, id=self.request.GET.get('id'))
+        cand = emploi.Candidat()
+        cand.offre_emploi = offre_emploi
+
+        if self.request.method == "POST":
+            form = emploiForms.PostulerOffreEmploiForm(self.request.POST,
+                     instance=cand, offre_emploi=offre_emploi)
+            piecesForm = emploiForms.CandidatPieceForm(self.request.POST, self.request.FILES,
+                        instance=cand)
+            
+            if form.is_valid() and piecesForm.is_valid():
+                offre = form.save()
+                piecesForm.instance = offre
+                piecesForm.save() 
+         
+                """courriel_template = CourrielTemplate.objects.\
+                            get(id=1)
+                send_templated_email(cand, courriel_template)
+                """
+                evaluateurs = offre_emploi.evaluateurs.all()
+                for evaluateur in evaluateurs:                
+                    candidat_evaluation = CandidatEvaluation()
+                    candidat_evaluation.candidat = cand
+                    candidat_evaluation.evaluateur = evaluateur
+                    candidat_evaluation.save()
+
+                return api_return(STATUS_OK)
+            else:
+                messages.add_message(self.request, messages.ERROR,
+                            'Il y a des erreurs dans le formulaire.')
+        else:
+            form = emploiForms.PostulerOffreEmploiForm(instance=cand,
+                    offre_emploi=offre_emploi)
+            piecesForm = emploiForms.CandidatPieceForm(instance=cand)
+
+        vars.update(dict(form=form, candidat=cand, piecesForm=piecesForm, ))
+     
+        return render_to_response('recrutement/postuler_appel_offre.html', vars, 
+                RequestContext(self.request))
+
+
+    def api_offre_emploi_liste(self):
+        return api_return(STATUS_OK, simplejson.dumps(
+                            [{"id": "%s" % offre.id, 
+                            "est_affiche": "%s" % offre.est_affiche,
+                            "statut": "%s" % offre.statut,
+                            "nom": "%s" % offre.nom, 
+                            "resume": "%s" % offre.resume,
+                            "description": "%s" % offre.description,
+                            "poste_nom": "%s" % offre.poste_nom,
+                            "region": "%s" % offre.region.id,
+                            "bureau": "%s" % offre.bureau.id,
+                            "date_limite": "%s" % offre.date_limite,
+                            "duree_affectation": "%s" % offre.duree_affectation,
+                            "renumeration": "%s" % offre.renumeration,
+                            "debut_affectation": "%s" % offre.debut_affectation,
+                            "lieu_affectation": "%s" % offre.lieu_affectation.id} 
+                            for offre in emploi.OffreEmploi.objects.all()]), json=True)
+        
+    def api_offre_emploi(self):
+        try:
+            offre = emploi.OffreEmploi.objects.get(id=self.request.GET.get('id'))
+        except emploi.OffreEmploi.DoesNotExist:
+            return api_return(STATUS_ERROR, "ID d'offre d'emploi invalide")
+        return api_return(STATUS_OK, simplejson.dumps(
+            {"id": "%s" % offre.id,
+            "est_affiche": "%s" % offre.est_affiche,
+            "statut": "%s" % offre.statut,
+            "nom": "%s" % offre.nom, 
+            "resume": "%s" % offre.resume,
+            "description": "%s" % offre.description,
+            "poste_nom": "%s" % offre.poste_nom,
+            "region": "%s" % offre.region.id,
+            "bureau": "%s" % offre.bureau.id,
+            "date_limite": "%s" % offre.date_limite,
+            "duree_affectation": "%s" % offre.duree_affectation,
+            "renumeration": "%s" % offre.renumeration,
+            "debut_affectation": "%s" % offre.debut_affectation,
+            "lieu_affectation": "%s" % offre.lieu_affectation.id} ), json=True)
+
index 298f922..2c54120 100644 (file)
@@ -8,15 +8,16 @@ 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
+from django.forms import ModelForm, ModelChoiceField, HiddenInput, CharField
 from django.forms.models import BaseInlineFormSet 
+from django.core.mail import send_mail
 
 from tinymce.widgets import TinyMCE
 from captcha.fields import CaptchaField
 
 from recrutement import models as recr
-from django.core.mail import send_mail
-#from recrutement.lib import send_templated_mail
+from auf.django.emploi import forms as emploi
+from project.rh import models as rh
 
 ################################################################################
 # EVALUATION
@@ -39,48 +40,61 @@ class EvaluateurForm(forms.Form):
                     recr.Evaluateur.objects.all())
 
     def __init__(self, *args, **kwargs):
-        self.candidats = kwargs.pop('candidats')
+        self.offres_emploi = kwargs.pop('offres_emploi')
         super(EvaluateurForm, self).__init__(*args, **kwargs)
 
     def save(self):
-        for candidat in self.candidats:
+        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(inlineformset_factory(recr.Candidat,
-                        recr.CandidatPiece)):
-    nom = forms.MultipleChoiceField(choices=recr.TYPE_PIECE_CHOICES,
-            widget=CheckboxSelectMultiple)
+class CandidatPieceForm(emploi.CandidatPieceForm):
+    pass
 
-class PostulerOffreEmploiForm(ModelForm):
-    captcha = CaptchaField()
+class PostulerOffreEmploiForm(emploi.PostulerOffreEmploiForm):
+    pass
 
-    def __init__(self, *args, **kwargs):
-        self.offre_emploi = kwargs.pop('offre_emploi')     
-        super(PostulerOffreEmploiForm, self).__init__(*args, **kwargs)
+class OffreEmploiForm(ModelForm):
+    poste = ModelChoiceField(queryset=rh.Poste.objects.all())
 
-    def save(self, *args, **kwargs): 
+    class Meta:
+        model = recr.OffreEmploi  
+
+    def save(self, *args, **kwargs):
         kwargs2 = kwargs.copy()
         kwargs2['commit'] = False
-        postulation = super(PostulerOffreEmploiForm, self).save(*args, **kwargs2)
+        offre = super(OffreEmploiForm, self).save(*args, **kwargs2)
+        offre.poste = self.cleaned_data.get("poste")
+        offre.poste_nom = self.cleaned_data.get("poste").nom
         if 'commit' not in kwargs or kwargs['commit']:
-            postulation.save()
-        return postulation
-
-    class Meta:
-        model = recr.Candidat   
-        exclude = ('actif', 'offre_emploi',)
-        fields = ('nom', 'prenom', 'genre', 'nationalite', 'situation_famille', 
-                    'nombre_dependant', 'niveau_diplome', 'employeur_actuel', 
-                    'poste_actuel', 'domaine_professionnel', 'telephone', 
-                    'email', 'adresse', 'ville', 'code_postal', 'etat_province',
-                    'pays', 'captcha', )
+            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.")
+
+        """if date_limite < datetime.date.today() or \
+            debut_affectation < datetime.date.today():
+            raise forms.ValidationError("La date limite et/ou la date \
+                d'affection doivent être supérieures à la date d'aujourdhui.")
+        """
+        return cleaned_data
 
 ################################################################################
 # TEMPLATE COURRIEL
index 94a4e2c..3b30f2b 100755 (executable)
@@ -6,10 +6,13 @@ from django.core.files.storage import FileSystemStorage
 from tinymce import models as tinymce_models
 from django.db import models
 import settings
-#from private_files import PrivateFileField
 
 import datamaster_modeles.models as ref
-from project.rh.models import Poste 
+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
@@ -23,7 +26,8 @@ HELP_TEXT_NB_DEPENDANT = "Le nombre de personnes à charge"
 HELP_TEXT_FORMAT_DATE = "Le format de la date est AAAA-MM-JJ"
 HELP_TEXT_TAGS_ACCEPTES = "Pour le texte, les variables disponibles sont : \
                             {{ nom_candidat }} {{ prenom_candidat }} \
-                            {{ offre_emploi }}. Ces champs seront \
+                            {{ offre_emploi }} et {{ genre_candidat }} \
+                            (Pour Monsieur/Madame). Ces champs seront \
                             automatiquement remplacés par les informations de \
                             chaque candidat."
 
@@ -40,59 +44,26 @@ class Metadata(models.Model):
     class Meta:
         abstract = True
 
-class OffreEmploiManager(models.Manager):
-    def get_query_set(self):
-        fkeys = ('region',)
-        return super(OffreEmploiManager, self).get_query_set().\
-                    select_related(*fkeys).all()
-
-class ProxyPoste(Poste):
+class Candidat(emploi.Candidat):
     class Meta:
         proxy = True
 
-    def __unicode__(self):
-        return '%s [%s]' % (self.nom, self.id)
-
-STATUT_OFFRE_EMPLOI_CHOICES = (
-    ('NOUV', 'Nouveau'),
-    ('AFFI', 'Offre d\'emploi en affichage'),
-    ('EVAL', 'En évaluation des candidatures'),
-    ('ENTR', 'En entrevue'),
-    ('TERM', 'Terminé'),
-)
+class OffreEmploi(emploi.OffreEmploi):
+    class Meta:
+        proxy = True
 
-class OffreEmploi(Metadata):
-    #objects = OffreEmploiManager()
-    est_affiche = models.BooleanField(default=False, 
-                                    verbose_name="En affichage sur le site")
-    statut = models.CharField(max_length=4, choices=STATUT_OFFRE_EMPLOI_CHOICES,
-                                default='NOUV')
-    nom = models.CharField(max_length=255)
-    resume = models.TextField(verbose_name="Résumé")
-    description = tinymce_models.HTMLField()
-    poste = models.ForeignKey(ProxyPoste, db_column='poste')
-    date_limite = models.DateField(verbose_name="Date limite",
-                        help_text=HELP_TEXT_FORMAT_DATE,)  
-    region = models.ForeignKey(ref.Region, db_column='region', 
-                verbose_name="Région")
-    bureau = models.ForeignKey(ref.Bureau, db_column='bureau', )
-    duree_affectation = models.CharField(max_length=255, 
-                        verbose_name="Durée de l'affectation")
-    renumeration = models.CharField(max_length=255,
-                    verbose_name='Rénumération')
-    debut_affectation = models.DateField(verbose_name="Début de l'affectation",
-                        help_text=HELP_TEXT_FORMAT_DATE,)
-    lieu_affectation = models.ForeignKey(ref.Implantation,  
-                        db_column='implantation', 
-                        verbose_name="Lieu d'affectation")
 
+class CandidatPiece(emploi.CandidatPiece):
     class Meta:
-        verbose_name_plural = "offres d'emploi"
+        proxy = True
 
-    def __unicode__(self):
-        return '%s [%s]' % (self.nom, self.id)
-class ProxyOffreEmploi(OffreEmploi):
+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 = "Offre d'emploi (visualisation)"
@@ -100,64 +71,8 @@ class ProxyOffreEmploi(OffreEmploi):
 
     def __unicode__(self):
         return '%s [%s] - View' % (self.nom, self.id)
-### CANDIDAT
-  
-GENRE_CHOICES = (
-    ('M', 'Homme'),
-    ('F', 'Femme'),
-)
-SITUATION_CHOICES = (
-    ('C', 'Célibataire'),
-    ('F', 'Conjoint de fait'),
-    ('M', 'Marié'),
-    ('D', 'Divorcé'),
-)
-STATUT_CHOICES = (
-    ('NOUV', 'Nouveau'),
-    ('REC', 'Recevable'),
-    ('SEL', 'Sélectionné'),
-    ('REF', 'Refusé'),
-    ('ACC', 'Accepté'),
-)
 
-class Candidat(Metadata):   
-    statut = models.CharField(max_length=4, choices=STATUT_CHOICES, 
-                default='NOUV')
-    offre_emploi = models.ForeignKey('OffreEmploi', db_column='offre_emploi',
-                    related_name='+')
-    prenom = models.CharField(max_length=255, verbose_name='Prénom', )
-    nom = models.CharField(max_length=255)
-    genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
-    nationalite = models.ForeignKey(ref.Pays, 
-                    db_column='nationalite', related_name='+',
-                    verbose_name='Nationalité')
-    situation_famille = models.CharField(max_length=1, 
-                        choices=SITUATION_CHOICES, 
-                        verbose_name='Situation familiale', )
-    nombre_dependant = models.IntegerField(verbose_name='Nombre de dépendant',
-                        help_text=HELP_TEXT_NB_DEPENDANT, )
-    niveau_diplome = models.CharField(max_length=255,
-                        verbose_name='Niveau du diplôme')
-    employeur_actuel = models.CharField(max_length=255, )
-    poste_actuel = models.CharField(max_length=255, )
-    domaine_professionnel = models.CharField(max_length=255, )
-    telephone = models.CharField(max_length=255, verbose_name='Téléphone', )
-    email = models.EmailField(max_length=255, verbose_name = 'Courriel', )
-
-    # Adresse
-    adresse = models.CharField(max_length=255)
-    ville = models.CharField(max_length=255)
-    etat_province = models.CharField(max_length=255, 
-                    verbose_name="État/Province")
-    code_postal = models.CharField(max_length=255, blank=True)
-    pays = models.ForeignKey(ref.Pays, db_column='pays',
-            related_name='+')
-
-    def __unicode__(self):
-        return '%s %s [%s]' % (self.prenom, self.nom, self.id)
-
-class ProxyCandidat(Candidat):
+class ProxyCandidat(emploi.Candidat):
     class Meta:
         proxy = True
         verbose_name = "Candidat (visualisation)"
@@ -166,44 +81,19 @@ class ProxyCandidat(Candidat):
     def __unicode__(self):
         return '%s %s [%s]' % (self.prenom, self.nom, self.id)
 
-### PIECE CANDIDAT
-
-TYPE_PIECE_CHOICES = (
-    ('CV','CV'),
-    ('LET','Lettre'),
-    ('AUT','Autre'),
-)
-# Upload de fichiers
-storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, 
-                            base_url=settings.PRIVE_MEDIA_URL)
-
-def candidat_piece_dispatch(instance, filename):
-    path = "offre_emploi/%s_%s/%s/%s_%s" % (instance.candidat.nom, 
-                instance.candidat.prenom, instance.nom, instance.candidat.id,
-                filename)
-    return path
-
-class CandidatPiece(models.Model):
-    candidat = models.ForeignKey(Candidat, db_column='candidat',
-                related_name='candidat_piece') 
-    nom = models.CharField(max_length=3, choices=TYPE_PIECE_CHOICES)
-    #path = PrivateFileField("file", upload_to=candidat_piece_dispatch)
-    path = models.FileField(verbose_name="Fichier", 
-            upload_to=candidat_piece_dispatch, 
-            storage=storage_prive, )
-
-    class Meta:
-        verbose_name = "pièce jointe"
-        verbose_name_plural = "pièces jointes"
-
-    def __unicode__(self):
-        return '%s' % (self.nom)
-
 class Evaluateur(models.Model):
     user = models.ForeignKey(User, unique=True, verbose_name="Évaluateur")
-    candidats = models.ManyToManyField(Candidat, through='CandidatEvaluation', 
-                related_name="evaluateurs")
-
+    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 = "évaluateur"
 
@@ -224,12 +114,12 @@ class AdministrateurRegional(models.Model):
         return '%s %s' % (self.user.first_name, self.user.last_name)
 
 class CandidatEvaluation(models.Model):
-    candidat = models.ForeignKey(Candidat, db_column='candidat', 
+    candidat = models.ForeignKey(emploi.Candidat, db_column='candidat', 
                 related_name='+',) 
     evaluateur = models.ForeignKey(Evaluateur, db_column='evaluateur', 
                     related_name='+', verbose_name='Évaluateur') 
     note = models.IntegerField(choices=NOTES, blank=True, null=True)
-    commentaire = models.TextField(null=True, blank=True)
+    commentaire = models.TextField(null=True, blank=True, default='Aucun')
     date = models.DateField(auto_now_add=True,
                         help_text=HELP_TEXT_FORMAT_DATE, )  
 
@@ -270,4 +160,8 @@ class CandidatCourriel(models.Model):
 
     def __unicode__(self):
         return '%s' % (self.titre)
+
+    class Meta:
+        verbose_name = "modèle de courriel"
+        verbose_name_plural = "modèles de courriel"
     
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..bdc3016
--- /dev/null
@@ -0,0 +1,77 @@
+{% 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="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 %}
+        <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 %}
+
+{% 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 %}
index f231760..f7d59bf 100644 (file)
@@ -1,7 +1,27 @@
 # -*- encoding: utf-8 -*
 from django.conf.urls.defaults import patterns, url
 
-urlpatterns = patterns(
-    'project.recrutement.views',
+
+
+
+urlpatterns = patterns('',
     url(r'^$', 'index', name='index'),
+
+    url(r'^affecter_evaluateurs_offre_emploi/$', 
+        'recrutement.views.affecter_evaluateurs_offre_emploi', 
+        name='affecter_evaluateurs_offre_emploi'),
+
+    url(r'^envoyer_courriel_candidats/$', 
+        'recrutement.views.envoyer_courriel_candidats', 
+        name='envoyer_courriel_candidats'),
+
+    url(r'^selectionner_template/$', 
+        'recrutement.views.selectionner_template', 
+        name='selectionner_template'),
+
+    url(r'^pieces/$', 'recrutement.views.postuler_appel_offre', 
+        name='pieces'),
+
+    url(r'^postuler_appel_offre/$', 
+        'recrutement.views.postuler_appel_offre', name='postuler_appel_offre'),   
 )
index f1a9b30..65753e5 100755 (executable)
@@ -11,30 +11,13 @@ from django.core.mail import EmailMultiAlternatives
 
 from forms import *
 from models import *
-from project.recrutement import models as recr
 from recrutement.workflow import grp_evaluateurs_recrutement
+from views import *
 
 def index(request):
     return render_to_response('recrutement/index.html', {}, 
                                 RequestContext(request))
 
-def affecter_evaluateurs_candidats(request):
-    candidat_ids = request.GET.get('ids').split(',')
-    candidats = Candidat.objects.filter(id__in=candidat_ids)
-    if request.method == "POST":
-        form = EvaluateurForm(request.POST, candidats=candidats)
-        if form.is_valid():
-            form.save()
-            messages.add_message(request, messages.SUCCESS, 
-                            "Les évaluateurs ont été affectés aux candidats.")
-            return redirect("admin:recrutement_candidat_changelist")
-    else:
-        form = EvaluateurForm(candidats=candidats)
-
-    c = {'form' : form}   
-    return render_to_response("recrutement/affecter_evaluateurs.html", 
-            Context(c), context_instance = RequestContext(request))
-
 def selectionner_template(request):
     candidat_ids = request.GET.get('ids')
     if request.method == "POST":
@@ -102,7 +85,7 @@ def postuler_appel_offre(request):
      
             courriel_template = CourrielTemplate.objects.\
                         get(nom_modele='Confirmation postulation (automatique)')
-            send_templated_email(candidat, courriel_template)
+            emp.send_templated_email(candidat, courriel_template)
      
             messages.add_message(request, messages.SUCCESS, 
                             "Votre application à l'appel d'offre d'emploi a \
@@ -124,16 +107,15 @@ def postuler_appel_offre(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)
-    candidats = Candidat.objects.filter(offre_emploi__in=offres_emploi)
     if request.method == "POST":
-        form = EvaluateurForm(request.POST, candidats=candidats)
+        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 candidats.")
+                        "Les évaluateurs ont été affectés aux offres d'emploi.")
             return redirect("admin:recrutement_offreemploi_changelist")
     else:
-        form = EvaluateurForm(candidats=candidats)
+        form = EvaluateurForm(offres_emploi=offres_emploi)
 
     c = {'form' : form}   
     return render_to_response("recrutement/affecter_evaluateurs.html", 
@@ -148,7 +130,10 @@ def send_templated_email(candidat, template):
     texte_template = Template(template.plain_text)
     dict_texte = {"nom_candidat": candidat.nom, 
                     "prenom_candidat": candidat.prenom, 
-                    "offre_emploi": candidat.offre_emploi.nom,}
+                    "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)
index 6ba68d9..8f46b73 100644 (file)
@@ -61,6 +61,7 @@ INSTALLED_APPS = (
     'django.contrib.messages',
     'django.contrib.sessions',
     'django.contrib.admin',
+    'auf.django.emploi',
     'auf.django.admingroup',
     'ajax_select',
     'south',
index 2ee2a78..43b6bb6 100644 (file)
@@ -2,6 +2,7 @@
 from django.conf.urls.defaults import patterns, include, handler500, url
 from django.conf import settings
 from django.contrib import admin
+from auf.django.emploi import settings as sett
 
 admin.autodiscover()
 
@@ -11,7 +12,10 @@ urlpatterns = patterns(
     '',
     # système
     url(r'^$', 'project.views.index', name='accueil'),
+
     (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'),
     #url(r'^private_files/', include('private_files.urls')),
@@ -26,23 +30,7 @@ urlpatterns = patterns(
 
     # apps
     (r'^dae/', include('project.dae.urls')),
-    (r'^recrutement/', include('project.recrutement.urls')),
-    url(r'^recrutement/affecter_evaluateurs_candidats/$', 
-        'recrutement.views.affecter_evaluateurs_candidats', 
-        name='affecter_evaluateurs_candidats'),
-    url(r'^recrutement/affecter_evaluateurs_offre_emploi/$', 
-        'recrutement.views.affecter_evaluateurs_offre_emploi', 
-        name='affecter_evaluateurs_offre_emploi'),
-    url(r'^recrutement/envoyer_courriel_candidats/$', 
-        'recrutement.views.envoyer_courriel_candidats', 
-        name='envoyer_courriel_candidats'),
-    url(r'^recrutement/selectionner_template/$', 
-        'recrutement.views.selectionner_template', 
-        name='selectionner_template'),
-    url(r'^recrutement/pieces/$', 'recrutement.views.postuler_appel_offre', 
-        name='pieces'),
-    url(r'^recrutement/postuler_appel_offre/$', 
-        'recrutement.views.postuler_appel_offre', name='postuler_appel_offre'),
+    (r'^recrutement/', include('recrutement.urls')),
     (r'^', include('project.rh.urls')),
 )
 
diff --git a/src/auf.django.emploi/.gitignore b/src/auf.django.emploi/.gitignore
new file mode 100644 (file)
index 0000000..499a075
--- /dev/null
@@ -0,0 +1,4 @@
+*.pyc
+*egg-info
+build
+dist
diff --git a/src/auf.django.emploi/CHANGES b/src/auf.django.emploi/CHANGES
new file mode 100644 (file)
index 0000000..218fcb3
--- /dev/null
@@ -0,0 +1,7 @@
+auf.django.emploi
+===================
+
+0.1
+---
+
+* Création du module : 
diff --git a/src/auf.django.emploi/MANIFEST.in b/src/auf.django.emploi/MANIFEST.in
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/auf.django.emploi/README b/src/auf.django.emploi/README
new file mode 100644 (file)
index 0000000..0143f2d
--- /dev/null
@@ -0,0 +1,3 @@
+auf.django.emploi
+===================
+
diff --git a/src/auf.django.emploi/auf/__init__.py b/src/auf.django.emploi/auf/__init__.py
new file mode 100644 (file)
index 0000000..35cf25b
--- /dev/null
@@ -0,0 +1,5 @@
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except:
+    # bootstrapping
+    pass
diff --git a/src/auf.django.emploi/auf/django/__init__.py b/src/auf.django.emploi/auf/django/__init__.py
new file mode 100644 (file)
index 0000000..35cf25b
--- /dev/null
@@ -0,0 +1,5 @@
+try:
+    __import__('pkg_resources').declare_namespace(__name__)
+except:
+    # bootstrapping
+    pass
diff --git a/src/auf.django.emploi/auf/django/emploi/__init__.py b/src/auf.django.emploi/auf/django/emploi/__init__.py
new file mode 100755 (executable)
index 0000000..e69de29
diff --git a/src/auf.django.emploi/auf/django/emploi/api.py b/src/auf.django.emploi/auf/django/emploi/api.py
new file mode 100644 (file)
index 0000000..5a7e778
--- /dev/null
@@ -0,0 +1,68 @@
+# -*- encoding: utf-8 -*
+from django.shortcuts import render_to_response, redirect, get_object_or_404
+from django.template import Context, RequestContext
+
+from django.utils import simplejson
+from auf.django.emploi import models as emploi
+from auf.django.emploi import forms as emploiForms
+from restkit import request as req
+import datamaster_modeles.models as ref
+
+class API:
+    def __init__(self, request):
+        self.request = request
+
+    def offre_emploi_liste(self):
+        url = "http://127.0.0.1:8000/api/offre_emploi_liste/"
+        r = req(url)
+        liste_json = r.body_string()
+        liste_offres = simplejson.loads(liste_json)
+        obj_offres_emploi = []
+
+        for offre_dict in liste_offres:
+            offre = emploi.OffreEmploi()
+            offre.est_affiche = offre_dict['est_affiche']
+            offre.statut = offre_dict['statut']
+            offre.nom = offre_dict['nom']
+            offre.resume = offre_dict['resume']
+            offre.description = offre_dict['description']
+            offre.poste_nom = offre_dict['poste_nom']
+            offre.date_limite = offre_dict['date_limite'] 
+            offre.region = ref.Region.objects.get(id=offre_dict['region'])
+            offre.bureau = ref.Bureau.objects.get(id=offre_dict['bureau'])
+            offre.duree_affectation = offre_dict['duree_affectation']
+            offre.renumeration = offre_dict['renumeration']
+            offre.debut_affectation = offre_dict['debut_affectation']
+            offre.lieu_affectation = ref.Implantation.objects.get(id=offre_dict['lieu_affectation'])
+            obj_offres_emploi.append(offre)  
+        return obj_offres_emploi
+
+    def offre_emploi(self, offre_id):
+        url = "http://127.0.0.1:8000/api/offre_emploi/?id=%s"
+        r = req(url % (offre_id))
+        offre_json = r.body_string()
+        offre_dict = simplejson.loads(offre_json)
+        obj_offres_emploi = []
+
+        offre = emploi.OffreEmploi()
+        offre.est_affiche = offre_dict['est_affiche']
+        offre.statut = offre_dict['statut']
+        offre.nom = offre_dict['nom']
+        offre.resume = offre_dict['resume']
+        offre.description = offre_dict['description']
+        offre.poste_nom = offre_dict['poste_nom']
+        offre.date_limite = offre_dict['date_limite']
+        offre.region = ref.Region.objects.get(id=offre_dict['region'])
+        offre.bureau = ref.Bureau.objects.get(id=offre_dict['bureau'])
+        offre.duree_affectation = offre_dict['duree_affectation']
+        offre.renumeration = offre_dict['renumeration']
+        offre.debut_affectation = offre_dict['debut_affectation']
+        offre.lieu_affectation = ref.Implantation.objects.get(id=offre_dict['lieu_affectation'])
+        obj_offres_emploi.append(offre)
+        return obj_offres_emploi
+
+    def candidat_add(self, offre_id):
+        url = "http://127.0.0.1:8000/api/candidat_add/?id=%s" % (offre_id)
+        return redirect(url)
+
+        
diff --git a/src/auf.django.emploi/auf/django/emploi/forms.py b/src/auf.django.emploi/auf/django/emploi/forms.py
new file mode 100644 (file)
index 0000000..7f963cc
--- /dev/null
@@ -0,0 +1,42 @@
+# -*- encoding: utf-8 -*-
+
+from django import forms
+from django.forms.models import inlineformset_factory
+from django.forms.widgets import CheckboxSelectMultiple
+from django.forms import ModelForm
+
+from captcha.fields import CaptchaField
+
+from auf.django.emploi import models as emploi
+
+################################################################################
+# OFFRE EMPLOI
+################################################################################
+class CandidatPieceForm(inlineformset_factory(emploi.Candidat,
+                        emploi.CandidatPiece)):
+    nom = forms.MultipleChoiceField(choices=emploi.TYPE_PIECE_CHOICES,
+            widget=CheckboxSelectMultiple)
+
+class PostulerOffreEmploiForm(ModelForm):
+    captcha = CaptchaField()
+
+    def __init__(self, *args, **kwargs):
+        self.offre_emploi = kwargs.pop('offre_emploi')     
+        super(PostulerOffreEmploiForm, self).__init__(*args, **kwargs)
+
+    def save(self, *args, **kwargs): 
+        kwargs2 = kwargs.copy()
+        kwargs2['commit'] = False
+        postulation = super(PostulerOffreEmploiForm, self).save(*args, **kwargs2)
+        if 'commit' not in kwargs or kwargs['commit']:
+            postulation.save()
+        return postulation
+
+    class Meta:
+        model = emploi.Candidat   
+        exclude = ('actif', 'offre_emploi',)
+        fields = ('nom', 'prenom', 'genre', 'nationalite', 'situation_famille', 
+                    'nombre_dependant', 'niveau_diplome', 'employeur_actuel', 
+                    'poste_actuel', 'domaine_professionnel', 'telephone', 
+                    'email', 'adresse', 'ville', 'code_postal', 'etat_province',
+                    'pays', 'captcha', )
diff --git a/src/auf.django.emploi/auf/django/emploi/models.py b/src/auf.django.emploi/auf/django/emploi/models.py
new file mode 100755 (executable)
index 0000000..8fa2e92
--- /dev/null
@@ -0,0 +1,169 @@
+# -*- encoding: utf-8 -*
+
+import datetime
+from django.core.files.storage import FileSystemStorage
+from tinymce import models as tinymce_models
+from django.db import models
+import settings
+
+import datamaster_modeles.models as ref
+
+### CONSTANTES ###
+# HELP_TEXT
+HELP_TEXT_NB_DEPENDANT = "Le nombre de personnes à charge"
+HELP_TEXT_FORMAT_DATE = "Le format de la date est AAAA-MM-JJ"
+HELP_TEXT_TAGS_ACCEPTES = "Pour le texte, les variables disponibles sont : \
+                            {{ nom_candidat }} {{ prenom_candidat }} \
+                            {{ offre_emploi }}. Ces champs seront \
+                            automatiquement remplacés par les informations de \
+                            chaque candidat."
+
+
+STATUT_OFFRE_EMPLOI_CHOICES = (
+    ('NOUV', 'Nouveau'),
+    ('AFFI', 'Offre d\'emploi en affichage'),
+    ('EVAL', 'En évaluation des candidatures'),
+    ('ENTR', 'En entrevue'),
+    ('TERM', 'Terminé'),
+)
+
+# CANDIDAT
+GENRE_CHOICES = (
+    ('M', 'Homme'),
+    ('F', 'Femme'),
+)
+SITUATION_CHOICES = (
+    ('C', 'Célibataire'),
+    ('F', 'Conjoint de fait'),
+    ('M', 'Marié'),
+    ('D', 'Divorcé'),
+)
+STATUT_CHOICES = (
+    ('NOUV', 'Nouveau'),
+    ('REC', 'Recevable'),
+    ('SEL', 'Sélectionné'),
+    ('REF', 'Refusé'),
+    ('ACC', 'Accepté'),
+)
+
+# PIECE CANDIDAT
+TYPE_PIECE_CHOICES = (
+    ('CV','CV'),
+    ('LET','Lettre'),
+    ('AUT','Autre'),
+)
+
+# 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,
+                        help_text=HELP_TEXT_FORMAT_DATE, )
+    
+    class Meta:
+        abstract = True
+
+class OffreEmploi(Metadata):
+    est_affiche = models.BooleanField(default=False, 
+                                    verbose_name="En affichage sur le site")
+    statut = models.CharField(max_length=4, choices=STATUT_OFFRE_EMPLOI_CHOICES,
+                                default='NOUV')
+    nom = models.CharField(max_length=255)
+    resume = models.TextField(verbose_name="Résumé")
+    description = models.TextField()
+    poste = models.CharField(max_length=255)
+    poste_nom = models.CharField(max_length=255)
+    date_limite = models.DateField(verbose_name="Date limite",
+                        help_text=HELP_TEXT_FORMAT_DATE,)  
+    region = models.ForeignKey(ref.Region, db_column='region', 
+                verbose_name="Région")
+    bureau = models.ForeignKey(ref.Bureau, db_column='bureau', )
+    duree_affectation = models.CharField(max_length=255, 
+                        verbose_name="Durée de l'affectation")
+    renumeration = models.CharField(max_length=255,
+                    verbose_name='Rénumération')
+    debut_affectation = models.DateField(verbose_name="Début de l'affectation",
+                        help_text=HELP_TEXT_FORMAT_DATE,)
+    lieu_affectation = models.ForeignKey(ref.Implantation,  
+                        db_column='implantation', 
+                        verbose_name="Lieu d'affectation")
+
+    class Meta:
+        db_table = 'emploi_offreemploi'
+        verbose_name_plural = "offres d'emploi"
+
+    def __unicode__(self):
+        return '%s [%s]' % (self.nom, self.id)
+
+class Candidat(Metadata):   
+    statut = models.CharField(max_length=4, choices=STATUT_CHOICES, 
+                default='NOUV')
+    offre_emploi = models.ForeignKey('OffreEmploi', db_column='offre_emploi',
+                    related_name='+')
+    prenom = models.CharField(max_length=255, verbose_name='Prénom', )
+    nom = models.CharField(max_length=255)
+    genre = models.CharField(max_length=1, choices=GENRE_CHOICES)
+    nationalite = models.ForeignKey(ref.Pays, 
+                    db_column='nationalite', related_name='+',
+                    verbose_name='Nationalité')
+    situation_famille = models.CharField(max_length=1, 
+                        choices=SITUATION_CHOICES, 
+                        verbose_name='Situation familiale', )
+    nombre_dependant = models.IntegerField(verbose_name='Nombre de dépendant',
+                        help_text=HELP_TEXT_NB_DEPENDANT, )
+    niveau_diplome = models.CharField(max_length=255,
+                        verbose_name='Niveau du diplôme')
+    employeur_actuel = models.CharField(max_length=255, )
+    poste_actuel = models.CharField(max_length=255, )
+    domaine_professionnel = models.CharField(max_length=255, )
+    telephone = models.CharField(max_length=255, verbose_name='Téléphone', )
+    email = models.EmailField(max_length=255, verbose_name = 'Courriel', )
+
+    # Adresse
+    adresse = models.CharField(max_length=255)
+    ville = models.CharField(max_length=255)
+    etat_province = models.CharField(max_length=255, 
+                    verbose_name="État/Province")
+    code_postal = models.CharField(max_length=255, blank=True)
+    pays = models.ForeignKey(ref.Pays, db_column='pays',
+            related_name='+')
+
+    class Meta:
+        db_table = 'emploi_candidat'
+
+    def pieces_jointes(self):
+        return CandidatPiece.objects.filter(candidat=self) 
+    pieces_jointes.allow_tags = True    
+
+    def __unicode__(self):
+        return '%s %s [%s]' % (self.nom, self.prenom, self.id)
+
+
+# Upload de fichiers
+storage_prive = FileSystemStorage(settings.PRIVE_MEDIA_ROOT, 
+                            base_url=settings.PRIVE_MEDIA_URL)
+
+def candidat_piece_dispatch(instance, filename):
+    path = u'%s/%s/%s_%s_%s/%s/%s' % ('emplois', instance.candidat.offre_emploi.id,
+            instance.candidat.nom, instance.candidat.prenom, instance.candidat.id, 
+            instance.nom, filename)
+    return path
+
+class CandidatPiece(models.Model):
+    candidat = models.ForeignKey(Candidat, db_column='candidat',
+                related_name='candidat_piece') 
+    nom = models.CharField(max_length=3, choices=TYPE_PIECE_CHOICES)
+    path = models.FileField(verbose_name="Fichier", 
+                        upload_to=candidat_piece_dispatch, storage=storage_prive)
+
+    class Meta:
+        db_table = 'emploi_pieces'
+        verbose_name = "pièce jointe"
+        verbose_name_plural = "pièces jointes"
+
+    def __unicode__(self):
+        return '%s' % (self.nom)
diff --git a/src/auf.django.emploi/auf/django/emploi/settings.py b/src/auf.django.emploi/auf/django/emploi/settings.py
new file mode 100644 (file)
index 0000000..101b2a1
--- /dev/null
@@ -0,0 +1,17 @@
+# -*- encoding: utf-8 -*-
+
+import os
+from django.conf import settings
+
+# Absolute path to the directory that holds media.
+# Example: "/home/media/media.lawrence.com/"
+MEDIA_ROOT = getattr(settings, 'OE_MEDIA_ROOT', 
+                        os.path.join(os.path.dirname(__file__), 'media'))
+PRIVE_MEDIA_ROOT = getattr(settings, 'OE_PRIV_MEDIA_ROOT', 
+                        os.path.join(os.path.dirname(__file__), 'media_prive'))
+
+# URL that handles the media served from MEDIA_ROOT. Make sure to use a
+# trailing slash if there is a path component (optional in other cases).
+# Examples: "http://media.lawrence.com", "http://example.com/media/"
+MEDIA_URL = '/media/'
+PRIVE_MEDIA_URL = '/prive/'
diff --git a/src/auf.django.emploi/auf/django/emploi/templates/recrutement/pieces.html b/src/auf.django.emploi/auf/django/emploi/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/src/auf.django.emploi/auf/django/emploi/templates/recrutement/postuler_appel_offre.html b/src/auf.django.emploi/auf/django/emploi/templates/recrutement/postuler_appel_offre.html
new file mode 100644 (file)
index 0000000..53569f0
--- /dev/null
@@ -0,0 +1,135 @@
+{% extends 'base.html' %}
+{% load adminmedia %}
+
+{% block title %}RH{% endblock %}
+{% block titre %}Ressources humaines{% 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/src/auf.django.emploi/setup.cfg b/src/auf.django.emploi/setup.cfg
new file mode 100644 (file)
index 0000000..01bb954
--- /dev/null
@@ -0,0 +1,3 @@
+[egg_info]
+tag_build = dev
+tag_svn_revision = true
diff --git a/src/auf.django.emploi/setup.py b/src/auf.django.emploi/setup.py
new file mode 100644 (file)
index 0000000..daf3575
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- encoding: utf-8 -*-
+
+from setuptools import setup, find_packages
+import sys, os
+
+name = 'auf.django.emploi'
+version = '0.1'
+
+setup(name=name,
+      version=version,
+      description="Outils nécessaires pour la diffusion des offres d'emploi et \
+                    la soumissions des candidatures, dans l'application de \
+                    recrutement du système SGRH.",
+      long_description="""\
+""",
+      classifiers=[], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
+      keywords='',
+      author='Nilovna Bascunan-Vasquez',
+      author_email='contact@nilovna.com',
+      url='http://pypi.auf.org/%s' % name,
+      license='GPL',
+      packages=find_packages(exclude=['ez_setup', 'examples', 'tests']),
+      include_package_data=True,
+      zip_safe=False,
+      install_requires=[
+          # -*- Extra requirements: -*-
+      ],
+      entry_points="""
+      # -*- Entry points: -*-
+      """,
+      )