From 07e44d920ef104ccb3eca65935a8178141ac2a52 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?=
 <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de>
Date: Mon, 9 Jun 2025 18:36:03 +0200
Subject: [PATCH] Introduce type filtering for slide generation

Introduce field to filter by type
Automatically restrict that field to valid types and hide if necessary
Use type information for LaTeX generation
Add type filtering to AK by category helper function in model
---
 AKModel/forms.py        |  6 ++++++
 AKModel/models.py       | 17 ++++++++++++-----
 AKModel/views/manage.py | 21 +++++++++++++++++++--
 3 files changed, 37 insertions(+), 7 deletions(-)

diff --git a/AKModel/forms.py b/AKModel/forms.py
index 93448b28..0701fda0 100644
--- a/AKModel/forms.py
+++ b/AKModel/forms.py
@@ -177,6 +177,12 @@ class SlideExportForm(AdminIntermediateForm):
         initial=3,
         label=_("# next AKs"),
         help_text=_("How many next AKs should be shown on a slide?"))
+    types = forms.MultipleChoiceField(
+        label=_("AK Types"),
+        help_text=_("Which AK types should be included in the slides?"),
+        widget=forms.CheckboxSelectMultiple,
+        choices=AKType.objects.all().values_list('id', 'name'),
+        required=False)
     presentation_mode = forms.TypedChoiceField(
         initial=False,
         label=_("Presentation only?"),
diff --git a/AKModel/models.py b/AKModel/models.py
index ce33bc0b..1773af01 100644
--- a/AKModel/models.py
+++ b/AKModel/models.py
@@ -205,7 +205,7 @@ class Event(models.Model):
         return event
 
     def get_categories_with_aks(self, wishes_seperately=False,
-                                filter_func=lambda ak: True, hide_empty_categories=False):
+                                filter_func=lambda ak: True, hide_empty_categories=False, types=None):
         """
         Get AKCategories as well as a list of AKs belonging to the category for this event
 
@@ -213,6 +213,10 @@ class Event(models.Model):
         :type wishes_seperately: bool
         :param filter_func: Optional filter predicate, only include AK in list if filter returns True
         :type filter_func: (AK)->bool
+        :param hide_empty_categories: If True, categories with no AKs will not be included in the result
+        :type hide_empty_categories: bool
+        :param types: Optional list of AK types to filter by, if None, all types are included
+        :type types: list[AKType] | None
         :return: list of category-AK-list-tuples, optionally the additional list of AK wishes
         :rtype: list[(AKCategory, list[AK])] [, list[AK]]
         """
@@ -224,7 +228,7 @@ class Event(models.Model):
         # A different behavior is needed depending on whether wishes should show up inside their categories
         # or as a separate category
 
-        def _get_category_aks(category):
+        def _get_category_aks(category, types):
             """
             Get all AKs belonging to a category
             Use joining and prefetching to reduce the number of necessary SQL queries
@@ -233,12 +237,15 @@ class Event(models.Model):
             :return: QuerySet over AKs
             :return: QuerySet[AK]
             """
-            return category.ak_set.select_related('event').prefetch_related('owners', 'akslot_set').all()
+            s = category.ak_set
+            if types is not None:
+                s = s.filter(types__in=types).distinct()
+            return s.select_related('event').prefetch_related('owners', 'akslot_set').all()
 
         if wishes_seperately:
             for category in categories:
                 ak_list = []
-                for ak in _get_category_aks(category):
+                for ak in _get_category_aks(category, types):
                     if filter_func(ak):
                         if ak.wish:
                             ak_wishes.append(ak)
@@ -250,7 +257,7 @@ class Event(models.Model):
 
         for category in categories:
             ak_list = []
-            for ak in _get_category_aks(category):
+            for ak in _get_category_aks(category, types):
                 if filter_func(ak):
                     ak_list.append(ak)
             if not hide_empty_categories or len(ak_list) > 0:
diff --git a/AKModel/views/manage.py b/AKModel/views/manage.py
index 1a7f1930..8e1e7fa4 100644
--- a/AKModel/views/manage.py
+++ b/AKModel/views/manage.py
@@ -16,7 +16,7 @@ from django_tex.response import PDFResponse
 
 from AKModel.forms import SlideExportForm, DefaultSlotEditorForm, JSONScheduleImportForm
 from AKModel.metaviews.admin import EventSlugMixin, IntermediateAdminView, IntermediateAdminActionView, AdminViewMixin
-from AKModel.models import ConstraintViolation, Event, DefaultSlot, AKOwner
+from AKModel.models import ConstraintViolation, Event, DefaultSlot, AKOwner, AKType
 
 
 class UserView(TemplateView):
@@ -37,6 +37,18 @@ class ExportSlidesView(EventSlugMixin, IntermediateAdminView):
     title = _('Export AK Slides')
     form_class = SlideExportForm
 
+    def get_form(self, form_class=None):
+        # Filter type choices to those of the current event
+        # or completely hide the field if no types are specified for this event
+        form = super().get_form(form_class)
+        if self.event.aktype_set.count() > 0:
+            form.fields['types'].choices = [
+                (ak_type.id, ak_type.name) for ak_type in self.event.aktype_set.all()
+            ]
+        else:
+            form.fields['types'].widget = form.fields['types'].hidden_widget()
+        return form
+
     def form_valid(self, form):
         # pylint: disable=invalid-name
         template_name = 'admin/AKModel/export/slides.tex'
@@ -62,10 +74,15 @@ class ExportSlidesView(EventSlugMixin, IntermediateAdminView):
             next_aks_list = zip_longest(*[ak_list[i + 1:] for i in range(NEXT_AK_LIST_LENGTH)], fillvalue=None)
             return list(zip_longest(ak_list, next_aks_list, fillvalue=[]))
 
+        # Create a list of types to filter AKs by (if at least one type was selected)
+        types = None
+        if len(form.cleaned_data['types']) > 0:
+            types = AKType.objects.filter(id__in=form.cleaned_data['types'])
+
         # Get all relevant AKs (wishes separately, and either all AKs or only those who should directly or indirectly
         # be presented when restriction setting was chosen)
         categories_with_aks, ak_wishes = self.event.get_categories_with_aks(wishes_seperately=True, filter_func=lambda
-            ak: not RESULT_PRESENTATION_MODE or (ak.present or (ak.present is None and ak.category.present_by_default)))
+            ak: not RESULT_PRESENTATION_MODE or (ak.present or (ak.present is None and ak.category.present_by_default)), types=types)
 
         # Create context for LaTeX rendering
         context = {
-- 
GitLab