Add an admin action to print teacher charge sheets

This commit is contained in:
Claude Paroz 2017-07-14 18:47:56 +02:00
parent a268f7ddd3
commit 073f012044
7 changed files with 161 additions and 0 deletions

View file

@ -151,4 +151,6 @@ INSTRUCTOR_IMPORT_MAPPING = {
'MAIL_FORMATEUR': 'email',
}
CHARGE_SHEET_TITLE = "Feuille de charge pour l'année scolaire 2017-2018"
from .local_settings import *

View file

@ -2,3 +2,4 @@ Django==1.11.2
tabimport>=0.4.0
openpyxl==2.2.6
xlrd
reportlab

View file

@ -1,11 +1,40 @@
import os
import tempfile
import zipfile
from django import forms
from django.contrib import admin
from django.db import models
from django.http import HttpResponse
from stages.models import (
Teacher, Student, Section, Level, Klass, Referent, Corporation,
CorpContact, Domain, Period, Availability, Training, Course,
)
from stages.pdf import ChargeSheetPDF
def print_charge_sheet(modeladmin, request, queryset):
"""
Génère un pdf pour chaque enseignant, écrit le fichier créé
dans une archive et renvoie une archive de pdf
"""
filename = 'archive_FeuillesDeCharges.zip'
path = os.path.join(tempfile.gettempdir(), filename)
with zipfile.ZipFile(path, mode='w', compression=zipfile.ZIP_DEFLATED) as filezip:
for teacher in queryset:
activities = teacher.calc_activity()
pdf = ChargeSheetPDF(teacher)
pdf.produce(activities)
filezip.write(pdf.filename)
with open(filezip.filename, mode='rb') as fh:
response = HttpResponse(fh.read(), content_type='application/zip')
response['Content-Disposition'] = 'attachment; filename="{0}"'.format(filename)
return response
print_charge_sheet.short_description = "Imprimer les feuilles de charge"
class ArchivedListFilter(admin.BooleanFieldListFilter):
@ -35,6 +64,7 @@ class KlassAdmin(admin.ModelAdmin):
class TeacherAdmin(admin.ModelAdmin):
list_display = ('__str__', 'abrev', 'email')
actions = [print_charge_sheet]
class StudentAdmin(admin.ModelAdmin):

View file

@ -78,6 +78,29 @@ class Teacher(models.Model):
def __str__(self):
return '{0} {1}'.format(self.last_name, self.first_name)
def calc_activity(self):
"""
Return a dictionary of calculations relative to teacher courses.
"""
mandats = self.course_set.filter(subject__startswith='#')
ens = self.course_set.exclude(subject__startswith='#')
tot_mandats = mandats.aggregate(models.Sum('period'))['period__sum'] or 0
tot_ens = ens.aggregate(models.Sum('period'))['period__sum'] or 0
tot_formation = int(round((tot_mandats + tot_ens) / 1900 * 250))
tot_trav = self.previous_report + tot_mandats + tot_ens + tot_formation
tot_paye = tot_trav
if self.rate == 100 and tot_paye != 100:
tot_paye = 2150
return {
'mandats': mandats,
'tot_mandats': tot_mandats,
'tot_ens': tot_ens,
'tot_formation': tot_formation,
'tot_trav': tot_trav,
'tot_paye': tot_paye,
'report': tot_trav - tot_paye,
}
class Student(models.Model):
ext_id = models.IntegerField(null=True, unique=True, verbose_name='ID externe')

83
stages/pdf.py Normal file
View file

@ -0,0 +1,83 @@
import os
import tempfile
from datetime import date
from django.conf import settings
from django.contrib.staticfiles.finders import find
from reportlab.platypus import (SimpleDocTemplate, Paragraph, Spacer,
PageBreak, Table, TableStyle, Image)
from reportlab.lib.pagesizes import A4, landscape
from reportlab.lib.units import cm
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_RIGHT
from reportlab.lib import colors
from reportlab.lib.styles import ParagraphStyle as PS
style_8_c = PS(name='CORPS', fontName='Helvetica', fontSize=6, alignment = TA_CENTER)
style_normal = PS(name='CORPS', fontName='Helvetica', fontSize=8, alignment = TA_LEFT)
style_mandat = PS(name='CORPS', fontName='Helvetica', fontSize=8, alignment = TA_LEFT, leftIndent=30)
style_bold = PS(name='CORPS', fontName='Helvetica-Bold', fontSize=10, alignment = TA_LEFT)
style_title = PS(name='CORPS', fontName='Helvetica-Bold', fontSize=12, alignment = TA_LEFT, spaceBefore=2*cm)
style_adress = PS(name='CORPS', fontName='Helvetica', fontSize=10, alignment = TA_LEFT, leftIndent=280)
style_normal_right = PS(name='CORPS', fontName='Helvetica', fontSize=8, alignment = TA_RIGHT)
class ChargeSheetPDF(SimpleDocTemplate):
"""
Génération des feuilles de charges en pdf.
"""
def __init__(self, teacher):
self.teacher = teacher
filename = '{0}_{1}.pdf'.format(teacher.last_name, teacher.first_name)
path = os.path.join(tempfile.gettempdir(), filename)
super().__init__(path, pagesize=A4, topMargin=0*cm, leftMargin=2*cm)
def produce(self, activities):
self.story = []
self.story.append(Image(find('img/header.gif'), width=520, height=75))
self.story.append(Spacer(0, 2*cm))
destinataire = '{0}<br/>{1}'.format(self.teacher.civility, str(self.teacher))
self.story.append(Paragraph(destinataire, style_adress))
self.story.append(Spacer(0, 2*cm))
data = [[settings.CHARGE_SHEET_TITLE]]
data.append(["Report de l'année précédente",
'{0:3d} pér.'.format(self.teacher.previous_report) ])
data.append(['Mandats',
'{0:3d} pér.'.format(activities['tot_mandats'])])
for act in activities['mandats']:
data.append([' * {0} ({1} pér.)'.format(act.subject, act.period)])
data.append(['Enseignement (coef.2)',
'{0:3d} pér.'.format(activities['tot_ens'])])
data.append(['Formation continue et autres tâches',
'{0:3d} pér.'.format(activities['tot_formation'])])
data.append(['Total des heures travaillées',
'{0:3d} pér.'.format(activities['tot_trav']),
'{0:4.1f} %'.format(activities['tot_trav']/21.50)])
data.append(['Total des heures payées',
'{0:3d} pér.'.format(activities['tot_paye']),
'{0:4.1f} %'.format(activities['tot_paye']/21.50)])
data.append(["Report à l'année prochaine",
'{0:3d} pér.'.format(activities['report'])])
t = Table(data, colWidths=[12*cm, 2*cm, 2*cm] )
t.setStyle(TableStyle([('ALIGN',(1,0),(-1,-1),'RIGHT'),
('FONT', (0,0),(-1,0), 'Helvetica-Bold'),
('LINEBELOW', (0,0),(-1,0), 0.5, colors.black),
('LINEABOVE', (0,-3) ,(-1,-1), 0.5, colors.black),
('FONT', (0,-2),(-1,-2), 'Helvetica-Bold'),
]))
t.hAlign = TA_CENTER
self.story.append(t)
self.story.append(Spacer(0, 2*cm))
d = 'La Chaux-de-Fonds, le {0}'.format(date.today().strftime('%d.%m.%y'))
self.story.append(Paragraph(d, style_normal))
self.story.append(Spacer(0, 0.5*cm))
self.story.append(Paragraph('la direction', style_normal))
self.story.append(PageBreak())
self.build(self.story)

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

@ -149,6 +149,28 @@ class PeriodTest(TestCase):
self.assertEqual(per.weeks, 2)
class TeacherTests(TestCase):
def setUp(self):
User.objects.create_superuser('me', 'me@example.org', 'mepassword')
def test_export_charge_sheet(self):
Teacher.objects.create(
first_name='Laurie', last_name='Bernasconi', birth_date='1974-08-08'
)
change_url = reverse('admin:stages_teacher_changelist')
self.client.login(username='me', password='mepassword')
response = self.client.post(change_url, {
'action': 'print_charge_sheet',
'_selected_action': Teacher.objects.values_list('pk', flat=True)
}, follow=True)
self.assertEqual(
response['Content-Disposition'],
'attachment; filename="archive_FeuillesDeCharges.zip"'
)
self.assertEqual(response['Content-Type'], 'application/zip')
self.assertGreater(len(response.content), 200)
class ImportTests(TestCase):
def setUp(self):
User.objects.create_user('me', 'me@example.org', 'mepassword')