Skip to content
Snippets Groups Projects
Commit 1d7b1976 authored by Jonathan Weth's avatar Jonathan Weth :keyboard:
Browse files

Merge branch '229-run-full-register-printout-generation-in-background' into 'master'

Resolve "Run full register printout generation in background"

Closes #229

See merge request !310
parents 91a16166 cf018a16
Branches engs-4.0
No related tags found
1 merge request!310Resolve "Run full register printout generation in background"
Pipeline #98457 failed
......@@ -13,6 +13,7 @@ Changed
~~~~~~~
* Use new icon set inside of models and templates
* Run full register printout generation in background
Fixed
~~~~~
......
from copy import deepcopy
from datetime import date, timedelta
from django.db.models import Q
from django.utils.translation import gettext as _
from calendarweek import CalendarWeek
from celery.result import allow_join_result
from celery.states import SUCCESS
from aleksis.apps.chronos.models import Event, ExtraLesson, LessonPeriod
from aleksis.core.models import Group, PDFFile
from aleksis.core.util.celery_progress import ProgressRecorder, recorded_task
from aleksis.core.util.pdf import generate_pdf_from_template
from .models import ExcuseType, ExtraMark, LessonDocumentation, PersonalNote
@recorded_task
def generate_full_register_printout(group: int, file_object: int, recorder: ProgressRecorder):
"""Generate a full register printout as PDF for a group."""
context = {}
_number_of_steps = 8
recorder.set_progress(1, _number_of_steps, _("Load data ..."))
group = Group.objects.get(pk=group)
file_object = PDFFile.objects.get(pk=file_object)
groups_q = (
Q(lesson_period__lesson__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)
)
personal_notes = (
PersonalNote.objects.prefetch_related(
"lesson_period__substitutions", "lesson_period__lesson__teachers"
)
.not_empty()
.filter(groups_q)
.filter(groups_of_person=group)
)
documentations = LessonDocumentation.objects.not_empty().filter(groups_q)
recorder.set_progress(2, _number_of_steps, _("Sort data ..."))
sorted_documentations = {"extra_lesson": {}, "event": {}, "lesson_period": {}}
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:
key = note.register_object.label_
sorted_personal_notes[key].setdefault(note.register_object_key, [])
sorted_personal_notes[key][note.register_object_key].append(note)
sorted_personal_notes["person"].setdefault(note.person.pk, [])
sorted_personal_notes["person"][note.person.pk].append(note)
recorder.set_progress(3, _number_of_steps, _("Load lesson data ..."))
# Get all lesson periods for the selected group
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)
register_objects_by_day = {}
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)
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 ..."))
weeks = CalendarWeek.weeks_within(
group.school_term.date_start,
group.school_term.date_end,
)
for lesson_period in lesson_periods:
for week in weeks:
day = week[lesson_period.period.weekday]
if (
lesson_period.lesson.validity.date_start
<= day
<= lesson_period.lesson.validity.date_end
):
filtered_documentation = sorted_documentations["lesson_period"].get(
f"{lesson_period.pk}_{week.week}_{week.year}"
)
filtered_personal_notes = sorted_personal_notes["lesson_period"].get(
f"{lesson_period.pk}_{week.week}_{week.year}", []
)
substitution = lesson_period.get_substitution(week)
register_objects_by_day.setdefault(day, []).append(
(lesson_period, filtered_documentation, filtered_personal_notes, substitution)
)
recorder.set_progress(5, _number_of_steps, _("Load statistics ..."))
persons = group.members.prefetch_related(None).select_related(None)
persons = group.generate_person_list_with_class_register_statistics(persons)
prefetched_persons = []
for person in persons:
person.filtered_notes = sorted_personal_notes["person"].get(person.pk, [])
prefetched_persons.append(person)
context["school_term"] = group.school_term
context["persons"] = prefetched_persons
context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
context["extra_marks"] = ExtraMark.objects.all()
context["group"] = group
context["weeks"] = weeks
context["register_objects_by_day"] = register_objects_by_day
context["register_objects"] = list(lesson_periods) + list(events) + list(extra_lessons)
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 ..."))
file_object, result = generate_pdf_from_template(
"alsijil/print/full_register.html", context, file_object=file_object
)
recorder.set_progress(7, _number_of_steps, _("Generate PDF ..."))
with allow_join_result():
result.wait()
file_object.refresh_from_db()
if not result.status == SUCCESS and file_object.file:
raise Exception(_("PDF generation failed"))
recorder.set_progress(8, _number_of_steps)
......@@ -17,8 +17,8 @@
<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 request.site.preferences.theme__logo.url aleksis_banner %}"
alt="{{ request.site.preferences.general__title }} – Logo" class="max-size-600 center">
<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>
......
from contextlib import nullcontext
from copy import deepcopy
from datetime import date, datetime, timedelta
from datetime import datetime, timedelta
from typing import Any, Dict, Optional
from django.apps import apps
......@@ -37,10 +37,10 @@ from aleksis.core.mixins import (
AdvancedEditView,
SuccessNextMixin,
)
from aleksis.core.models import Group, Person, SchoolTerm
from aleksis.core.models import Group, PDFFile, Person, SchoolTerm
from aleksis.core.util import messages
from aleksis.core.util.celery_progress import render_progress_page
from aleksis.core.util.core_helpers import get_site_preferences, objectgetter_optional
from aleksis.core.util.pdf import render_pdf
from aleksis.core.util.predicates import check_global_permission
from .filters import PersonalNoteFilter
......@@ -58,14 +58,7 @@ from .forms import (
RegisterObjectActionForm,
SelectForm,
)
from .models import (
ExcuseType,
ExtraMark,
GroupRole,
GroupRoleAssignment,
LessonDocumentation,
PersonalNote,
)
from .models import ExcuseType, ExtraMark, GroupRole, GroupRoleAssignment, PersonalNote
from .tables import (
ExcuseTypeTable,
ExtraMarkTable,
......@@ -74,6 +67,7 @@ from .tables import (
RegisterObjectSelectTable,
RegisterObjectTable,
)
from .tasks import generate_full_register_printout
from .util.alsijil_helpers import (
annotate_documentations,
generate_list_of_all_register_objects,
......@@ -642,138 +636,36 @@ def week_view(
"alsijil.view_full_register_rule", fn=objectgetter_optional(Group, None, False)
)
def full_register_group(request: HttpRequest, id_: int) -> HttpResponse:
context = {}
group = get_object_or_404(Group, pk=id_)
groups_q = (
Q(lesson_period__lesson__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)
)
personal_notes = (
PersonalNote.objects.prefetch_related(
"lesson_period__substitutions", "lesson_period__lesson__teachers"
)
.not_empty()
.filter(groups_q)
.filter(groups_of_person=group)
)
documentations = LessonDocumentation.objects.not_empty().filter(groups_q)
sorted_documentations = {"extra_lesson": {}, "event": {}, "lesson_period": {}}
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
file_object = PDFFile.objects.create()
for note in personal_notes:
key = note.register_object.label_
sorted_personal_notes[key].setdefault(note.register_object_key, [])
sorted_personal_notes[key][note.register_object_key].append(note)
sorted_personal_notes["person"].setdefault(note.person.pk, [])
sorted_personal_notes["person"][note.person.pk].append(note)
# Get all lesson periods for the selected group
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)
register_objects_by_day = {}
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,
)
)
redirect_url = reverse("redirect_to_pdf_file", args=[file_object.pk])
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)
register_objects_by_day.setdefault(day, []).append(
(
event_copy,
sorted_documentations["event"].get(event.pk),
sorted_personal_notes["event"].get(event.pk, []),
None,
)
)
result = generate_full_register_printout.delay(group.pk, file_object.pk)
weeks = CalendarWeek.weeks_within(
group.school_term.date_start,
group.school_term.date_end,
back_url = request.GET.get("back", "")
back_url_is_safe = url_has_allowed_host_and_scheme(
url=back_url,
allowed_hosts={request.get_host()},
require_https=request.is_secure(),
)
for lesson_period in lesson_periods:
for week in weeks:
day = week[lesson_period.period.weekday]
if (
lesson_period.lesson.validity.date_start
<= day
<= lesson_period.lesson.validity.date_end
):
filtered_documentation = sorted_documentations["lesson_period"].get(
f"{lesson_period.pk}_{week.week}_{week.year}"
)
filtered_personal_notes = sorted_personal_notes["lesson_period"].get(
f"{lesson_period.pk}_{week.week}_{week.year}", []
)
substitution = lesson_period.get_substitution(week)
register_objects_by_day.setdefault(day, []).append(
(lesson_period, filtered_documentation, filtered_personal_notes, substitution)
)
persons = group.members.prefetch_related(None).select_related(None)
persons = group.generate_person_list_with_class_register_statistics(persons)
prefetched_persons = []
for person in persons:
person.filtered_notes = sorted_personal_notes["person"].get(person.pk, [])
prefetched_persons.append(person)
context["school_term"] = group.school_term
context["persons"] = prefetched_persons
context["excuse_types"] = ExcuseType.objects.filter(count_as_absent=True)
context["excuse_types_not_absent"] = ExcuseType.objects.filter(count_as_absent=False)
context["extra_marks"] = ExtraMark.objects.all()
context["group"] = group
context["weeks"] = weeks
context["register_objects_by_day"] = register_objects_by_day
context["register_objects"] = list(lesson_periods) + list(events) + list(extra_lessons)
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",
)
if not back_url_is_safe:
back_url = reverse("my_groups")
return render_progress_page(
request,
result,
title=_("Generate full register printout for {}").format(group),
progress_title=_("Generate full register printout …"),
success_message=_("The printout has been generated successfully."),
error_message=_("There was a problem while generating the printout."),
redirect_on_success_url=redirect_url,
back_url=back_url,
button_title=_("Download PDF"),
button_url=redirect_url,
button_icon="picture_as_pdf",
)
return render_pdf(request, "alsijil/print/full_register.html", context)
@permission_required("alsijil.view_my_students_rule")
......
......@@ -48,7 +48,7 @@ secondary = true
[tool.poetry.dependencies]
python = "^3.9"
aleksis-core = "^2.11"
aleksis-core = "^2.12"
aleksis-app-chronos = "^2.2"
aleksis-app-stoelindeling = { version = "^1.0", optional = true }
......
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