From: Nilovna Bascunan-Vasquez Date: Mon, 18 Jul 2011 18:00:16 +0000 (-0400) Subject: Ajout du package auf.django.emploi X-Git-Tag: 1.0~34 X-Git-Url: http://git.auf.org/?p=auf_django_emploi.git;a=commitdiff_plain;h=184b84de3b9dced475dcf04f59da74d653896a68 Ajout du package auf.django.emploi --- 184b84de3b9dced475dcf04f59da74d653896a68 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..499a075 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*.pyc +*egg-info +build +dist diff --git a/CHANGES b/CHANGES new file mode 100644 index 0000000..218fcb3 --- /dev/null +++ b/CHANGES @@ -0,0 +1,7 @@ +auf.django.emploi +=================== + +0.1 +--- + +* Création du module : diff --git a/MANIFEST.in b/MANIFEST.in new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..0143f2d --- /dev/null +++ b/README @@ -0,0 +1,3 @@ +auf.django.emploi +=================== + diff --git a/auf/__init__.py b/auf/__init__.py new file mode 100644 index 0000000..35cf25b --- /dev/null +++ b/auf/__init__.py @@ -0,0 +1,5 @@ +try: + __import__('pkg_resources').declare_namespace(__name__) +except: + # bootstrapping + pass diff --git a/auf/django/__init__.py b/auf/django/__init__.py new file mode 100644 index 0000000..35cf25b --- /dev/null +++ b/auf/django/__init__.py @@ -0,0 +1,5 @@ +try: + __import__('pkg_resources').declare_namespace(__name__) +except: + # bootstrapping + pass diff --git a/auf/django/emploi/__init__.py b/auf/django/emploi/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/auf/django/emploi/forms.py b/auf/django/emploi/forms.py new file mode 100644 index 0000000..8987fa5 --- /dev/null +++ b/auf/django/emploi/forms.py @@ -0,0 +1,71 @@ +# -*- encoding: utf-8 -*- + +import os +from django import forms +from django.contrib import admin +from django.forms.models import inlineformset_factory +from django.forms.widgets import CheckboxSelectMultiple +from django.forms import ModelForm + +from captcha.fields import CaptchaField + +from recrutement import models as recr + +################################################################################ +# OFFRE EMPLOI +################################################################################ +class CandidatPieceForm(inlineformset_factory(recr.Candidat, + recr.CandidatPiece)): + nom = forms.MultipleChoiceField(choices=recr.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 = 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', ) + +# TODO: Vérifier si on garde, pour l'envoi automatique d'un email lors de la +# postulation de l'offre d'emploi +################################################################################ +# 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/auf/django/emploi/models.py b/auf/django/emploi/models.py new file mode 100755 index 0000000..75d8338 --- /dev/null +++ b/auf/django/emploi/models.py @@ -0,0 +1,174 @@ +# -=- 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 +#from private_files import PrivateFileField + +import datamaster_modeles.models as ref +from project.rh.models import Poste + +### 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 ProxyPoste(Poste): + class Meta: + proxy = True + + def __unicode__(self): + return '%s [%s]' % (self.nom, self.id) + +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 = 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 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='+') + + def __unicode__(self): + return '%s %s [%s]' % (self.prenom, self.nom, self.id) + + class Meta: + db_table = 'emploi_candidat' + +# 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: + db_table = 'emploi_evaluateur' + verbose_name = "pièce jointe" + verbose_name_plural = "pièces jointes" + + def __unicode__(self): + return '%s' % (self.nom) diff --git a/auf/django/emploi/templates/recrutement/pieces.html b/auf/django/emploi/templates/recrutement/pieces.html new file mode 100644 index 0000000..d8587d7 --- /dev/null +++ b/auf/django/emploi/templates/recrutement/pieces.html @@ -0,0 +1,30 @@ + + {% for f in piecesForm.management_form %} + {{ f }} + {% endfor %} + + + {% for field in piecesForm.forms.0 %} + {% if not field.is_hidden %} + + {% endif %} + {% endfor %} + + {% for f in piecesForm.forms %} + + + {% for field in f %} + {% if not field.is_hidden %} + + {% else %} + {{ field }} + {% endif %} + {% endfor %} + + {% endfor %} +
{{ field.label }}
+ {{ f.errors }} + {% if f.initial.fichier %} + Télécharger + {% endif %} + {{ field }}
diff --git a/auf/django/emploi/templates/recrutement/postuler_appel_offre.html b/auf/django/emploi/templates/recrutement/postuler_appel_offre.html new file mode 100644 index 0000000..53569f0 --- /dev/null +++ b/auf/django/emploi/templates/recrutement/postuler_appel_offre.html @@ -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 %} +
+ {% block object-tools %}{% endblock %} + + + + +
+

Poster pour un appel d'offre d'emploi

+
+ +
+
+

Informations personnelles

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ form.prenom.label }}{{ form.prenom }}
{{ form.nom.label }}{{ form.nom }}
{{ form.genre.label }}{{ form.genre }}
{{ form.nationalite.label }}{{ form.nationalite }}
{{ form.situation_famille.label }}{{ form.situation_famille }}
{{ form.nombre_dependant.label }}{{ form.nombre_dependant }}
+ {{ form.nombre_dependant.help_text }} + +
+
+
+

Coordonnées

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
{{ form.telephone.label }}{{ form.telephone }}
{{ form.email.label }}{{ form.email }}
{{ form.adresse.label }}{{ form.adresse }}
{{ form.ville.label }}{{ form.ville }}
{{ form.etat_province.label }}{{ form.etat_province }}
{{ form.code_postal.label }}{{ form.code_postal }}
{{ form.pays.label }}{{ form.pays }}
+
+
+

Informations professionnelles

+ + + + + + + + + + + + + + + + + + + +
{{ form.niveau_diplome.label }}{{ form.niveau_diplome }}
{{ form.employeur_actuel.label }}{{ form.employeur_actuel }}
{{ form.poste_actuel.label }}{{ form.poste_actuel }}
{{ form.domaine_professionnel.label }}{{ form.domaine_professionnel }}
+
+
+

Pièces jointes

+

CV, lettre de motivation...

+ {% include "recrutement/pieces.html" %} +
+
+

Vérification CAPTCHA

+

Entrez les caractères figurant dans l'image ci-dessous.

+ + + {{ form.captcha }} + {{ form.captcha.errors }} + + +
+
+ +
+
+ + + +
+ +{% endblock %} diff --git a/auf/django/emploi/views.py b/auf/django/emploi/views.py new file mode 100755 index 0000000..ccca850 --- /dev/null +++ b/auf/django/emploi/views.py @@ -0,0 +1,68 @@ +# -*- encoding: utf-8 -*- + +from django.contrib import messages +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 project.recrutement import models as recr + +def postuler_appel_offre(request): + vars = dict() + offre_emploi = get_object_or_404(OffreEmploi, id=request.GET.get('id')) + candidat = Candidat() + candidat.offre_emploi = offre_emploi + + if request.method == "POST": + form = PostulerOffreEmploiForm(request.POST, + instance=candidat, offre_emploi=offre_emploi) + piecesForm = CandidatPieceForm(request.POST, request.FILES, + instance=candidat) + if form.is_valid() and piecesForm.is_valid(): + offre = form.save() + piecesForm.instance = offre + piecesForm.save() + + courriel_template = CourrielTemplate.objects.\ + get(nom_modele='Confirmation postulation (automatique)') + send_templated_email(candidat, courriel_template) + + messages.add_message(request, messages.SUCCESS, + "Votre application à l'appel d'offre d'emploi a \ + été effectuée.") + return redirect("admin:recrutement_offreemploi_changelist") + else: + messages.add_message(request, messages.ERROR, + 'Il y a des erreurs dans le formulaire.') + else: + form = PostulerOffreEmploiForm(instance=candidat, + offre_emploi=offre_emploi) + piecesForm = CandidatPieceForm(instance=candidat) + + vars.update(dict(form=form, candidat=candidat, piecesForm=piecesForm, )) + + return render_to_response('recrutement/postuler_appel_offre.html', vars, + RequestContext(request)) + +def send_templated_email(candidat, template): + # 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,} + texte = Context(dict_texte) + # HTML text + html_template = Template(template.html) + texte_html = Context(dict_texte) + msg = EmailMultiAlternatives(sujet_template.render(sujet), + texte_template.render(texte), + 'recrutement@auf.org', + [candidat.email]) + msg.attach_alternative(texte_template.render(texte_html), "text/html") + msg.send() diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..01bb954 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,3 @@ +[egg_info] +tag_build = dev +tag_svn_revision = true diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..daf3575 --- /dev/null +++ b/setup.py @@ -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: -*- + """, + )