From ffb54c9f78b9ad4452bb2ff7924c71ee544a18d2 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 26 Jan 2018 18:19:16 +0100 Subject: [PATCH] Add EDE convocation form --- candidats/admin.py | 14 ++- candidats/models.py | 6 +- candidats/tests.py | 50 +++++++++++ candidats/views.py | 90 +++++++++++++++++++ common/settings.py | 2 + common/urls.py | 4 + templates/candidats/convocation.html | 20 +++++ templates/email/candidate_convocation_EDE.txt | 19 ++++ templates/email/rappel_document_EDE.txt | 4 + 9 files changed, 207 insertions(+), 2 deletions(-) create mode 100644 candidats/views.py create mode 100644 templates/candidats/convocation.html create mode 100644 templates/email/candidate_convocation_EDE.txt create mode 100644 templates/email/rappel_document_EDE.txt diff --git a/candidats/admin.py b/candidats/admin.py index 45a2756..e369da6 100644 --- a/candidats/admin.py +++ b/candidats/admin.py @@ -6,6 +6,7 @@ from django.contrib import admin from django.core.mail import EmailMessage from django.db.models import BooleanField from django.template import loader +from django.urls import reverse from stages.exports import OpenXMLExport from .models import Candidate, Interview, GENDER_CHOICES @@ -119,7 +120,7 @@ class CandidateAdminForm(forms.ModelForm): class CandidateAdmin(admin.ModelAdmin): form = CandidateAdminForm - list_display = ('last_name', 'first_name', 'section', 'confirm_mail') + list_display = ('last_name', 'first_name', 'section', 'confirm_mail', 'convocation') list_filter = ('section', 'option') readonly_fields = ('total_result_points', 'total_result_mark', 'date_confirmation_mail') actions = [export_candidates, send_confirmation_mail] @@ -157,6 +158,17 @@ class CandidateAdmin(admin.ModelAdmin): return obj.date_confirmation_mail is not None confirm_mail.boolean = True + def convocation(self, obj): + if obj.interview is None: + return '???' + elif obj.interview and obj.convocation_date: + return obj.interview + else: + url = reverse('candidate-convocation', args=[obj.pk]) + return 'Envoyer convocation' + convocation.short_description = 'Convoc. aux examens' + convocation.allow_tags = True + class InterviewAdmin(admin.ModelAdmin): pass diff --git a/candidats/models.py b/candidats/models.py index 05863c6..6a29d25 100644 --- a/candidats/models.py +++ b/candidats/models.py @@ -166,7 +166,11 @@ class Interview(models.Model): def __str__(self): return '{0} : {1}/{2} - ({3}) -salle:{4}-{5}'.format( - django_format(self.date, "l j F Y à H\hi"), + self.date_formatted, self.teacher_int or '?', self.teacher_file or '?', self.status, self.room, self.candidat or '???' ) + + @property + def date_formatted(self): + return django_format(self.date, "l j F Y à H\hi") diff --git a/candidats/tests.py b/candidats/tests.py index 4a000a8..81700df 100644 --- a/candidats/tests.py +++ b/candidats/tests.py @@ -113,3 +113,53 @@ me@example.org self.assertContains(response, "Échec d’envoi pour le candidat Dupond Henri (Error sending mail)") henri.refresh_from_db() self.assertIsNone(henri.date_confirmation_mail) + + def test_convocation_ede(self): + ede = Section.objects.create(name='EDE') + henri = Candidate.objects.create( + first_name='Henri', last_name='Dupond', gender='M', section=ede, + email='henri@example.org', deposite_date=date.today() + ) + inter = Interview.objects.create(date=datetime(2018, 3, 10, 10, 30), room='B103', candidat=henri) + self.client.login(username='me', password='mepassword') + response = self.client.get(reverse('candidate-convocation', args=[henri.pk])) + self.assertContains(response, '

Dupond Henri

') + self.assertContains(response, '', html=True) + self.assertContains(response, """ +Monsieur Henri Dupond, + +Nous vous adressons par la présente votre convocation personnelle à la procédure d’admission de la filière Education de l’enfance, dipl. ES. + +Vous êtes attendu-e à l’Ecole Santé-social Pierre-Coullery, rue de la Prévoyance 82, 2300 La Chaux-de-Fonds aux dates suivantes: + + - mercredi 7 mars 2018, à 13h30, salle 405, pour l’examen écrit (durée approx. 4 heures) + + - samedi 10 mars 2018 à 10h30, en salle B103, pour l’entretien d’admission (durée approx. 45 min.). + +En cas d’empêchement de dernière minute, nous vous remercions d’annoncer votre absence au secrétariat (Tél. 032 886 33 00). + +De plus, afin que nous puissions enregistrer définitivement votre inscription, nous vous remercions par avance +de nous faire parvenir, dans les meilleurs délais, le ou les documents suivants: + - Formulaire d'inscription, Attest. de paiement, Casier judic., CV, Texte réflexif, Photo passeport, Bilan act. prof./dernier stage, Bull. de notes + +Dans l’intervalle, nous vous adressons, Monsieur, nos salutations les plus cordiales. + +Secrétariat de la filière Education de l’enfance, dipl. ES +Hans Schmid +me@example.org +tél. 032 886 33 00""" + ) + response = self.client.post(reverse('candidate-convocation', args=[henri.pk]), data={ + 'id_candidate': str(henri.pk), + 'cci': 'me@example.org', + 'to': henri.email, + 'subject': "Procédure de qualification", + 'message': "Monsieur Henri Dupond, ...", + 'sender': 'me@example.org', + }) + self.assertRedirects(response, reverse('admin:candidats_candidate_changelist')) + self.assertEqual(len(mail.outbox), 1) + self.assertEqual(mail.outbox[0].recipients(), ['henri@example.org', 'me@example.org']) + self.assertEqual(mail.outbox[0].subject, "Procédure de qualification") + henri.refresh_from_db() + self.assertIsNotNone(henri.convocation_date) diff --git a/candidats/views.py b/candidats/views.py new file mode 100644 index 0000000..0483c57 --- /dev/null +++ b/candidats/views.py @@ -0,0 +1,90 @@ +from django import forms +from django.conf import settings +from django.contrib import messages +from django.core.mail import EmailMessage +from django.template import loader +from django.urls import reverse_lazy +from django.utils import timezone +from django.views.generic import FormView + +from candidats.models import Candidate + + +class ConvocationForm(forms.Form): + id_candidate = forms.CharField(widget=forms.HiddenInput()) + sender = forms.CharField(widget=forms.HiddenInput()) + to = forms.CharField(widget=forms.TextInput(attrs={'size': '60'})) + cci = forms.CharField(widget=forms.TextInput(attrs={'size': '60'})) + subject = forms.CharField(widget=forms.TextInput(attrs={'size': '60'})) + message = forms.CharField(widget=forms.Textarea(attrs={'rows': 25, 'cols': 120})) + + +class SendConvocationView(FormView): + template_name = 'candidats/convocation.html' + form_class = ConvocationForm + success_url = reverse_lazy('admin:candidats_candidate_changelist') + + def get_context_data(self, **kwargs): + context = super().get_context_data(**kwargs) + + candidate = Candidate.objects.get(pk=self.kwargs['pk']) + docs = [ + 'registration_form', 'certificate_of_payement', 'police_record', 'cv', 'reflexive_text', + 'has_photo', 'work_certificate', 'marks_certificate', + ] + if candidate.option == 'PE-5400h': + docs.append('promise', 'contract', 'certif_of_800h') + elif candidate.option == 'PE-3600h': + docs.append('certif_of_cfc', 'promise', 'contract') + elif candidate.option == 'PS': + docs.append('certif_of_800h') + + missing_documents = {'documents': ', '.join([ + Candidate._meta.get_field(doc).verbose_name for doc in docs if not getattr(candidate, doc) + ])} + + msg_context = { + 'candidate_name': " ".join([candidate.civility, candidate.first_name, candidate.last_name]), + 'candidate_civility': candidate.civility, + 'date_lieu_examen': settings.DATE_LIEU_EXAMEN_EDE, + 'date_entretien': candidate.interview.date_formatted, + 'salle_entretien': candidate.interview.room, + 'rappel': loader.render_to_string('email/rappel_document_EDE.txt', missing_documents), + 'sender_name': " ".join([self.request.user.first_name, self.request.user.last_name]), + 'sender_email': self.request.user.email, + } + + form = ConvocationForm(initial={ + 'id_candidate': candidate.pk, + 'cci': self.request.user.email, + 'to': candidate.email, + 'subject': "Procédure de qualification", + 'message': loader.render_to_string('email/candidate_convocation_EDE.txt', msg_context), + 'sender': self.request.user.email, + }) + context.update({ + 'candidat': candidate, + 'form': form, + }) + return context + + def form_valid(self, form): + email = EmailMessage( + subject=form.cleaned_data['subject'], + body=form.cleaned_data['message'], + from_email=form.cleaned_data['sender'], + to=form.cleaned_data['to'].split(';'), + bcc=form.cleaned_data['cci'].split(';'), + ) + candidate = Candidate.objects.get(pk=self.kwargs['pk']) + try: + email.send() + except Exception as err: + messages.error(self.request, "Échec d’envoi pour le candidat {0} ({1})".format(candidate, err)) + else: + candidate.convocation_date = timezone.now() + candidate.save() + messages.success(self.request, + "Le message de convocation a été envoyé pour le candidat {0}".format(candidate) + ) + return super().form_valid(form) diff --git a/common/settings.py b/common/settings.py index bcc8fa1..5ad26d1 100644 --- a/common/settings.py +++ b/common/settings.py @@ -146,6 +146,8 @@ CHARGE_SHEET_TITLE = "Feuille de charge pour l'année scolaire 2017-2018" MAX_ENS_PERIODS = 1900 MAX_ENS_FORMATION = 250 +DATE_LIEU_EXAMEN_EDE = "mercredi 7 mars 2018, à 13h30, salle 405" + if 'TRAVIS' in os.environ: SECRET_KEY = 'secretkeyfortravistests' STATICFILES_STORAGE = 'django.contrib.staticfiles.storage.StaticFilesStorage' diff --git a/common/urls.py b/common/urls.py index c190a6c..54fd3c8 100644 --- a/common/urls.py +++ b/common/urls.py @@ -2,6 +2,7 @@ from django.conf.urls import include, url from django.contrib import admin from django.views.generic import RedirectView +from candidats import views as candidats_views from stages import views urlpatterns = [ @@ -21,6 +22,9 @@ urlpatterns = [ url(r'^classes/$', views.KlassListView.as_view(), name='classes'), url(r'^classes/(?P\d+)/$', views.KlassView.as_view(), name='class'), + url(r'^candidate/(?P\d+)/send_convocation/$', candidats_views.SendConvocationView.as_view(), + name='candidate-convocation'), + url(r'^imputations/export/$', views.imputations_export, name='imputations_export'), url(r'^print/update_form/$', views.print_update_form, name='print_update_form'), url(r'^general_export/$', views.general_export, name='general-export'), diff --git a/templates/candidats/convocation.html b/templates/candidats/convocation.html new file mode 100644 index 0000000..17a5eed --- /dev/null +++ b/templates/candidats/convocation.html @@ -0,0 +1,20 @@ +{% extends "admin/base_site.html" %} +{% load i18n static %} + +{% block breadcrumbs %} + +{% endblock %} + +{% block content %} +

{{ candidat }}

+
{% csrf_token %} + +{{ form.as_table }} + + +
+
+{% endblock %} diff --git a/templates/email/candidate_convocation_EDE.txt b/templates/email/candidate_convocation_EDE.txt new file mode 100644 index 0000000..3196d50 --- /dev/null +++ b/templates/email/candidate_convocation_EDE.txt @@ -0,0 +1,19 @@ +{{ candidate_name }}, + +Nous vous adressons par la présente votre convocation personnelle à la procédure d’admission de la filière Education de l’enfance, dipl. ES. + +Vous êtes attendu-e à l’Ecole Santé-social Pierre-Coullery, rue de la Prévoyance 82, 2300 La Chaux-de-Fonds aux dates suivantes: + + - {{ date_lieu_examen }}, pour l’examen écrit (durée approx. 4 heures) + + - {{ date_entretien }}, en salle {{ salle_entretien }}, pour l’entretien d’admission (durée approx. 45 min.). + +En cas d’empêchement de dernière minute, nous vous remercions d’annoncer votre absence au secrétariat (Tél. 032 886 33 00). +{{ rappel }} + +Dans l’intervalle, nous vous adressons, {{ candidate_civility }}, nos salutations les plus cordiales. + +Secrétariat de la filière Education de l’enfance, dipl. ES +{{ sender_name }} +{{ sender_email }} +tél. 032 886 33 00 diff --git a/templates/email/rappel_document_EDE.txt b/templates/email/rappel_document_EDE.txt new file mode 100644 index 0000000..b15c765 --- /dev/null +++ b/templates/email/rappel_document_EDE.txt @@ -0,0 +1,4 @@ + +De plus, afin que nous puissions enregistrer définitivement votre inscription, nous vous remercions par avance +de nous faire parvenir, dans les meilleurs délais, le ou les documents suivants: + - {{ documents }} \ No newline at end of file