Import and send student PDF bulletins
This commit is contained in:
parent
b578ccc363
commit
7a09a92bde
8 changed files with 162 additions and 2 deletions
|
|
@ -11,6 +11,7 @@ urlpatterns = [
|
|||
url(r'^import_students/', views.StudentImportView.as_view(), name='import-students'),
|
||||
url(r'^import_hp/', views.HPImportView.as_view(), name='import-hp'),
|
||||
url(r'^import_hp_contacts/', views.HPContactsImportView.as_view(), name='import-hp-contacts'),
|
||||
url(r'^import_bulletins/', views.ImportBulletinView.as_view(), name='import-bulletins'),
|
||||
|
||||
url(r'^attribution/$', views.AttributionView.as_view(), name='attribution'),
|
||||
url(r'^stages/export/(?P<scope>all)?/?$', views.stages_export, name='stages_export'),
|
||||
|
|
|
|||
|
|
@ -34,3 +34,7 @@ class PeriodForm(forms.Form):
|
|||
|
||||
class UploadHPFileForm(forms.Form):
|
||||
upload = forms.FileField(label='Fichier HyperPlanning')
|
||||
|
||||
|
||||
class UploadBulletinForm(forms.Form):
|
||||
upload = forms.FileField(label='Bulletins CLOEE (pdf)')
|
||||
|
|
|
|||
|
|
@ -189,7 +189,7 @@ class Student(models.Model):
|
|||
|
||||
@property
|
||||
def civility(self):
|
||||
return 'Monsieur' if self.gender == 'M' else 'Madame'
|
||||
return {'M': 'Monsieur', 'F': 'Madame'}.get(self.gender, '')
|
||||
|
||||
@property
|
||||
def full_name(self):
|
||||
|
|
|
|||
BIN
stages/test_files/1ASEFEa.pdf
Normal file
BIN
stages/test_files/1ASEFEa.pdf
Normal file
Binary file not shown.
|
|
@ -4,6 +4,7 @@ from datetime import date
|
|||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core import mail
|
||||
from django.test import TestCase, override_settings
|
||||
from django.urls import reverse
|
||||
from django.utils.html import escape
|
||||
|
|
@ -348,3 +349,32 @@ class ImportTests(TestCase):
|
|||
self.assertContains(response, "NoSIRET est vide à ligne 4. Ligne ignorée")
|
||||
st1.refresh_from_db()
|
||||
self.assertEqual(st1.instructor.last_name, 'Geiser')
|
||||
|
||||
def test_import_and_send_bulletins(self):
|
||||
lev1 = Level.objects.create(name='1')
|
||||
klass1 = Klass.objects.create(
|
||||
name='1ASEFEa',
|
||||
section=Section.objects.create(name='ASE'),
|
||||
level=lev1,
|
||||
)
|
||||
Student.objects.bulk_create([
|
||||
Student(first_name="Albin", last_name="Dupond", birth_date="1994-05-12", gender='M',
|
||||
pcode="2300", city="La Chaux-de-Fonds", email="albin@example.org",
|
||||
klass=klass1),
|
||||
Student(first_name="Justine", last_name="Varrin", birth_date="1994-07-12",
|
||||
pcode="2000", city="Neuchâtel", email="justine@example.org", klass=klass1),
|
||||
Student(first_name="Elvire", last_name="Hickx", birth_date="1994-05-20",
|
||||
pcode="2053", city="Cernier", email="elvire@example.org", klass=klass1),
|
||||
])
|
||||
path = os.path.join(os.path.dirname(__file__), 'test_files', '1ASEFEa.pdf')
|
||||
self.client.login(username='me', password='mepassword')
|
||||
with open(path, 'rb') as fh:
|
||||
response = self.client.post(reverse('import-bulletins'), {'upload': fh}, follow=True)
|
||||
messages = [str(msg) for msg in response.context['messages']]
|
||||
self.assertIn("Impossible de trouver un fichier PDF pour l'étudiant Hickx Elvire", messages)
|
||||
self.assertIn('2 messages sur 3 élèves ont été envoyés', messages)
|
||||
self.assertEqual(len(mail.outbox), 2)
|
||||
# Second email as bcc
|
||||
self.assertEqual(mail.outbox[0].recipients(), ['albin@example.org', 'me@example.org'])
|
||||
self.assertEqual(mail.outbox[1].recipients(), ['justine@example.org', 'me@example.org'])
|
||||
self.assertIn("le bulletin scolaire de Monsieur Albin Dupond", mail.outbox[0].body)
|
||||
|
|
|
|||
115
stages/views.py
115
stages/views.py
|
|
@ -1,5 +1,8 @@
|
|||
import json
|
||||
import os
|
||||
import re
|
||||
from subprocess import PIPE, Popen, call
|
||||
|
||||
import tempfile
|
||||
import zipfile
|
||||
from collections import OrderedDict
|
||||
|
|
@ -10,15 +13,18 @@ from tabimport import CSVImportedFile, FileFactory
|
|||
from django.conf import settings
|
||||
from django.contrib import messages
|
||||
from django.core.files import File
|
||||
from django.core.mail import EmailMessage
|
||||
from django.db.models import Case, Count, When, Q
|
||||
from django.http import HttpResponse, HttpResponseNotAllowed, HttpResponseRedirect
|
||||
from django.shortcuts import get_object_or_404
|
||||
from django.template import loader
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.utils.text import slugify
|
||||
from django.views.generic import DetailView, FormView, TemplateView, ListView
|
||||
|
||||
from .exports import OpenXMLExport
|
||||
from .forms import PeriodForm, StudentImportForm, UploadHPFileForm
|
||||
from .forms import PeriodForm, StudentImportForm, UploadHPFileForm, UploadBulletinForm
|
||||
from .models import (
|
||||
Klass, Section, Option, Student, Teacher, Corporation, CorpContact, Course, Period,
|
||||
Training, Availability,
|
||||
|
|
@ -497,6 +503,113 @@ class HPContactsImportView(ImportViewBase):
|
|||
return {'modified': obj_modified, 'errors': errors}
|
||||
|
||||
|
||||
class ImportBulletinView(FormView):
|
||||
template_name = 'file_import.html'
|
||||
form_class = UploadBulletinForm
|
||||
|
||||
def form_valid(self, form):
|
||||
upfile = form.cleaned_data['upload']
|
||||
klass_name = upfile.name[:-4]
|
||||
|
||||
try:
|
||||
klass = Klass.objects.get(name=klass_name)
|
||||
except Klass.DoesNotExist:
|
||||
messages.error(self.request, "La classe %s n'existe pas !" % klass_name)
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
# Check poppler-utils presence on server
|
||||
res = call(['pdftotext', '-v'], stderr=PIPE)
|
||||
if res != 0:
|
||||
messages.error(self.request, "Unable to find pdftotext on your system. Try to install the poppler-utils package.")
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
# Move the file to MEDIA directory
|
||||
pdf_origin = os.path.join(settings.MEDIA_ROOT, upfile.name)
|
||||
with open(pdf_origin, 'wb+') as destination:
|
||||
for chunk in upfile.chunks():
|
||||
destination.write(chunk)
|
||||
|
||||
try:
|
||||
self.send_bulletins(klass, pdf_origin)
|
||||
except Exception as err:
|
||||
if settings.DEBUG:
|
||||
raise
|
||||
else:
|
||||
messages.error(self.request, "Erreur durant l'envoi des bulletins PDF: %s" % err)
|
||||
return HttpResponseRedirect(reverse('admin:index'))
|
||||
|
||||
def send_bulletins(self, klass, pdf_path):
|
||||
path = os.path.abspath(pdf_path)
|
||||
student_regex = '[E|É]lève\s*:\s*([^\n]*)'
|
||||
# Directory automatically deleted when the variable is deleted
|
||||
_temp_dir = tempfile.TemporaryDirectory()
|
||||
temp_dir = _temp_dir.name
|
||||
|
||||
os.system("pdfseparate %s %s/%s_%%d.pdf" % (path, temp_dir, os.path.basename(path)[:-4]))
|
||||
|
||||
# Look for student names in each separated PDF and rename PDF with student name
|
||||
for filename in os.listdir(temp_dir):
|
||||
p = Popen(['pdftotext', os.path.join(temp_dir, filename), '-'],
|
||||
shell=False, stdout=PIPE, stderr=PIPE)
|
||||
output, errors = p.communicate()
|
||||
m = re.search(student_regex, output.decode('utf-8'))
|
||||
if not m:
|
||||
print("Unable to find student name in %s" % filename)
|
||||
continue
|
||||
student_name = m.groups()[0]
|
||||
os.rename(
|
||||
os.path.join(temp_dir, filename),
|
||||
"%s.pdf" % (os.path.join(temp_dir, slugify(student_name)))
|
||||
)
|
||||
|
||||
email_sent = 0
|
||||
pdf_file_list = os.listdir(temp_dir)
|
||||
|
||||
students = klass.student_set.exclude(archived=True)
|
||||
for student in students:
|
||||
if not student.email:
|
||||
messages.warning(self.request, "L'étudiant %s ne possède pas d'email." % student)
|
||||
continue
|
||||
context = {
|
||||
'student_name': " ".join([student.civility, student.first_name, student.last_name]),
|
||||
'sender_name': " ".join([self.request.user.first_name, self.request.user.last_name]),
|
||||
'sender_email': self.request.user.email,
|
||||
}
|
||||
student_filename = slugify('{0} {1}'.format(student.last_name, student.first_name))
|
||||
student_filename = '{0}.pdf'.format(student_filename)
|
||||
try:
|
||||
attach_idx = pdf_file_list.index(student_filename)
|
||||
except ValueError:
|
||||
messages.error(self.request,
|
||||
"Impossible de trouver un fichier PDF pour l'étudiant %s" % student)
|
||||
continue
|
||||
|
||||
to = [student.email]
|
||||
if student.instructor and student.instructor.email:
|
||||
to.append(student.instructor.email)
|
||||
email = EmailMessage(
|
||||
subject='Bulletins scolaires',
|
||||
body=loader.render_to_string('email/bulletins_scolaires.txt', context),
|
||||
from_email=self.request.user.email,
|
||||
to=to,
|
||||
bcc=[self.request.user.email],
|
||||
)
|
||||
# Attach PDF file to email
|
||||
pdf_file = os.path.join(temp_dir, pdf_file_list[attach_idx])
|
||||
pdf_name = 'bulletin_scol_{0}'.format(student_filename)
|
||||
with open(pdf_file, 'rb') as pdf:
|
||||
email.attach(pdf_name, pdf.read(), 'application/pdf')
|
||||
|
||||
try:
|
||||
email.send(fail_silently=False)
|
||||
email_sent += 1
|
||||
except Exception as err:
|
||||
messages.error(self.request, "Échec d'envoi pour le candidat {0} ({1})".format(student, err))
|
||||
|
||||
messages.warning(self.request, '{0} messages sur {1} élèves ont été envoyés'
|
||||
.format(email_sent, students.count()))
|
||||
|
||||
|
||||
EXPORT_FIELDS = [
|
||||
# Student fields
|
||||
('ID externe', 'student__ext_id'),
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@
|
|||
<li><a href="{% url 'import-students' %}">Importer un fichier d'étudiants</a></li>
|
||||
<li><a href="{% url 'import-hp' %}">Importer le fichier HP</a></li>
|
||||
<li><a href="{% url 'import-hp-contacts' %}">Importer les formateurs (fichier HP)</a></li>
|
||||
<li><a href="{% url 'import-bulletins' %}">Envoyer les bulletins (par classe)</a></li>
|
||||
<li style="margin-top: 1em;"><a href="{% url 'stages_export' %}">Exporter les données de stages</a> (récentes)</li>
|
||||
<li><a href="{% url 'stages_export' 'all' %}">Exporter les données de stages</a> (toutes)</li>
|
||||
<li><a href="{% url 'imputations_export' %}">Exporter les données comptables</a></li>
|
||||
|
|
|
|||
11
templates/email/bulletins_scolaires.txt
Normal file
11
templates/email/bulletins_scolaires.txt
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
Madame, Monsieur,
|
||||
|
||||
Vous trouverez en annexe le bulletin scolaire de {{ student_name }} pour le semestre écoulé.
|
||||
|
||||
Nous vous souhaitons bonne réception de ce document et vous prions de recevoir, Madame, Monsieur, nos salutations les plus cordiales.
|
||||
|
||||
Secrétariat de l'EPC
|
||||
tél. 032 886 33 00
|
||||
|
||||
{{ sender_name }}
|
||||
{{ sender_email }}
|
||||
Loading…
Add table
Add a link
Reference in a new issue