Add compensation form PDF for EDS section
This commit is contained in:
parent
bfb5609c9a
commit
e0cd5db7fb
7 changed files with 212 additions and 88 deletions
|
|
@ -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 l’expert</a> '
|
||||
'<a class="button" href="{}">Mail convocation soutenance</a> ',
|
||||
'', #reverse('print-expert-compens-eds', args=[obj.pk]),
|
||||
reverse('print-expert-compens-eds', args=[obj.pk]),
|
||||
reverse('student-eds-convocation', args=[obj.pk]),
|
||||
)
|
||||
else:
|
||||
|
|
|
|||
136
stages/pdf.py
136
stages/pdf.py
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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 à l’expert!"]
|
||||
+ 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 à l’expert!"]
|
||||
+ 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 à l’expert!"]
|
||||
+ 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())
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue