From 067f6f96acaa6fd915e3103a4e345d521a03dfd0 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Fri, 4 Oct 2019 13:48:15 +0200 Subject: [PATCH] Add convocation email functionality for EDS section --- common/urls.py | 4 + stages/admin.py | 17 +++- stages/models.py | 12 +++ stages/tests.py | 65 ++++++++++++++ stages/views/__init__.py | 99 ++++++++++++++++----- templates/email/student_convocation_EDS.txt | 21 +++++ 6 files changed, 194 insertions(+), 24 deletions(-) create mode 100644 templates/email/student_convocation_EDS.txt diff --git a/common/urls.py b/common/urls.py index 491f813..a4e66d7 100644 --- a/common/urls.py +++ b/common/urls.py @@ -52,6 +52,10 @@ urlpatterns = [ path('student_ede/export_qualif_ede/', views.export.export_qualification_ede, name='export-qualif-ede'), + # Qualification EDS + path('student_eds//send_convocation/', views.StudentConvocationEDSView.as_view(), + name='student-eds-convocation'), + path('imputations/export/', views.export.imputations_export, name='imputations_export'), path('export_sap/', views.export.export_sap, name='export_sap'), path('print/update_form/', views.PrintUpdateForm.as_view(), name='print_update_form'), diff --git a/stages/admin.py b/stages/admin.py index 7ed0a98..19b98a7 100644 --- a/stages/admin.py +++ b/stages/admin.py @@ -107,7 +107,8 @@ class StudentAdmin(admin.ModelAdmin): search_fields = ('last_name', 'first_name', 'pcode', 'city', 'klass__name') autocomplete_fields = ('corporation', 'instructor', 'supervisor', 'mentor', 'expert') readonly_fields = ( - 'report_sem1_sent', 'report_sem2_sent', 'examination_actions', + 'report_sem1_sent', 'report_sem2_sent', + 'examination_actions', 'examination_ep_actions', 'date_soutenance_mailed', 'date_soutenance_ep_mailed' ) fieldsets = ( @@ -140,7 +141,7 @@ class StudentAdmin(admin.ModelAdmin): ('session_ep', 'date_exam_ep', 'room_ep', 'mark_ep'), ('internal_expert_ep', 'expert_ep'), ('date_soutenance_ep_mailed', 'date_confirm_ep_received'), - #('examination_actions',) + ('examination_ep_actions',) ) }), ) @@ -174,6 +175,18 @@ class StudentAdmin(admin.ModelAdmin): return '' examination_actions.short_description = 'Actions pour les examens ES' + def examination_ep_actions(self, obj): + if obj.klass and obj.klass.section.name == 'EDS' and obj.klass.level.name == "3": + return format_html( + 'Courrier pour l’expert ' + 'Mail convocation soutenance ', + '', #reverse('print-expert-compens-eds', args=[obj.pk]), + reverse('student-eds-convocation', args=[obj.pk]), + ) + else: + return '' + examination_ep_actions.short_description = 'Actions pour les examens ES' + class CorpContactAdmin(admin.ModelAdmin): list_display = ('__str__', 'corporation', 'role') diff --git a/stages/models.py b/stages/models.py index 1029288..5512c32 100644 --- a/stages/models.py +++ b/stages/models.py @@ -407,6 +407,18 @@ class Student(models.Model): missing.append("L’expert interne n’est pas défini") return missing + def missing_examination_ep_data(self): + missing = [] + if not self.date_exam_ep: + missing.append("La date d’examen est manquante") + if not self.room_ep: + missing.append("La salle d’examen n’est pas définie") + if not self.expert_ep: + missing.append("L’expert externe n’est pas défini") + if not self.internal_expert_ep: + missing.append("L’expert interne n’est pas défini") + return missing + class StudentFile(models.Model): student = models.ForeignKey(Student, on_delete=models.CASCADE) diff --git a/stages/tests.py b/stages/tests.py index 708bc9b..a58f4e2 100644 --- a/stages/tests.py +++ b/stages/tests.py @@ -26,6 +26,7 @@ class StagesTests(TestCase): sect_ase = Section.objects.get(name='MP_ASE') lev1 = Level.objects.create(name='1') lev2 = Level.objects.create(name='2') + lev3 = Level.objects.create(name='3') klass1 = Klass.objects.create(name="1ASE3", section=sect_ase, level=lev1) klass2 = Klass.objects.create(name="2ASE3", section=sect_ase, level=lev2) klass3 = Klass.objects.create(name="2EDS", section=Section.objects.get(name='EDS'), level=lev2) @@ -261,6 +262,70 @@ tél. 032 886 33 00 st.refresh_from_db() self.assertIsNotNone(st.date_soutenance_mailed) + def test_send_eds_convocation(self): + klass = Klass.objects.create( + name="3EDS", section=Section.objects.get(name='EDS'), level=Level.objects.get(name='3') + ) + st = Student.objects.create( + first_name="Laurent", last_name="Hots", birth_date="1994-07-12", + pcode="2000", city="Neuchâtel", klass=klass + ) + + self.client.login(username='me', password='mepassword') + url = reverse('student-eds-convocation', args=[st.pk]) + response = self.client.get(url, follow=True) + for err in ("L’étudiant-e n’a pas de courriel valide", + "La date d’examen est manquante", + "La salle d’examen n’est pas définie", + "L’expert externe n’est pas défini", + "L’expert interne n’est pas défini"): + self.assertContains(response, err) + st.email = 'hots@example.org' + st.date_exam_ep = datetime(2018, 6, 28, 12, 00) + st.room_ep = "B123" + st.expert_ep = CorpContact.objects.get(last_name="Horner") + st.internal_expert_ep = Teacher.objects.get(last_name="Caux") + st.save() + response = self.client.get(url, follow=True) + self.assertContains(response, "L’expert externe n’a pas de courriel valide !") + st.expert_ep.email = "horner@example.org" + st.expert_ep.save() + response = self.client.get(url) + expected_message = """ Laurent Hots, +Madame Julie Caux, +Monsieur Jean Horner, + + +Nous vous informons que la soutenance du travail final de Laurent Hots aura lieu dans les locaux de l’Ecole Santé-social Pierre-Coullery, rue Sophie-Mairet 29-31, 2300 La Chaux-de-Fonds en date du: + + - jeudi 28 juin 2018 à 12h00 en salle B123 + + +Nous informons également Monsieur Horner que le mémoire lui est adressé ce jour par courrier postal. + + +Nous vous remercions de nous confirmer par retour de courriel que vous avez bien reçu ce message et dans l’attente du plaisir de vous rencontrer prochainement, nous vous prions d’agréer, Madame, Messieurs, nos salutations les meilleures. + + + +Secrétariat de la filière Education sociale, dipl. ES +Jean Valjean +me@example.org +tél. 032 886 33 00 +""" + self.assertEqual(response.context['form'].initial['message'], expected_message) + # Now send the message + response = self.client.post(url, data={ + 'cci': 'me@example.org', + 'to': st.email, + 'subject': "Convocation", + 'message': "Monsieur Albin, ...", + 'sender': 'me@example.org', + }) + self.assertEqual(len(mail.outbox), 1) + st.refresh_from_db() + self.assertIsNotNone(st.date_soutenance_ep_mailed) + def test_print_ede_compensation_forms(self): st = Student.objects.get(first_name="Albin") url = reverse('print-expert-compens-ede', args=[st.pk]) diff --git a/stages/views/__init__.py b/stages/views/__init__.py index a49e78f..d1b32f7 100644 --- a/stages/views/__init__.py +++ b/stages/views/__init__.py @@ -398,36 +398,55 @@ class EmailConfirmationView(EmailConfirmationBaseView): class StudentConvocationExaminationView(EmailConfirmationView): success_message = "Le message de convocation a été envoyé pour l’étudiant {person}" title = "Convocation à la soutenance du travail de diplôme" + email_template = 'email/student_convocation_EDE.txt' + + @property + def expert(self): + return self.student.expert + + @property + def internal_expert(self): + return self.student.internal_expert + + @property + def date_soutenance_mailed(self): + return self.student.date_soutenance_mailed + + def missing_examination_data(self): + return self.student.missing_examination_data() def dispatch(self, request, *args, **kwargs): self.student = Student.objects.get(pk=self.kwargs['pk']) - errors = self.student.missing_examination_data() - if self.student.expert and not self.student.expert.email: - errors.append("L’expert externe n’a pas de courriel valide !") - if self.student.internal_expert and not self.student.internal_expert.email: - errors.append("L’expert interne n'a pas de courriel valide !") - if self.student.date_soutenance_mailed is not None: - errors.append("Une convocation a déjà été envoyée !") + errors = self.missing_examination_data() + errors.extend(self.check_errors()) if errors: messages.error(request, "\n".join(errors)) return redirect(reverse("admin:stages_student_change", args=(self.student.pk,))) return super().dispatch(request, *args, **kwargs) - def get_initial(self): - initial = super().get_initial() - to = [self.student.email, self.student.expert.email, self.student.internal_expert.email] - src_email = 'email/student_convocation_EDE.txt' + def check_errors(self): + errors = [] + if not self.student.email: + errors.append("L’étudiant-e n’a pas de courriel valide !") + if self.expert and not self.expert.email: + errors.append("L’expert externe n’a pas de courriel valide !") + if self.internal_expert and not self.internal_expert.email: + errors.append("L’expert interne n'a pas de courriel valide !") + if self.date_soutenance_mailed is not None: + errors.append("Une convocation a déjà été envoyée !") + return errors + def msg_context(self): # Recipients with ladies first! recip_names = sorted([ self.student.civility_full_name, - self.student.expert.civility_full_name, - self.student.internal_expert.civility_full_name, + self.expert.civility_full_name, + self.internal_expert.civility_full_name, ]) titles = [ self.student.civility, - self.student.expert.civility, - self.student.internal_expert.civility, + self.expert.civility, + self.internal_expert.civility, ] mme_count = titles.count('Madame') # Civilities, with ladies first! @@ -439,29 +458,65 @@ class StudentConvocationExaminationView(EmailConfirmationView): civilities = 'Mesdames, Monsieur' else: civilities = 'Mesdames' - - msg_context = { + return { 'recipient1': recip_names[0], 'recipient2': recip_names[1], 'recipient3': recip_names[2], 'student': self.student, 'sender': self.request.user, 'global_civilities': civilities, - 'date_examen': django_format(self.student.date_exam, 'l j F Y à H\hi'), + 'date_examen': django_format(self.student.date_exam, 'l j F Y à H\hi') if self.student.date_exam else '-', 'salle': self.student.room, } + + def get_initial(self): + initial = super().get_initial() + to = [self.student.email, self.expert.email, self.internal_expert.email] + initial.update({ 'cci': self.request.user.email, 'to': '; '.join(to), - 'subject': "Convocation à la soutenance de travail de diplôme", - 'message': loader.render_to_string(src_email, msg_context), + 'subject': self.title, + 'message': loader.render_to_string(self.email_template, self.msg_context()), 'sender': self.request.user.email, }) return initial def on_success(self, student): - student.date_soutenance_mailed = timezone.now() - student.save() + self.student.date_soutenance_mailed = timezone.now() + self.student.save() + + +class StudentConvocationEDSView(StudentConvocationExaminationView): + title = "Convocation à la soutenance du travail final" + email_template = 'email/student_convocation_EDS.txt' + + @property + def expert(self): + return self.student.expert_ep + + @property + def internal_expert(self): + return self.student.internal_expert_ep + + @property + def date_soutenance_mailed(self): + return self.student.date_soutenance_ep_mailed + + def missing_examination_data(self): + return self.student.missing_examination_ep_data() + + def msg_context(self): + context = super().msg_context() + context.update({ + 'date_examen': django_format(self.student.date_exam_ep, 'l j F Y à H\hi'), + 'salle': self.student.room_ep, + }) + return context + + def on_success(self, student): + self.student.date_soutenance_ep_mailed = timezone.now() + self.student.save() class PrintUpdateForm(ZippedFilesBaseView): diff --git a/templates/email/student_convocation_EDS.txt b/templates/email/student_convocation_EDS.txt new file mode 100644 index 0000000..0a540be --- /dev/null +++ b/templates/email/student_convocation_EDS.txt @@ -0,0 +1,21 @@ +{{ recipient1 }}, +{{ recipient2 }}, +{{ recipient3 }}, + + +Nous vous informons que la soutenance du travail final de {{ student.civility_full_name }} aura lieu dans les locaux de l’Ecole Santé-social Pierre-Coullery, rue Sophie-Mairet 29-31, 2300 La Chaux-de-Fonds en date du: + + - {{ date_examen }} en salle {{ salle }} + + +Nous informons également {{ student.expert_ep.civility }} {{ student.expert_ep.last_name }} que le mémoire lui est adressé ce jour par courrier postal. + + +Nous vous remercions de nous confirmer par retour de courriel que vous avez bien reçu ce message et dans l’attente du plaisir de vous rencontrer prochainement, nous vous prions d’agréer, {{ global_civilities }}, nos salutations les meilleures. + + + +Secrétariat de la filière Education sociale, dipl. ES +{{ sender.first_name }} {{ sender.last_name }} +{{ sender.email }} +tél. 032 886 33 00