From 8b673665cef0dd0384fdd569a9df9c258403607f Mon Sep 17 00:00:00 2001 From: alazo Date: Tue, 8 Aug 2017 16:11:55 +0200 Subject: [PATCH] PDF to update students data --- common/urls.py | 1 + stages/models.py | 16 ++++++ stages/pdf.py | 106 +++++++++++++++++++++++++++++++++++++ stages/tests.py | 29 ++++++---- stages/views.py | 23 ++++++++ templates/admin/index.html | 1 + 6 files changed, 167 insertions(+), 9 deletions(-) diff --git a/common/urls.py b/common/urls.py index abbc506..ebd9891 100644 --- a/common/urls.py +++ b/common/urls.py @@ -20,6 +20,7 @@ urlpatterns = [ url(r'^classes/(?P\d+)/$', views.KlassView.as_view(), name='class'), url(r'^imputations/export/$', views.imputations_export, name='imputations_export'), + url(r'^print/update_form/$', views.print_update_form, name='print_update_form'), # AJAX/JSON urls url(r'^section/(?P\d+)/periods/', views.section_periods, name='section_periods'), diff --git a/stages/models.py b/stages/models.py index cbce1e5..60ab485 100644 --- a/stages/models.py +++ b/stages/models.py @@ -161,6 +161,18 @@ class Student(models.Model): def __str__(self): return '%s %s' % (self.last_name, self.first_name) + @property + def civility(self): + return 'Monsieur' if self.gender == 'M' else 'Madame' + + @property + def full_name(self): + return '{0} {1}'.format(self.first_name, self.last_name) + + @property + def pcode_city(self): + return '{0} {1}'.format(self.pcode, self.city) + def save(self, **kwargs): if self.archived and not self.archived_text: # Fill archived_text with training data, JSON-formatted @@ -222,6 +234,10 @@ class Corporation(models.Model): sect = ' (%s)' % self.sector if self.sector else '' return "%s%s, %s %s" % (self.name, sect, self.pcode, self.city) + @property + def pcode_city(self): + return '{0} {1}'.format(self.pcode, self.city) + class CorpContact(models.Model): corporation = models.ForeignKey(Corporation, verbose_name='Institution', on_delete=models.CASCADE) diff --git a/stages/pdf.py b/stages/pdf.py index de92b54..fa9bfc4 100644 --- a/stages/pdf.py +++ b/stages/pdf.py @@ -71,3 +71,109 @@ class ChargeSheetPDF(SimpleDocTemplate): self.story.append(Paragraph('la direction', style_normal)) self.story.append(PageBreak()) self.build(self.story) + + +class UpdateDataFormPDF(SimpleDocTemplate): + """ + Génération des formulaires PDF de mise à jour des données. + """ + def __init__(self, path): + super().__init__(path, pagesize=A4, topMargin=0*cm, leftMargin=2*cm) + self.text = ( + "Afin de mettre à jour nos bases de données, nous vous serions reconnaissant " + "de contrôler les données ci-dessous qui vous concernent selon votre filière " + "et de retourner le présent document corrigé et complété à votre maître de classe jusqu'au " + "vendredi 9 septembre prochain.

" + "Nous vous remercions de votre précieuse collaboration.

" + "Le secrétariat" + ) + self.underline = '__________________________________' + + def produce(self, klass): + self.story = [] + for student in klass.student_set.all(): + self.story.append(Image(find('img/header.gif'), width=520, height=75)) + self.story.append(Spacer(0, 2*cm)) + destinataire = '{0}
{1}
{2}'.format(student.civility, student.full_name, student.klass) + self.story.append(Paragraph(destinataire, style_adress)) + self.story.append(Spacer(0, 2*cm)) + self.story.append(Paragraph('{0},
'.format(student.civility), style_normal)) + self.story.append(Paragraph(self.text, style_normal)) + self.story.append(Spacer(0, 2*cm)) + + data = [['Données enregistrées', 'Données corrigées et/ou complétées']] + t = Table(data, colWidths=[8*cm, 8*cm]) + t.setStyle(TableStyle([ + ('ALIGN', (0, 0), (-1, -1), 'LEFT'), + ('FONT', (0, 0), (-1, -1), 'Helvetica-Bold'), + ])) + t.hAlign = TA_CENTER + self.story.append(t) + + # Personal data + data = [ + ['NOM', student.last_name, self.underline], + ['PRENOM', student.first_name, self.underline], + ['ADRESSE', student.street, self.underline], + ['LOCALITE', student.pcode_city, self.underline], + ['MOBILE', student.mobile, self.underline], + ['CLASSE', student.klass, self.underline], + ['', '', ''], + ] + + # Corporation data + if self.is_corp_required(student.klass.name): + if student.corporation is None: + data.extend([ + ["Données de l'Employeur", '', ''], + ['NOM', '', self.underline], + ['ADRESSE', '', self.underline], + ['LOCALITE', '', self.underline], + ['', '', ''] + ]) + else: + data.extend([ + ["Données de l'Employeur", '', ''], + ['NOM', student.corporation.name, self.underline], + ['ADRESSE', student.corporation.street, self.underline], + ['LOCALITE', student.corporation.pcode_city, self.underline], + ['', '', ''] + ]) + + # Instructor data + if self.is_instr_required(student.klass.name): + if student.instructor is None: + data.extend([ + ['Données du FEE/FPP (personne de contact pour les informations)', '', ''], + ['NOM', '', self.underline], + ['PRENOM', '', self.underline], + ['TELEPHONE', '', self.underline], + ['E-MAIL', '', self.underline], + ]) + else: + data.extend([ + ['Données du FEE/FPP (personne de contact pour les informations)', '', ''], + ['NOM', student.instructor.last_name, self.underline], + ['PRENOM', student.instructor.first_name, self.underline], + ['TELEPHONE', student.instructor.tel, self.underline], + ['E-MAIL', student.instructor.email, self.underline], + ]) + + t = Table(data, colWidths=[3*cm, 5*cm, 8*cm]) + t.setStyle(TableStyle([ + ('ALIGN', (1, 0), (-1, -1), 'LEFT'), + ('FONT', (0, 0), (0, -1), 'Helvetica-Bold'), + ])) + t.hAlign = TA_CENTER + self.story.append(t) + self.story.append(PageBreak()) + if len(self.story) == 0: + self.story.append(Paragraph("Pas d'élèves dans cette classe", style_normal)) + + self.build(self.story) + + def is_corp_required(self, klass_name): + return any(el in klass_name for el in ['FE', 'EDS', 'EDEpe']) + + def is_instr_required(self, klass_name): + return any(el in klass_name for el in ['FE', 'EDS']) diff --git a/stages/tests.py b/stages/tests.py index 317e51f..0c9ad8e 100644 --- a/stages/tests.py +++ b/stages/tests.py @@ -18,15 +18,24 @@ class StagesTest(TestCase): @classmethod def setUpTestData(cls): Section.objects.bulk_create([ - Section(name='ASE'), Section(name='ASSC'), Section(name='EDE') + Section(name='ASE'), Section(name='ASSC'), Section(name='EDE'), Section(name='EDS') ]) sect_ase = Section.objects.get(name='ASE') lev1 = Level.objects.create(name='1') lev2 = Level.objects.create(name='2') 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) dom_hand = Domain.objects.create(name="handicap") dom_pe = Domain.objects.create(name="petite enfance") + corp = Corporation.objects.create( + name="Centre pédagogique XY", typ="Institution", street="Rue des champs 12", + city="Moulineaux", pcode="2500", + ) + contact = CorpContact.objects.create( + corporation=corp, title="Monsieur", first_name="Jean", last_name="Horner", + is_main=True, role="Responsable formation", + ) Student.objects.bulk_create([ Student(first_name="Albin", last_name="Dupond", birth_date="1994-05-12", pcode="2300", city="La Chaux-de-Fonds", klass=klass1), @@ -36,16 +45,10 @@ class StagesTest(TestCase): pcode="2053", city="Cernier", klass=klass1), Student(first_name="André", last_name="Allemand", birth_date="1994-10-11", pcode="2314", city="La Sagne", klass=klass2), + Student(first_name="Gil", last_name="Schmid", birth_date="1996-02-14", + pcode="2000", city="Neuchâtel", klass=klass3, corporation=corp), ]) ref1 = Teacher.objects.create(first_name="Julie", last_name="Caux", abrev="JCA") - corp = Corporation.objects.create( - name="Centre pédagogique XY", typ="Institution", street="Rue des champs 12", - city="Moulineaux", pcode="2500", - ) - contact = CorpContact.objects.create( - corporation=corp, title="Monsieur", first_name="Jean", last_name="Horner", - is_main=True, role="Responsable formation", - ) cls.p1 = Period.objects.create( title="Stage de pré-sensibilisation", start_date="2012-11-26", end_date="2012-12-07", section=sect_ase, level=lev1, @@ -130,6 +133,14 @@ class StagesTest(TestCase): self.assertEqual(len(decoded), 2) self.assertEqual([item['priority'] for item in decoded], [True, False]) + def test_export_update_forms(self): + self.client.login(username='me', password='mepassword') + response = self.client.get(reverse('print_update_form')) + self.assertEqual( + response['Content-Disposition'], 'attachment; filename="modification.zip"' + ) + self.assertGreater(int(response['Content-Length']), 10) + class PeriodTest(TestCase): def setUp(self): diff --git a/stages/views.py b/stages/views.py index d2a87a8..c2cf81d 100644 --- a/stages/views.py +++ b/stages/views.py @@ -1,4 +1,7 @@ import json +import os +import tempfile +import zipfile from collections import OrderedDict from datetime import date, datetime, timedelta @@ -23,6 +26,7 @@ from .models import ( Klass, Section, Student, Teacher, Corporation, CorpContact, Course, Period, Training, Availability, ) +from .pdf import UpdateDataFormPDF from .utils import is_int @@ -628,3 +632,22 @@ def imputations_export(request): response['Content-Disposition'] = 'attachment; filename=%s%s.xlsx' % ( 'Imputations_export', date.strftime(date.today(), '%Y-%m-%d')) return response + + +def print_update_form(request): + """ + PDF form to update personal data + """ + tmp_file = tempfile.NamedTemporaryFile() + with zipfile.ZipFile(tmp_file, mode='w', compression=zipfile.ZIP_DEFLATED) as filezip: + for klass in Klass.objects.filter(level__gte=2).exclude(section__name='MP_ASSC').exclude(section__name='MP_ASE'): + path = os.path.join(tempfile.gettempdir(), '{0}.pdf'.format(klass.name)) + pdf = UpdateDataFormPDF(path) + pdf.produce(klass) + filezip.write(pdf.filename) + break + + with open(filezip.filename, mode='rb') as fh: + response = HttpResponse(fh.read(), content_type='application/zip') + response['Content-Disposition'] = 'attachment; filename="modification.zip"' + return response diff --git a/templates/admin/index.html b/templates/admin/index.html index 3c5b69d..7150580 100644 --- a/templates/admin/index.html +++ b/templates/admin/index.html @@ -78,6 +78,7 @@
  • Exporter les données de stages (récentes)
  • Exporter les données de stages (toutes)
  • Exporter les données comptables
  • +
  • Imprimer les formulaires de MAJ