Add compensation form PDF for EDS section

This commit is contained in:
Claude Paroz 2019-10-04 15:36:55 +02:00
parent bfb5609c9a
commit e0cd5db7fb
7 changed files with 212 additions and 88 deletions

View file

@ -105,7 +105,7 @@ class StudentAdmin(admin.ModelAdmin):
ordering = ('last_name', 'first_name')
list_filter = (('archived', ArchivedListFilter), ('klass', KlassRelatedListFilter))
search_fields = ('last_name', 'first_name', 'pcode', 'city', 'klass__name')
autocomplete_fields = ('corporation', 'instructor', 'supervisor', 'mentor', 'expert')
autocomplete_fields = ('corporation', 'instructor', 'supervisor', 'mentor', 'expert', 'expert_ep')
readonly_fields = (
'report_sem1_sent', 'report_sem2_sent',
'examination_actions', 'examination_ep_actions',
@ -180,7 +180,7 @@ class StudentAdmin(admin.ModelAdmin):
return format_html(
'<a class="button" href="{}">Courrier pour lexpert</a>&nbsp;'
'<a class="button" href="{}">Mail convocation soutenance</a>&nbsp;',
'', #reverse('print-expert-compens-eds', args=[obj.pk]),
reverse('print-expert-compens-eds', args=[obj.pk]),
reverse('student-eds-convocation', args=[obj.pk]),
)
else:

View file

@ -3,7 +3,6 @@ from datetime import date
from django.conf import settings
from django.contrib.staticfiles.finders import find
from django.utils.dateformat import format as django_format
from django.utils.text import slugify
from reportlab.lib.pagesizes import A4
from reportlab.lib.units import cm
@ -411,6 +410,29 @@ class CompensationForm:
class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
reference = 'ASH/val'
title = 'Travail de diplôme'
resp_filiere, resp_genre = settings.RESP_FILIERE_EDE
part1_text = """
{expert_civility},<br/><br/>
Vous avez accepté de fonctionner comme expert{expert_accord} pour un {title_lower} de l'un-e de nos
étudiant-e-s. Nous vous remercions très chaleureusement de votre disponibilité.<br/><br/>
En annexe, nous avons l'avantage de vous remettre le travail de {student_civility_full_name},
ainsi que la grille d'évaluation commune aux deux membres du jury.<br/><br/>
La soutenance de ce travail de diplôme se déroulera le:<br/><br/>
"""
part2_text = """
<br/>
L'autre membre du jury sera {internal_expert_civility} {internal_expert_full_name}, {internal_expert_role} dans notre école.<br/>
<br/>
Par ailleurs, nous nous permettons de vous faire parvenir en annexe le formulaire «Indemnisation d'experts aux examens»
que vous voudrez bien compléter au niveau des «données privées / coordonnées de paiement» et nous retourner dans les meilleurs délais.
<br/><br/>
Restant à votre disposition pour tout complément d'information et en vous remerciant de
l'attention que vous porterez à la présente, nous vous prions d'agréer, {expert_civility}, l'asurance de notre considération distinguée.<br/>
<br/><br/><br/>
"""
def __init__(self, out, student):
self.student = student
super().__init__(out)
@ -419,64 +441,69 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
PageTemplate(id='ISOPage', frames=[self.page_frame], onPage=self.header_iso),
])
def produce(self):
self.add_address(self.student.expert)
def exam_data(self):
return {
'expert': self.student.expert,
'internal_expert': self.student.internal_expert,
'date_exam': self.student.date_exam,
'room': self.student.room,
}
ptext = """
def produce(self):
exam_data = self.exam_data()
self.add_address(exam_data['expert'])
header_text = """
<br/><br/><br/>
La Chaux-de-Fonds, le {current_date}<br/>
N/réf.:ASH/val<br/>
N/réf.: {ref}<br/>
<br/><br/><br/>
<strong>Travail de diplôme</strong>
<strong>{title}</strong>
<br/><br/><br/>
{expert_civility},<br/><br/>
Vous avez accepté de fonctionner comme expert{expert_accord} pour un travail de diplôme de l'un-e de nos
étudiant-e-s. Nous vous remercions très chaleureusement de votre disponibilité.<br/><br/>
En annexe, nous avons l'avantage de vous remettre le travail de {student_civility_full_name},
ainsi que la grille d'évaluation commune aux deux membres du jury.<br/><br/>
La soutenance de ce travail de diplôme se déroulera le:<br/><br/>
"""
self.story.append(Paragraph(ptext.format(
self.story.append(Paragraph(header_text.format(
current_date=django_format(date.today(), 'j F Y'),
expert_civility=self.student.expert.civility,
expert_accord=self.student.expert.adjective_ending,
ref=self.reference,
title=self.title,
), style_normal))
self.story.append(Paragraph(self.part1_text.format(
title_lower=self.title.lower(),
expert_civility=exam_data['expert'].civility,
expert_accord=exam_data['expert'].adjective_ending,
student_civility_full_name=self.student.civility_full_name,
), style_normal))
ptext = "<br/>{0} à l'Ecole Santé-social Pierre-Coullery, salle {1}<br/><br/>"
self.story.append(Paragraph(ptext.format(
django_format(self.student.date_exam, 'l j F Y à H\hi'),
self.student.room
date_text = "<br/>{0} à l'Ecole Santé-social Pierre-Coullery, salle {1}<br/><br/>"
self.story.append(Paragraph(date_text.format(
django_format(exam_data['date_exam'], 'l j F Y à H\hi'),
exam_data['room'],
), style_bold_center))
ptext = """
<br/>
L'autre membre du jury sera {internal_expert_civility} {internal_expert_full_name}, {internal_expert_role} dans notre école.<br/>
<br/>
Par ailleurs, nous nous permettons de vous faire parvenir en annexe le formulaire «Indemnisation d'experts aux examens»
que vous voudrez bien compléter au niveau des «données privées / coordonnées de paiement» et nous retourner dans les meilleurs délais.
<br/><br/>
Restant à votre disposition pour tout complément d'information et en vous remerciant de
l'attention que vous porterez à la présente, nous vous prions d'agréer, {expert_civility}, l'asurance de notre considération distinguée.<br/>
<br/><br/><br/>
La responsable de filière:<br/>
<br/><br/>
{resp_filiere}
<br/><br/><br/>
Annexes: ment.
"""
self.story.append(Paragraph(ptext.format(
internal_expert_civility=self.student.internal_expert.civility,
internal_expert_full_name=self.student.internal_expert.full_name,
internal_expert_role=self.student.internal_expert.role,
expert_civility=self.student.expert.civility,
resp_filiere=settings.RESP_FILIERE_EDE,
self.story.append(Paragraph(self.part2_text.format(
internal_expert_civility=exam_data['internal_expert'].civility,
internal_expert_full_name=exam_data['internal_expert'].full_name,
internal_expert_role=exam_data['internal_expert'].role,
expert_civility=exam_data['expert'].civility,
), style_normal))
footer_text = """
{lela} responsable de filière:<br/>
<br/><br/>
{resp_filiere}
<br/><br/><br/>
Annexes: ment.
"""
self.story.append(Paragraph(footer_text.format(
lela='Le' if self.resp_genre == 'M' else 'La',
resp_filiere=self.resp_filiere,
), style_normal))
# ISO page
self.story.append(NextPageTemplate('ISOPage'))
self.story.append(PageBreak())
self.add_private_data(self.student.expert)
self.add_private_data(exam_data['expert'])
self.story.append(Paragraph(
"Mandat: Soutenance de {0} {1}, classe {2}".format(
@ -484,7 +511,7 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
), style_normal
))
self.story.append(Paragraph(
"Date de l'examen : {}".format(django_format(self.student.date_exam, 'l j F Y')), style_normal
"Date de l'examen : {}".format(django_format(exam_data['date_exam'], 'l j F Y')), style_normal
))
self.story.append(Spacer(0, 2 * cm))
@ -493,6 +520,29 @@ class ExpertEdeLetterPdf(CompensationForm, EpcBaseDocTemplate):
self.build(self.story)
class ExpertEdsLetterPdf(ExpertEdeLetterPdf):
reference = 'BAH/ner'
title = 'Travail final'
resp_filiere, resp_genre = settings.RESP_FILIERE_EDS
part1_text = """
{expert_civility},<br/><br/>
Vous avez accepté de fonctionner comme expert{expert_accord} pour un {title_lower} de l'un-e de nos
étudiant-e-s. Nous vous remercions très chaleureusement de votre disponibilité.<br/><br/>
En annexe, nous avons l'avantage de vous remettre le travail de {student_civility_full_name},
ainsi que diverses informations sur le cadre de cet examen et la grille d'évaluation
commune aux deux membres du jury.<br/><br/>
La soutenance de ce travail de diplôme se déroulera le:<br/><br/>
"""
def exam_data(self):
return {
'expert': self.student.expert_ep,
'internal_expert': self.student.internal_expert_ep,
'date_exam': self.student.date_exam_ep,
'room': self.student.room_ep,
}
class MentorCompensationPdfForm(CompensationForm, EpcBaseDocTemplate):
def __init__(self, out, student):
self.student = student

View file

@ -330,7 +330,7 @@ tél. 032 886 33 00
st = Student.objects.get(first_name="Albin")
url = reverse('print-expert-compens-ede', args=[st.pk])
self.client.login(username='me', password='mepassword')
response = self.client.post(url, follow=True)
response = self.client.get(url, follow=True)
self.assertContains(response, "Toutes les informations ne sont pas disponibles")
st.expert = CorpContact.objects.get(last_name="Horner")
@ -339,7 +339,7 @@ tél. 032 886 33 00
st.room = "B123"
st.save()
self.client.login(username='me', password='mepassword')
response = self.client.post(url, follow=True)
response = self.client.get(url, follow=True)
self.assertEqual(
response['Content-Disposition'],
'attachment; filename="dupond_albin_Expert.pdf"'
@ -349,13 +349,13 @@ tél. 032 886 33 00
# Expert without corporation
st.expert = CorpContact.objects.create(first_name='James', last_name='Bond')
st.save()
response = self.client.post(url, follow=True)
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)
# Mentor form
st.mentor = CorpContact.objects.get(last_name="Horner")
st.save()
response = self.client.post(reverse('print-mentor-compens-ede', args=[st.pk]), follow=True)
response = self.client.get(reverse('print-mentor-compens-ede', args=[st.pk]), follow=True)
self.assertEqual(
response['Content-Disposition'],
'attachment; filename="dupond_albin_Indemn_mentor.pdf"'
@ -363,6 +363,38 @@ tél. 032 886 33 00
self.assertEqual(response['Content-Type'], 'application/pdf')
self.assertGreater(int(response['Content-Length']), 1000)
def test_print_eds_compensation_forms(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
)
url = reverse('print-expert-compens-eds', args=[st.pk])
self.client.login(username='me', password='mepassword')
response = self.client.get(url, follow=True)
self.assertContains(response, "Toutes les informations ne sont pas disponibles")
st.expert_ep = CorpContact.objects.get(last_name="Horner")
st.internal_expert_ep = Teacher.objects.get(last_name="Caux")
st.date_exam_ep = datetime(2018, 6, 28, 12, 00)
st.room_ep = "B123"
st.save()
response = self.client.get(url, follow=True)
self.assertEqual(
response['Content-Disposition'],
'attachment; filename="hots_laurent_Expert.pdf"'
)
self.assertEqual(response['Content-Type'], 'application/pdf')
self.assertGreater(int(response['Content-Length']), 1000)
# Expert without corporation
st.expert_ep = CorpContact.objects.create(first_name='James', last_name='Bond')
st.save()
response = self.client.get(url, follow=True)
self.assertEqual(response.status_code, 200)
def test_EDEpe_klass(self):
lev3 = Level.objects.create(name='3')
klass4 = Klass.objects.create(name="3EDEp_pe", section=Section.objects.get(name='EDE'), level=lev3)

View file

@ -18,7 +18,7 @@ from django.utils.dateformat import format as django_format
from django.utils.text import slugify
from django.views.generic import DetailView, FormView, ListView, TemplateView, UpdateView
from .base import EmailConfirmationBaseView, ZippedFilesBaseView
from .base import EmailConfirmationBaseView, PDFBaseView, ZippedFilesBaseView
from .export import OpenXMLExport
from .imports import HPContactsImportView, HPImportView, ImportReportsView, StudentImportView
from ..forms import CorporationMergeForm, EmailBaseForm, StudentCommentForm
@ -27,8 +27,8 @@ from ..models import (
Training, Availability
)
from ..pdf import (
ChargeSheetPDF, ExpertEdeLetterPdf, UpdateDataFormPDF, MentorCompensationPdfForm,
KlassListPDF,
ChargeSheetPDF, ExpertEdeLetterPdf, ExpertEdsLetterPdf, UpdateDataFormPDF,
MentorCompensationPdfForm, KlassListPDF,
)
from ..utils import school_year_start
@ -543,47 +543,73 @@ class PrintUpdateForm(ZippedFilesBaseView):
yield ('{0}.pdf'.format(klass.name), buff.getvalue())
def print_expert_ede_compensation_form(request, pk):
class PrintExpertEDECompensationForm(PDFBaseView):
"""
Imprime le PDF à envoyer à l'expert EDE en accompagnement du
travail de diplôme
"""
student = Student.objects.get(pk=pk)
missing = student.missing_examination_data()
if missing:
messages.error(request, "\n".join(
["Toutes les informations ne sont pas disponibles pour la lettre à lexpert!"]
+ missing
))
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
buff = io.BytesIO()
pdf = ExpertEdeLetterPdf(buff, student)
pdf.produce()
filename = slugify(
'{0}_{1}'.format(student.last_name, student.first_name)
) + '_Expert.pdf'
buff.seek(0)
return FileResponse(buff, as_attachment=True, filename=filename)
pdf_class = ExpertEdeLetterPdf
def filename(self, student):
return slugify('{0}_{1}'.format(student.last_name, student.first_name)) + '_Expert.pdf'
def get_object(self):
return Student.objects.get(pk=self.kwargs['pk'])
def check_object(self, student):
missing = student.missing_examination_data()
if missing:
messages.error(self.request, "\n".join(
["Toutes les informations ne sont pas disponibles pour la lettre à lexpert!"]
+ missing
))
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
def get(self, request, *args, **kwargs):
student = self.get_object()
response = self.check_object(student)
if response:
return response
return super().get(request, *args, **kwargs)
def print_mentor_ede_compensation_form(request, pk):
class PrintMentorEDECompensationForm(PDFBaseView):
"""
Imprime le PDF à envoyer au mentor EDE pour le mentoring
"""
student = Student.objects.get(pk=pk)
if not student.mentor:
messages.error(request, "Aucun mentor n'est attribué à cet étudiant")
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
buff = io.BytesIO()
pdf = MentorCompensationPdfForm(buff, student)
pdf.produce()
filename = slugify(
'{0}_{1}'.format(student.last_name, student.first_name)
) + '_Indemn_mentor.pdf'
buff.seek(0)
return FileResponse(buff, as_attachment=True, filename=filename)
pdf_class = MentorCompensationPdfForm
def filename(self, student):
return slugify(
'{0}_{1}'.format(student.last_name, student.first_name)
) + '_Indemn_mentor.pdf'
def get_object(self):
return Student.objects.get(pk=self.kwargs['pk'])
def get(self, request, *args, **kwargs):
student = self.get_object()
if not student.mentor:
messages.error(request, "Aucun mentor n'est attribué à cet étudiant")
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
return super().get(request, *args, **kwargs)
class PrintExpertEDSCompensationForm(PrintExpertEDECompensationForm):
"""
Imprime le PDF à envoyer à l'expert EDS en accompagnement du
travail final.
"""
pdf_class = ExpertEdsLetterPdf
def check_object(self, student):
missing = student.missing_examination_ep_data()
if missing:
messages.error(self.request, "\n".join(
["Toutes les informations ne sont pas disponibles pour la lettre à lexpert!"]
+ missing
))
return redirect(reverse("admin:stages_student_change", args=(student.pk,)))
class PrintKlassList(ZippedFilesBaseView):
@ -594,7 +620,7 @@ class PrintKlassList(ZippedFilesBaseView):
buff = io.BytesIO()
pdf = KlassListPDF(buff, klass)
pdf.produce(klass)
filename = slugify('{0}.pdf'.format(klass.name))
filename = slugify(klass.name + '.pdf')
yield (filename, buff.getvalue())
@ -612,5 +638,5 @@ class PrintChargeSheet(ZippedFilesBaseView):
buff = io.BytesIO()
pdf = ChargeSheetPDF(buff, teacher)
pdf.produce(activities)
filename = slugify('{0}_{1}.pdf'.format(teacher.last_name, teacher.first_name))
filename = slugify('{0}_{1}'.format(teacher.last_name, teacher.first_name)) + '.pdf'
yield (filename, buff.getvalue())

View file

@ -1,10 +1,11 @@
import io
import os
import tempfile
import zipfile
from django.contrib import messages
from django.core.mail import EmailMessage
from django.http import HttpResponse
from django.http import FileResponse, HttpResponse
from django.urls import reverse_lazy
from django.views.generic import FormView, View
@ -51,6 +52,18 @@ class EmailConfirmationBaseView(FormView):
return context
class PDFBaseView(View):
pdf_class = None
def get(self, request, *args, **kwargs):
obj = self.get_object()
buff = io.BytesIO()
pdf = self.pdf_class(buff, obj)
pdf.produce()
buff.seek(0)
return FileResponse(buff, as_attachment=True, filename=self.filename(obj))
class ZippedFilesBaseView(View):
"""A base class to return a .zip file containing a compressed list of files."""
filename = 'to_be_defined.zip'