From 2cf8863bf0b00a9472070c994fd49a32a957629b Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Fri, 4 Apr 2025 13:23:47 +0200
Subject: [PATCH] Implement calendar-based AnnouncementManager

---
 aleksis/core/managers.py | 81 ++++++++++++++++++++++++++++++++++++++--
 aleksis/core/models.py   | 59 ++---------------------------
 2 files changed, 81 insertions(+), 59 deletions(-)

diff --git a/aleksis/core/managers.py b/aleksis/core/managers.py
index 7fc89a5a7..5ae52e070 100644
--- a/aleksis/core/managers.py
+++ b/aleksis/core/managers.py
@@ -1,9 +1,11 @@
-from datetime import date
-from typing import TYPE_CHECKING, Union
+from datetime import date, datetime
+from typing import TYPE_CHECKING, Optional, Union
 
 from django.apps import apps
-from django.db.models import BooleanField, Case, QuerySet, Value, When
+from django.contrib.contenttypes.models import ContentType
+from django.db.models import BooleanField, Case, Model, QuerySet, Value, When
 from django.db.models.manager import Manager
+from django.utils import timezone
 
 from calendarweek import CalendarWeek
 from django_cte.cte import CTEQuerySet
@@ -225,3 +227,76 @@ class HolidayQuerySet(DateRangeQuerySetMixin, CalendarEventQuerySet):
 
 class HolidayManager(CalendarEventManager):
     queryset_class = HolidayQuerySet
+
+
+class AnnouncementQuerySet(CalendarEventQuerySet):
+    """Queryset for announcements providing time-based utility functions."""
+
+    def relevant_for(self, obj: Union[Model, QuerySet]) -> QuerySet:
+        """Get all relevant announcements.
+
+        Get a QuerySet with all announcements relevant for a certain Model (e.g. a Group)
+        or a set of models in a QuerySet.
+        """
+        if isinstance(obj, QuerySet):
+            ct = ContentType.objects.get_for_model(obj.model)
+            pks = list(obj.values_list("pk", flat=True))
+        else:
+            ct = ContentType.objects.get_for_model(obj)
+            pks = [obj.pk]
+
+        return self.filter(recipients__content_type=ct, recipients__recipient_id__in=pks)
+
+    def at_time(self, when: Optional[datetime] = None) -> QuerySet:
+        """Get all announcements at a certain time."""
+        when = when or timezone.now()
+
+        # Get announcements by time
+        announcements = self.filter(datetime_start__lte=when, datetime_end__gte=when)
+
+        return announcements
+
+    def on_date(self, when: Optional[date] = None) -> QuerySet:
+        """Get all announcements at a certain date."""
+        when = when or timezone.now().date()
+
+        # Get announcements by time
+        announcements = self.filter(datetime_start__date__lte=when, datetime_end__date__gte=when)
+
+        return announcements
+
+    def within_days(self, start: date, stop: date) -> QuerySet:
+        """Get all announcements valid for a set of days."""
+        # Get announcements
+        announcements = self.filter(datetime_start__date__lte=stop, datetime_end__date__gte=start)
+
+        return announcements
+
+    def for_person(self, person: "Person") -> list:  # noqa: F821
+        """Get all announcements for one person."""
+        # Filter by person
+        announcements_for_person = []
+        for announcement in self:
+            if person in announcement.recipient_persons:
+                announcements_for_person.append(announcement)
+
+        return announcements_for_person
+
+
+class AnnouncementManager(CalendarEventManager):
+    queryset_class = AnnouncementQuerySet
+
+    def relevant_for(self, obj: Union[Model, QuerySet]) -> QuerySet:
+        return self.get_queryset().relevant_for(obj)
+
+    def at_time(self, when: Optional[datetime] = None) -> QuerySet:
+        return self.get_queryset().at_time(when)
+
+    def on_date(self, when: Optional[date] = None) -> QuerySet:
+        return self.get_queryset().on_date(when)
+
+    def within_days(self, start: date, stop: date) -> QuerySet:
+        return self.get_queryset().within_days(start, stop)
+
+    def for_person(self, person: "Person") -> list:  # noqa: F821
+        return self.get_queryset().for_person(person)
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index f32545c04..c50d5aa6e 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -65,6 +65,7 @@ from aleksis.core.data_checks import (
 
 from .managers import (
     AlekSISBaseManagerWithoutMigrations,
+    AnnouncementManager,
     CalendarEventManager,
     CalendarEventMixinManager,
     GroupManager,
@@ -87,7 +88,7 @@ from .mixins import (
     SchoolTermRelatedExtensibleModel,
 )
 from .tasks import send_notification
-from .util.core_helpers import generate_random_code, get_site_preferences, now_tomorrow
+from .util.core_helpers import generate_random_code, get_site_preferences
 from .util.email import send_email
 from .util.model_helpers import ICONS
 
@@ -960,60 +961,6 @@ class Notification(ExtensibleModel, TimeStampedModel):
         ]
 
 
-class AnnouncementQuerySet(models.QuerySet):
-    """Queryset for announcements providing time-based utility functions."""
-
-    def relevant_for(self, obj: Union[models.Model, models.QuerySet]) -> models.QuerySet:
-        """Get all relevant announcements.
-
-        Get a QuerySet with all announcements relevant for a certain Model (e.g. a Group)
-        or a set of models in a QuerySet.
-        """
-        if isinstance(obj, models.QuerySet):
-            ct = ContentType.objects.get_for_model(obj.model)
-            pks = list(obj.values_list("pk", flat=True))
-        else:
-            ct = ContentType.objects.get_for_model(obj)
-            pks = [obj.pk]
-
-        return self.filter(recipients__content_type=ct, recipients__recipient_id__in=pks)
-
-    def at_time(self, when: Optional[datetime] = None) -> models.QuerySet:
-        """Get all announcements at a certain time."""
-        when = when or timezone.now()
-
-        # Get announcements by time
-        announcements = self.filter(valid_from__lte=when, valid_until__gte=when)
-
-        return announcements
-
-    def on_date(self, when: Optional[date] = None) -> models.QuerySet:
-        """Get all announcements at a certain date."""
-        when = when or timezone.now().date()
-
-        # Get announcements by time
-        announcements = self.filter(valid_from__date__lte=when, valid_until__date__gte=when)
-
-        return announcements
-
-    def within_days(self, start: date, stop: date) -> models.QuerySet:
-        """Get all announcements valid for a set of days."""
-        # Get announcements
-        announcements = self.filter(valid_from__date__lte=stop, valid_until__date__gte=start)
-
-        return announcements
-
-    def for_person(self, person: Person) -> list:
-        """Get all announcements for one person."""
-        # Filter by person
-        announcements_for_person = []
-        for announcement in self:
-            if person in announcement.recipient_persons:
-                announcements_for_person.append(announcement)
-
-        return announcements_for_person
-
-
 class DashboardWidget(RegistryObject, PolymorphicModel, PureDjangoModel):
     """Base class for dashboard widgets on the index page."""
 
@@ -1907,7 +1854,7 @@ class Announcement(CalendarEvent):
     _class_name = "announcement"
     dav_verbose_name = "Announcements"
 
-    objects = models.Manager.from_queryset(AnnouncementQuerySet)()
+    objects = AnnouncementManager()
 
     title = models.CharField(max_length=150, verbose_name=_("Title"))
     description = models.TextField(max_length=500, verbose_name=_("Description"), blank=True)
-- 
GitLab