Skip to content
Snippets Groups Projects
Commit caf31370 authored by magicfelix's avatar magicfelix
Browse files

Implement coursebook printout

parent 11aa9d33
No related branches found
No related tags found
No related merge requests found
...@@ -8,7 +8,13 @@ from calendarweek import CalendarWeek ...@@ -8,7 +8,13 @@ from calendarweek import CalendarWeek
from celery.result import allow_join_result from celery.result import allow_join_result
from celery.states import SUCCESS from celery.states import SUCCESS
from aleksis.apps.chronos.models import Event, ExtraLesson, Lesson, LessonPeriod, ValidityRange from aleksis.apps.chronos.models import (
Lesson,
LessonPeriod,
LessonSubstitution,
Subject,
ValidityRange,
)
from aleksis.core.celery import app from aleksis.core.celery import app
from aleksis.core.models import Group, PDFFile from aleksis.core.models import Group, PDFFile
from aleksis.core.util.celery_progress import ProgressRecorder, recorded_task from aleksis.core.util.celery_progress import ProgressRecorder, recorded_task
...@@ -29,14 +35,10 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog ...@@ -29,14 +35,10 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog
group = Group.objects.get(pk=group) group = Group.objects.get(pk=group)
file_object = PDFFile.objects.get(pk=file_object) file_object = PDFFile.objects.get(pk=file_object)
groups_q = ( groups_q = Q(lesson_period__lesson__groups=group) | Q(
Q(lesson_period__lesson__groups=group) lesson_period__lesson__groups__parent_groups=group
| Q(lesson_period__lesson__groups__parent_groups=group)
| Q(extra_lesson__groups=group)
| Q(extra_lesson__groups__parent_groups=group)
| Q(event__groups=group)
| Q(event__groups__parent_groups=group)
) )
subjects = Subject.objects.filter(lessons__groups=group).distinct()
personal_notes = ( personal_notes = (
PersonalNote.objects.prefetch_related( PersonalNote.objects.prefetch_related(
"lesson_period__substitutions", "lesson_period__lesson__teachers" "lesson_period__substitutions", "lesson_period__lesson__teachers"
...@@ -45,15 +47,10 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog ...@@ -45,15 +47,10 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog
.filter(groups_q) .filter(groups_q)
.filter(groups_of_person=group) .filter(groups_of_person=group)
) )
documentations = LessonDocumentation.objects.not_empty().filter(groups_q)
recorder.set_progress(2, _number_of_steps, _("Sort data ...")) recorder.set_progress(2, _number_of_steps, _("Sort data ..."))
sorted_documentations = {"extra_lesson": {}, "event": {}, "lesson_period": {}} sorted_personal_notes = {"lesson_period": {}, "person": {}}
sorted_personal_notes = {"extra_lesson": {}, "event": {}, "lesson_period": {}, "person": {}}
for documentation in documentations:
key = documentation.register_object.label_
sorted_documentations[key][documentation.register_object_key] = documentation
for note in personal_notes: for note in personal_notes:
key = note.register_object.label_ key = note.register_object.label_
...@@ -66,41 +63,9 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog ...@@ -66,41 +63,9 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog
# Get all lesson periods for the selected group # Get all lesson periods for the selected group
lesson_periods = LessonPeriod.objects.filter_group(group).distinct() lesson_periods = LessonPeriod.objects.filter_group(group).distinct()
events = Event.objects.filter_group(group).distinct()
extra_lessons = ExtraLesson.objects.filter_group(group).distinct()
weeks = CalendarWeek.weeks_within(group.school_term.date_start, group.school_term.date_end) weeks = CalendarWeek.weeks_within(group.school_term.date_start, group.school_term.date_end)
register_objects_by_day = {} register_objects_by_week = {}
for extra_lesson in extra_lessons:
day = extra_lesson.date
register_objects_by_day.setdefault(day, []).append(
(
extra_lesson,
sorted_documentations["extra_lesson"].get(extra_lesson.pk),
sorted_personal_notes["extra_lesson"].get(extra_lesson.pk, []),
None,
)
)
for event in events:
day_number = (event.date_end - event.date_start).days + 1
for i in range(day_number):
day = event.date_start + timedelta(days=i)
event_copy = deepcopy(event)
event_copy.annotate_day(day)
# Skip event days if it isn't inside the timetable schema
if not (event_copy.raw_period_from_on_day and event_copy.raw_period_to_on_day):
continue
register_objects_by_day.setdefault(day, []).append(
(
event_copy,
sorted_documentations["event"].get(event.pk),
sorted_personal_notes["event"].get(event.pk, []),
None,
)
)
recorder.set_progress(4, _number_of_steps, _("Sort lesson data ...")) recorder.set_progress(4, _number_of_steps, _("Sort lesson data ..."))
...@@ -109,27 +74,28 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog ...@@ -109,27 +74,28 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog
group.school_term.date_end, group.school_term.date_end,
) )
for lesson_period in lesson_periods: for week in weeks:
for week in weeks: register_objects_by_week.setdefault(week.week, {"substitutions": [], "documentations": {}})
day = week[lesson_period.period.weekday] for substitution in LessonSubstitution.objects.filter(
year=week.year, week=week.week, lesson_period__lesson__groups=group
if ( ):
lesson_period.lesson.validity.date_start register_objects_by_week[week.week]["substitutions"].append(substitution)
<= day for subject in subjects:
<= lesson_period.lesson.validity.date_end documentation = (
): LessonDocumentation.objects.not_empty()
filtered_documentation = sorted_documentations["lesson_period"].get( .filter(
f"{lesson_period.pk}_{week.week}_{week.year}" year=week.year,
) week=week.week,
filtered_personal_notes = sorted_personal_notes["lesson_period"].get( lesson_period__lesson__subject=subject,
f"{lesson_period.pk}_{week.week}_{week.year}", [] lesson_period__lesson__groups=group,
)
substitution = lesson_period.get_substitution(week)
register_objects_by_day.setdefault(day, []).append(
(lesson_period, filtered_documentation, filtered_personal_notes, substitution)
) )
.first()
)
if not documentation:
continue
register_objects_by_week[week.week]["documentations"][
subject.short_name
] = documentation
recorder.set_progress(5, _number_of_steps, _("Load statistics ...")) recorder.set_progress(5, _number_of_steps, _("Load statistics ..."))
...@@ -148,33 +114,14 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog ...@@ -148,33 +114,14 @@ def generate_full_register_printout(group: int, file_object: int, recorder: Prog
context["extra_marks"] = ExtraMark.objects.all() context["extra_marks"] = ExtraMark.objects.all()
context["group"] = group context["group"] = group
context["weeks"] = weeks context["weeks"] = weeks
context["register_objects_by_day"] = register_objects_by_day context["register_objects_by_week"] = register_objects_by_week
context["register_objects"] = list(lesson_periods) + list(events) + list(extra_lessons) context["register_objects"] = list(lesson_periods)
context["today"] = date.today() context["today"] = date.today()
context["lessons"] = (
group.lessons.all()
.select_related(None)
.prefetch_related(None)
.select_related("validity", "subject")
.prefetch_related("teachers", "lesson_periods")
)
context["child_groups"] = (
group.child_groups.all()
.select_related(None)
.prefetch_related(None)
.prefetch_related(
"lessons",
"lessons__validity",
"lessons__subject",
"lessons__teachers",
"lessons__lesson_periods",
)
)
recorder.set_progress(6, _number_of_steps, _("Generate template ...")) recorder.set_progress(6, _number_of_steps, _("Generate template ..."))
file_object, result = generate_pdf_from_template( file_object, result = generate_pdf_from_template(
"alsijil/print/full_register.html", context, file_object=file_object "alsijil/print/full_coursebook.html", context, file_object=file_object
) )
recorder.set_progress(7, _number_of_steps, _("Generate PDF ...")) recorder.set_progress(7, _number_of_steps, _("Generate PDF ..."))
......
{% extends "core/base_print.html" %}
{% load static i18n data_helpers week_helpers %}
{% block page_title %}
{% trans "Coursebook" %} {{ group.name }}
{% endblock %}
{% block extra_head %}
<link rel="stylesheet" href="{% static 'css/alsijil/full_register.css' %}"/>
{% endblock %}
{% block content %}
<div class="center-align">
<h1>{% trans 'Coursebook' %}</h1>
<h5>{{ school_term }}</h5>
<p>({{ school_term.date_start }}–{{ school_term.date_end }})</p>
{% static "img/aleksis-banner.svg" as aleksis_banner %}
<img src="{% firstof SITE_PREFERENCES.theme__logo.url aleksis_banner %}"
alt="{{ SITE_PREFERENCES.general__title }} – Logo" class="max-size-600 center">
<h4 id="group-desc">
{{ group.name }}
</h4>
<p id="group-owners" class="flow-text">
{% trans 'Owners' %}:
{{ group.owners.all|join:', ' }}
</p>
<p id="printed-info">
{% trans 'Printed on' %} {{ today }}
</p>
</div>
<div>
<hr/>
</div>
<div>
<p>
{% blocktrans %}
This printout is intended for archival purposes. The main copy of
the class register is stored in the AlekSIS School Information
System.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
Copies of the class register, both digital and as printout, must
only be kept inside the school and/or on devices authorised by the
school.
{% endblocktrans %}
</p>
<p>
{% blocktrans %}
The owner of the group and the headteacher confirm the above, as
well as the correctness of this printout.
{% endblocktrans %}
</p>
<div id="signatures">
<div class="signature">
{% trans 'Owners' %}
</div>
<div class="signature">
{% trans 'Headteacher' %}
</div>
</div>
</div>
<div class="page-break">&nbsp;</div>
<h4>{% trans "Abbreviations" %}</h4>
<h5>{% trans "General" %}</h5>
<ul class="collection">
<li class="collection-item">
<strong>(a)</strong> {% trans "Absent" %}
</li>
<li class="collection-item">
<strong>(b)</strong> {% trans "Late" %}
</li>
<li class="collection-item">
<strong>(u)</strong> {% trans "Unexcused" %}
</li>
<li class="collection-item">
<strong>(e)</strong> {% trans "Excused" %}
</li>
</ul>
{% if excuse_types %}
<h5>{% trans "Custom excuse types" %}</h5>
<ul class="collection">
{% for excuse_type in excuse_types %}
<li class="collection-item">
<strong>({{ excuse_type.short_name }})</strong> {{ excuse_type.name }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if excuse_types_not_absent %}
<h5>{% trans "Custom excuse types (not counted as absent)" %}</h5>
<ul class="collection">
{% for excuse_type in excuse_types_not_absent %}
<li class="collection-item">
<strong>({{ excuse_type.short_name }})</strong> {{ excuse_type.name }}
</li>
{% endfor %}
</ul>
{% endif %}
{% if extra_marks %}
<h5>{% trans "Available extra marks" %}</h5>
<ul class="collection">
{% for extra_mark in extra_marks %}
<li class="collection-item">
<strong>{{ extra_mark.short_name }}</strong> {{ extra_mark.name }}
</li>
{% endfor %}
</ul>
{% endif %}
<div class="page-break">&nbsp;</div>
<h4>{% trans 'Persons in group' %} {{ group.name }}</h4>
<table id="persons">
<thead>
<tr>
<th>{% trans 'No.' %}</th>
<th>{% trans 'Last name' %}</th>
<th>{% trans 'First name' %}</th>
<th>{% trans 'Sex' %}</th>
<th>{% trans 'Date of birth' %}</th>
<th>{% trans '(a)' %}</th>
<th>{% trans "Sum (e)" %}</th>
<th>{% trans "(e)" %}</th>
{% for excuse_type in excuse_types %}
<th>({{ excuse_type.short_name }})</th>
{% endfor %}
<th>{% trans '(u)' %}</th>
{% for excuse_type in excuse_types_not_absent %}
<th>({{ excuse_type.short_name }})</th>
{% endfor %}
<th>{% trans '(b)' %}</th>
{% for extra_mark in extra_marks %}
<th>{{ extra_mark.short_name }}</th>
{% endfor %}
</tr>
</thead>
<tbody>
{% for person in persons %}
<tr>
<td>{{ forloop.counter }}</td>
<td>{{ person.last_name }}</td>
<td>{{ person.first_name }}</td>
<td>{{ person.get_sex_display }}</td>
<td>{{ person.date_of_birth }}</td>
<td>{{ person.absences_count }}</td>
<td>{{ person.excused }}</td>
<td>{{ person.excused_without_excuse_type }}</td>
{% for excuse_type in excuse_types %}
<td>{{ person|get_dict:excuse_type.count_label }}</td>
{% endfor %}
<td>{{ person.unexcused }}</td>
{% for excuse_type in excuse_types_not_absent %}
<td>{{ person|get_dict:excuse_type.count_label }}</td>
{% endfor %}
<td>{{ person.tardiness }}'/{{ person.tardiness_count }}&times;</td>
{% for extra_mark in extra_marks %}
<td>{{ person|get_dict:extra_mark.count_label }}</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
<div class="page-break">&nbsp;</div>
{% for person in persons %}
<h4>{% trans 'Personal overview' %}: {{ person.last_name }}, {{ person.first_name }}</h4>
<h5>{% blocktrans %}Contact details{% endblocktrans %}</h5>
<table class="person-info">
<tr>
<td rowspan="6" class="person-img">
{% if person.photo %}
<img src="{{ person.photo.url }}" alt="{{ person.first_name }} {{ person.last_name }}"/>
{% else %}
<img src="{% static 'img/fallback.png' %}" alt="{{ person.first_name }} {{ person.last_name }}"/>
{% endif %}
</td>
<td><i class="material-icons iconify" data-icon="mdi:account-outline"></i></td>
<td colspan="2">{{ person.first_name }} {{ person.additional_name }} {{ person.last_name }}</td>
</tr>
<tr>
<td><i class="material-icons iconify" data-icon="mdi:human-non-binary"></i></td>
<td colspan="2">{{ person.get_sex_display }}</td>
</tr>
<tr>
<td><i class="material-icons iconify" data-icon="mdi:map-marker-outline"></i></td>
<td>{{ person.street }} {{ person.housenumber }}</td>
<td>{{ person.postal_code }} {{ person.place }}</td>
</tr>
<tr>
<td><i class="material-icons iconify" data-icon="mdi:phone-outline"></i></td>
<td>{{ person.phone_number }}</td>
<td>{{ person.mobile_number }}</td>
</tr>
<tr>
<td><i class="material-icons iconify" data-icon="mdi:email-outline"></i></td>
<td colspan="2">{{ person.email }}</td>
</tr>
<tr>
<td><i class="material-icons iconify" data-icon="mdi:cake"></i></td>
<td colspan="2">{{ person.date_of_birth|date }}</td>
</tr>
</table>
<div class="row">
<div class="col s6">
<h5>{% trans 'Absences and tardiness' %}</h5>
<table>
<tr>
<th colspan="3">{% trans 'Absences' %}</th>
<td>{{ person.absences_count }}</td>
</tr>
<tr>
<td rowspan="{{ excuse_types.count|add:3 }}" style="width: 16mm;"
class="rotate small-print">{% trans "thereof" %}</td>
<th colspan="2">{% trans 'Excused' %}</th>
<td>{{ person.excused }}</td>
</tr>
<tr>
<td rowspan="{{ excuse_types.count|add:1 }}" style="width: 16mm;"
class="rotate small-print">{% trans "thereof" %}</td>
<th>{% trans "Without excuse type" %}</th>
<td>{{ person.excused_without_excuse_type }}</td>
</tr>
{% for excuse_type in excuse_types %}
<tr>
<th>{{ excuse_type.name }}</th>
<td>{{ person|get_dict:excuse_type.count_label }}</td>
</tr>
{% endfor %}
<tr>
<th colspan="2">{% trans 'Unexcused' %}</th>
<td>{{ person.unexcused }}</td>
</tr>
{% for excuse_type in excuse_types_not_absent %}
<tr>
<th colspan="3">{{ excuse_type.name }}</th>
<td>{{ person|get_dict:excuse_type.count_label }}</td>
</tr>
{% endfor %}
<tr>
<th colspan="3">{% trans 'Tardiness' %}</th>
<td>{{ person.tardiness }}'/{{ person.tardiness_count }}&times;</td>
</tr>
</table>
</div>
<div class="col s6">
{% if extra_marks %}
<h5>{% trans 'Extra marks' %}</h5>
<table>
{% for extra_mark in extra_marks %}
<tr>
<th>{{ extra_mark.name }}</th>
<td>{{ person|get_dict:extra_mark.count_label }}</td>
</tr>
{% endfor %}
</table>
{% endif %}
</div>
</div>
<h5>{% trans 'Relevant personal notes' %}</h5>
<table class="small-print">
<thead>
<tr>
<th>{% trans 'Date' %}</th>
<th>{% trans 'Pe.' %}</th>
<th>{% trans 'Subj.' %}</th>
<th>{% trans 'Te.' %}</th>
<th>{% trans 'Absent' %}</th>
<th>{% trans 'Tard.' %}</th>
<th colspan="2">{% trans 'Remarks' %}</th>
</tr>
</thead>
<tbody>
{% for note in person.filtered_notes %}
{% if note.absent or note.tardiness or note.remarks or note.extra_marks.all %}
<tr>
{% if note.date %}
<td>{{ note.date }}</td>
<td>{{ note.register_object.period.period }}</td>
{% else %}
<td colspan="2">
{{ note.register_object.date_start }} {{ note.register_object.period_from.period }}.–{{ note.register_object.date_end }}
{{ note.register_object.period_to.period }}.
</td>
{% endif %}
<td>
{% if note.register_object.label_ != "event" %}
{{ note.register_object.get_subject.short_name }}
{% else %}
{% trans "Event" %}
{% endif %}
</td>
<td>{{ note.register_object.teacher_short_names }}</td>
<td>
{% if note.absent %}
{% trans 'Yes' %}
{% if note.excused %}
{% if note.excuse_type %}
({{ note.excuse_type.short_name }})
{% else %}
({% trans 'e' %})
{% endif %}
{% endif %}
{% endif %}
</td>
<td>
{% if note.tardiness %}
{{ note.tardiness }}'
{% endif %}
</td>
<td>
{% for extra_mark in note.extra_marks.all %}
{{ extra_mark.short_name }}{% if not forloop.last %},{% endif %}
{% endfor %}
</td>
<td>{{ note.remarks }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<div class="page-break">&nbsp;</div>
{% endfor %}
{% for week in weeks %}
{% with register_objects_by_week|get_dict:week.week as register_objects %}
<h4>{% trans 'Week' %} {{ week.week }}: {{ week.0 }}–{{ week.6 }}</h4>
<table class="small-print">
<thead>
<tr>
<th>{% trans 'Subject' %}</th>
<th>{% trans 'Lesson topic' %}</th>
<th>{% trans 'Homework' %}</th>
<th>{% trans 'Notes' %}</th>
</tr>
</thead>
<tbody>
{% with register_objects|get_dict:"documentations" as documentations %}
{% for subject in documentations.keys %}
{% with documentations|get_dict:subject as documentation %}
<td class="lesson-subj">
{% include "chronos/partials/subject.html" with subject=documentation.lesson_period.lesson.subject %}
</td>
<td class="lesson-topic">
{{ documentation.topic }}
</td>
<td class="lesson-homework">{{ documentation.homework }}</td>
<td class="lesson-notes">
{{ documentation.group_note }}
</td>
</tr>
{% endwith %}
{% endfor %}
{% endwith %}
</tbody>
</table>
{% if register_objects|get_dict:"substitutions" %}
<h5>{% trans "Substitutions" %}</h5>
<table class="small-print">
<thead>
<tr>
<th>{% trans 'Period' %}</th>
<th>{% trans 'Subject' %}</th>
<th>{% trans 'Teachers' %}</th>
<th>{% trans 'Comment' %}</th>
</tr>
</thead>
<tbody>
{% for substitution in register_objects|get_dict:"substitutions" %}
<tr class="
{% if substitution.cancelled %}
lesson-cancelled
{% else %}
lesson-substituted
{% endif %}
">
<td class="lesson-pe">
{{ substitution.date }}.
</td>
<td class="lesson-subj">
{% include "chronos/partials/subs/subject.html" with type="substitution" el=substitution %}
</td>
<td class="lesson-te">
{% for teacher in substitution.teachers.all %}
{{ teacher.short_name }}&nbsp;
{% endfor %}
</td>
<td class="lesson-comment">
{{ substitution.comment }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
{% endwith %}
{% if week.week|divisibleby:4 %}
<div class="page-break">&nbsp;</div>
{% endif %}
{% endfor %}
{% endblock %}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment