Add an admin action to print teacher charge sheets
This commit is contained in:
parent
a268f7ddd3
commit
073f012044
7 changed files with 161 additions and 0 deletions
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
83
stages/pdf.py
Normal 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)
|
||||
BIN
stages/static/img/header.gif
Normal file
BIN
stages/static/img/header.gif
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
|
|
@ -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')
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue