diff --git a/CHANGELOG.rst b/CHANGELOG.rst index b6443616ef0e981041c82f7c2c11be428f85b898..b1000af70c49f45a1adf510bc3e0be703292bea6 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -16,6 +16,11 @@ If you're upgrading from 3.x, there is now a migration path to use. Therefore, please install ``AlekSIS-App-Lesrooster`` which now includes parts of the legacy Chronos and the migration path. +Added +~~~~~ + +* Printout with person overview including all statistics. + `4.0.0.dev9`_ - 2024-12-07 -------------------------- diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue index 8a9058d6d1a8391d6b72db9bb239e66afcefa9a8..52444d0e5b5b03a72bbcb631bc7d75ed28427fb1 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/CoursebookPrintDialog.vue @@ -157,7 +157,7 @@ export default { }, print() { this.$router.push({ - name: "alsijil.coursebook_print", + name: "alsijil.coursebookPrintGroups", params: { groupIds: this.selectedGroups, }, diff --git a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue index c9c8bcd3ce8d762c9fe091f343adda5307cbfe8b..84e63a49529e9536c550616d4738f856232db104 100644 --- a/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue +++ b/aleksis/apps/alsijil/frontend/components/coursebook/statistics/StatisticsForPersonPage.vue @@ -257,8 +257,16 @@ v-model="$root.activeSchoolTerm" color="secondary" /> - <!-- TODO: add functionality --> - <v-btn v-if="toolbar" icon color="primary" disabled> + <v-btn + v-if="toolbar" + icon + color="primary" + :to="{ + name: 'alsijil.coursebookPrintPerson', + params: { id: personId }, + }" + target="_blank" + > <v-icon>$print</v-icon> </v-btn> <FabButton v-else icon-text="$print" i18n-key="actions.print" disabled /> diff --git a/aleksis/apps/alsijil/frontend/index.js b/aleksis/apps/alsijil/frontend/index.js index 18e0f68eaca178cbaf3b358a8065257cc3e745b3..13967281fe1de32919000b48ecadef95304e2ef3 100644 --- a/aleksis/apps/alsijil/frontend/index.js +++ b/aleksis/apps/alsijil/frontend/index.js @@ -95,7 +95,15 @@ export default { { path: "print/groups/:groupIds+/", component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), - name: "alsijil.coursebook_print", + name: "alsijil.coursebookPrintGroups", + props: { + byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, + }, + }, + { + path: "print/person/:id(\\d+)?/", + component: () => import("aleksis.core/components/LegacyBaseTemplate.vue"), + name: "alsijil.coursebookPrintPerson", props: { byTheGreatnessOfTheAlmightyAleksolotlISwearIAmWorthyOfUsingTheLegacyBaseTemplate: true, }, diff --git a/aleksis/apps/alsijil/tasks.py b/aleksis/apps/alsijil/tasks.py index 355396dcb91fdb1436df8ec0b62d78dadde87066..47fd9074c4ad6fd81383089cf2563d2691ee3ba4 100644 --- a/aleksis/apps/alsijil/tasks.py +++ b/aleksis/apps/alsijil/tasks.py @@ -9,7 +9,7 @@ from celery.states import SUCCESS from aleksis.apps.cursus.models import Course from aleksis.apps.kolego.models.absence import AbsenceReason -from aleksis.core.models import Group, PDFFile +from aleksis.core.models import Group, PDFFile, Person, SchoolTerm from aleksis.core.util.celery_progress import ProgressRecorder, recorded_task from aleksis.core.util.pdf import generate_pdf_from_template @@ -18,7 +18,7 @@ from .util.statistics import StatisticsBuilder @recorded_task -def generate_full_register_printout( +def generate_groups_register_printout( groups: list[int], file_object: int, recorder: ProgressRecorder, @@ -138,3 +138,66 @@ def generate_full_register_printout( raise Exception(_("PDF generation failed")) recorder.set_progress(5 + len(groups), _number_of_steps) + + +@recorded_task +def generate_person_register_printout( + person: int, + school_term: int, + file_object: int, + recorder: ProgressRecorder, +): + """Generate a register printout as PDF for a person.""" + + context = {} + + _number_of_steps = 4 + + recorder.set_progress(1, _number_of_steps, _("Loading data ...")) + + person = Person.objects.get(pk=person) + school_term = SchoolTerm.objects.get(pk=school_term) + + doc_query_set = Documentation.objects.select_related("subject").prefetch_related("teachers") + + statistics = ( + ( + StatisticsBuilder(Person.objects.filter(id=person.id)) + .use_from_school_term(school_term) + .annotate_statistics() + ) + .prefetch_relevant_participations(documentation_with_details=doc_query_set) + .prefetch_relevant_personal_notes(documentation_with_details=doc_query_set) + .build() + .first() + ) + + context["person"] = statistics + + context["school_term"] = school_term + + context["absence_reasons"] = AbsenceReason.objects.filter( + tags__short_name="class_register", count_as_absent=True + ) + context["absence_reasons_not_counted"] = AbsenceReason.objects.filter( + tags__short_name="class_register", count_as_absent=False + ) + context["extra_marks"] = ExtraMark.objects.all() + + recorder.set_progress(2, _number_of_steps, _("Generating template ...")) + + file_object, result = generate_pdf_from_template( + "alsijil/print/register_for_person.html", + context, + file_object=PDFFile.objects.get(pk=file_object), + ) + + recorder.set_progress(3, _number_of_steps, _("Generating 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(4, _number_of_steps) diff --git a/aleksis/apps/alsijil/templates/alsijil/print/register_for_group.html b/aleksis/apps/alsijil/templates/alsijil/print/register_for_group.html index 2257633273a5faccede0e6bef2abbb059a2f65b5..8e395b7239765b60c0bfa8cd5d4db5a00bb2671b 100644 --- a/aleksis/apps/alsijil/templates/alsijil/print/register_for_group.html +++ b/aleksis/apps/alsijil/templates/alsijil/print/register_for_group.html @@ -42,7 +42,7 @@ {% if include_person_overviews %} {% for person in group.members_with_stats %} - {% include "alsijil/partials/person_overview.html" with person=person group=group %} + {% include "alsijil/partials/person_overview.html" with person=person %} <div class="page-break"> </div> {% endfor %} {% endif %} diff --git a/aleksis/apps/alsijil/templates/alsijil/print/register_for_person.html b/aleksis/apps/alsijil/templates/alsijil/print/register_for_person.html new file mode 100644 index 0000000000000000000000000000000000000000..f22d62060b79f8c416e461ecdadabe303738ae43 --- /dev/null +++ b/aleksis/apps/alsijil/templates/alsijil/print/register_for_person.html @@ -0,0 +1,15 @@ +{% extends "core/base_print.html" %} + +{% load static i18n %} + +{% block page_title %} + {% trans "Class Register" %} · {{ school_term.name }} +{% endblock %} + +{% block extra_head %} + <link rel="stylesheet" href="{% static 'css/alsijil/full_register.css' %}"/> +{% endblock %} + +{% block content %} + {% include "alsijil/partials/person_overview.html" with person=person %} +{% endblock %} diff --git a/aleksis/apps/alsijil/urls.py b/aleksis/apps/alsijil/urls.py index 8017db1c94d063930ed462be021846841bce3d40..e0dfc61b044cdbe28dac8d03680dc132e8b4665d 100644 --- a/aleksis/apps/alsijil/urls.py +++ b/aleksis/apps/alsijil/urls.py @@ -3,7 +3,10 @@ from django.urls import path from . import views urlpatterns = [ - path("print/groups/<path:ids>/", views.full_register_for_group, name="full_register_for_group"), + path( + "print/groups/<path:ids>/", views.groups_register_printout, name="full_register_for_group" + ), + path("print/person/<int:pk>/", views.person_register_printout, name="full_register_for_person"), path("group_roles/", views.GroupRoleListView.as_view(), name="group_roles"), path("group_roles/create/", views.GroupRoleCreateView.as_view(), name="create_group_role"), path( diff --git a/aleksis/apps/alsijil/views.py b/aleksis/apps/alsijil/views.py index 71b4cbdd6ee85c3eb280c9f15549e31736b060ab..3e68392033b4bb56e241583f704657563222f108 100644 --- a/aleksis/apps/alsijil/views.py +++ b/aleksis/apps/alsijil/views.py @@ -22,10 +22,10 @@ from aleksis.core.mixins import ( AdvancedEditView, SuccessNextMixin, ) -from aleksis.core.models import Group, PDFFile +from aleksis.core.models import Group, PDFFile, Person from aleksis.core.util import messages from aleksis.core.util.celery_progress import render_progress_page -from aleksis.core.util.core_helpers import has_person +from aleksis.core.util.core_helpers import get_active_school_term, has_person from .forms import ( AssignGroupRoleForm, @@ -36,10 +36,10 @@ from .models import GroupRole, GroupRoleAssignment from .tables import ( GroupRoleTable, ) -from .tasks import generate_full_register_printout +from .tasks import generate_groups_register_printout, generate_person_register_printout -def full_register_for_group(request: HttpRequest, ids: str) -> HttpResponse: +def groups_register_printout(request: HttpRequest, ids: str) -> HttpResponse: """Show a configurable register printout as PDF for a group.""" def parse_get_param(name): @@ -65,7 +65,7 @@ def full_register_for_group(request: HttpRequest, ids: str) -> HttpResponse: redirect_url = f"/pdfs/{file_object.pk}" - result = generate_full_register_printout.delay( + result = generate_groups_register_printout.delay( groups=ids, file_object=file_object.pk, include_cover=parse_get_param("cover"), @@ -95,6 +95,43 @@ def full_register_for_group(request: HttpRequest, ids: str) -> HttpResponse: ) +def person_register_printout(request: HttpRequest, pk: int) -> HttpResponse: + """Show a statistics printout as PDF for a person.""" + + person = get_object_or_404(Person, pk=pk) + school_term = get_active_school_term(request) + if not request.user.has_perm("alsijil.view_person_statistics_rule", person) or not school_term: + raise PermissionDenied() + + file_object = PDFFile.objects.create() + file_object.person = request.user.person + file_object.save() + + redirect_url = f"/pdfs/{file_object.pk}" + + result = generate_person_register_printout.delay( + person=person.id, + school_term=school_term.id, + file_object=file_object.pk, + ) + + back_url = request.GET.get("back", "") + + return render_progress_page( + request, + result, + title=_(f"Generate register printout for {person.full_name}"), + progress_title=_("Generate 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", + ) + + @method_decorator(pwa_cache, "dispatch") class GroupRoleListView(PermissionRequiredMixin, SingleTableView): """Table of all group roles."""