From 26bf236ec134cca0f13d663ff043ee94817d705f Mon Sep 17 00:00:00 2001
From: magicfelix <felix@felix-zauberer.de>
Date: Wed, 2 Apr 2025 14:31:56 +0200
Subject: [PATCH] Migrate Announcement to CalendarEvent

---
 .../migrations/0081_migrate_announcements.py  |  42 ++++
 .../0082_announcement_remove_fields.py        |  42 ++++
 aleksis/core/models.py                        | 198 ++++++++++--------
 3 files changed, 191 insertions(+), 91 deletions(-)
 create mode 100644 aleksis/core/migrations/0081_migrate_announcements.py
 create mode 100644 aleksis/core/migrations/0082_announcement_remove_fields.py

diff --git a/aleksis/core/migrations/0081_migrate_announcements.py b/aleksis/core/migrations/0081_migrate_announcements.py
new file mode 100644
index 000000000..75e7f97bc
--- /dev/null
+++ b/aleksis/core/migrations/0081_migrate_announcements.py
@@ -0,0 +1,42 @@
+# Generated by Django 5.1.7 on 2025-04-03 11:42
+
+import aleksis.core.mixins
+import django.db.models.deletion
+from django.conf import settings
+from django.contrib.contenttypes.models import ContentType
+from django.db import migrations, models
+
+
+def migrate_announcements(apps, schema_editor):
+    CalendarEvent = apps.get_model("core", "CalendarEvent")
+    Announcement = apps.get_model("core", "Announcement")
+    announcement_ctype = ContentType.objects.get_for_model(Announcement)
+
+    db_alias = schema_editor.connection.alias
+
+    for announcement in Announcement.objects.all():
+        event = CalendarEvent.objects.create(
+            datetime_start=announcement.valid_from,
+            datetime_end=announcement.valid_until,
+            polymorphic_ctype_id = announcement_ctype.pk,
+        )
+
+        announcement.calendarevent_ptr_id = event.pk
+        announcement.save()
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0080_remove_guardians'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='announcement',
+            name='calendarevent_ptr',
+            field=models.OneToOneField(auto_created=True, null=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, serialize=False, to='core.calendarevent'),
+        ),
+        migrations.RunPython(migrate_announcements),
+    ]
diff --git a/aleksis/core/migrations/0082_announcement_remove_fields.py b/aleksis/core/migrations/0082_announcement_remove_fields.py
new file mode 100644
index 000000000..55cab8f7b
--- /dev/null
+++ b/aleksis/core/migrations/0082_announcement_remove_fields.py
@@ -0,0 +1,42 @@
+# Generated by Django 5.1.7 on 2025-04-03 11:42
+
+import aleksis.core.mixins
+import django.db.models.deletion
+from django.conf import settings
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('core', '0081_migrate_announcements'),
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='announcement',
+            name='id',
+        ),
+        migrations.AlterField(
+            model_name='announcement',
+            name='calendarevent_ptr',
+            field=models.OneToOneField(auto_created=True, null=False, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='core.calendarevent'),
+        ),
+        migrations.RemoveField(
+            model_name='announcement',
+            name='extended_data',
+        ),
+        migrations.RemoveField(
+            model_name='announcement',
+            name='managed_by_app_label',
+        ),
+        migrations.RemoveField(
+            model_name='announcement',
+            name='valid_from',
+        ),
+        migrations.RemoveField(
+            model_name='announcement',
+            name='valid_until',
+        ),
+    ]
diff --git a/aleksis/core/models.py b/aleksis/core/models.py
index bb6e29a59..f32545c04 100644
--- a/aleksis/core/models.py
+++ b/aleksis/core/models.py
@@ -1014,97 +1014,6 @@ class AnnouncementQuerySet(models.QuerySet):
         return announcements_for_person
 
 
-class Announcement(ExtensibleModel):
-    """Announcement model.
-
-    Persistent announcement to display to groups or persons in various places during a
-    specific time range.
-    """
-
-    objects = models.Manager.from_queryset(AnnouncementQuerySet)()
-
-    title = models.CharField(max_length=150, verbose_name=_("Title"))
-    description = models.TextField(max_length=500, verbose_name=_("Description"), blank=True)
-    link = models.URLField(blank=True, verbose_name=_("Link to detailed view"))
-    priority = models.PositiveSmallIntegerField(verbose_name=_("Priority"), blank=True, null=True)
-
-    valid_from = models.DateTimeField(
-        verbose_name=_("Date and time from when to show"), default=timezone.now
-    )
-    valid_until = models.DateTimeField(
-        verbose_name=_("Date and time until when to show"),
-        default=now_tomorrow,
-    )
-
-    @property
-    def recipient_persons(self) -> Sequence[Person]:
-        """Return a list of Persons this announcement is relevant for."""
-        persons = []
-        for recipient in self.recipients.all():
-            persons += recipient.persons
-        return persons
-
-    def get_recipients_for_model(self, obj: Union[models.Model]) -> Sequence[models.Model]:
-        """Get all recipients.
-
-        Get all recipients for this announcement
-        with a special content type (provided through model)
-        """
-        ct = ContentType.objects.get_for_model(obj)
-        return [r.recipient for r in self.recipients.filter(content_type=ct)]
-
-    def __str__(self):
-        return self.title
-
-    class Meta:
-        verbose_name = _("Announcement")
-        verbose_name_plural = _("Announcements")
-
-
-class AnnouncementRecipient(ExtensibleModel):
-    """Announcement recipient model.
-
-    Generalisation of a recipient for an announcement, used to wrap arbitrary
-    objects that can receive announcements.
-
-    Contract: Objects to serve as recipient have a property announcement_recipients
-    returning a flat list of Person objects.
-    """
-
-    announcement = models.ForeignKey(
-        Announcement, on_delete=models.CASCADE, related_name="recipients"
-    )
-
-    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
-    recipient_id = models.PositiveIntegerField()
-    recipient = GenericForeignKey("content_type", "recipient_id")
-
-    @property
-    def persons(self) -> Sequence[Person]:
-        """Return a list of Persons selected by this recipient object.
-
-        If the recipient is a Person, return that object. If not, it returns the list
-        from the announcement_recipients field on the target model.
-        """
-        if isinstance(self.recipient, Person):
-            return [self.recipient]
-        else:
-            return getattr(self.recipient, "announcement_recipients", [])
-
-    def __str__(self):
-        if hasattr(self.recipient, "short_name") and self.recipient.short_name:
-            return self.recipient.short_name
-        elif hasattr(self.recipient, "name") and self.recipient.name:
-            return self.recipient.name
-        elif hasattr(self.recipient, "full_name") and self.recipient.full_name:
-            return self.recipient.full_name
-        return str(self.recipient)
-
-    class Meta:
-        verbose_name = _("Announcement recipient")
-        verbose_name_plural = _("Announcement recipients")
-
-
 class DashboardWidget(RegistryObject, PolymorphicModel, PureDjangoModel):
     """Base class for dashboard widgets on the index page."""
 
@@ -1988,6 +1897,113 @@ class CalendarEvent(
         ordering = ["datetime_start", "date_start", "datetime_end", "date_end"]
 
 
+class Announcement(CalendarEvent):
+    """Announcement model.
+
+    Persistent announcement to display to groups or persons in various places during a
+    specific time range.
+    """
+
+    _class_name = "announcement"
+    dav_verbose_name = "Announcements"
+
+    objects = models.Manager.from_queryset(AnnouncementQuerySet)()
+
+    title = models.CharField(max_length=150, verbose_name=_("Title"))
+    description = models.TextField(max_length=500, verbose_name=_("Description"), blank=True)
+    link = models.URLField(blank=True, verbose_name=_("Link to detailed view"))
+    priority = models.PositiveSmallIntegerField(verbose_name=_("Priority"), blank=True, null=True)
+
+    @classmethod
+    def value_title(
+        cls, reference_object: "Announcement", request: HttpRequest | None = None
+    ) -> str:
+        """Return the title of the announcement."""
+        return reference_object.title
+
+    @classmethod
+    def value_description(
+        cls, reference_object: "Announcement", request: HttpRequest | None = None
+    ) -> str:
+        """Return the description of the announcement."""
+        return reference_object.description
+
+    @classmethod
+    def value_link(
+        cls, reference_object: "Announcement", request: HttpRequest | None = None
+    ) -> str:
+        """Return the link of the announcement."""
+        return reference_object.link
+
+    @property
+    def recipient_persons(self) -> Sequence[Person]:
+        """Return a list of Persons this announcement is relevant for."""
+        persons = []
+        for recipient in self.recipients.all():
+            persons += recipient.persons
+        return persons
+
+    def get_recipients_for_model(self, obj: Union[models.Model]) -> Sequence[models.Model]:
+        """Get all recipients.
+
+        Get all recipients for this announcement
+        with a special content type (provided through model)
+        """
+        ct = ContentType.objects.get_for_model(obj)
+        return [r.recipient for r in self.recipients.filter(content_type=ct)]
+
+    def __str__(self):
+        return self.title
+
+    class Meta:
+        verbose_name = _("Announcement")
+        verbose_name_plural = _("Announcements")
+
+
+class AnnouncementRecipient(ExtensibleModel):
+    """Announcement recipient model.
+
+    Generalisation of a recipient for an announcement, used to wrap arbitrary
+    objects that can receive announcements.
+
+    Contract: Objects to serve as recipient have a property announcement_recipients
+    returning a flat list of Person objects.
+    """
+
+    announcement = models.ForeignKey(
+        Announcement, on_delete=models.CASCADE, related_name="recipients"
+    )
+
+    content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE)
+    recipient_id = models.PositiveIntegerField()
+    recipient = GenericForeignKey("content_type", "recipient_id")
+
+    @property
+    def persons(self) -> Sequence[Person]:
+        """Return a list of Persons selected by this recipient object.
+
+        If the recipient is a Person, return that object. If not, it returns the list
+        from the announcement_recipients field on the target model.
+        """
+        if isinstance(self.recipient, Person):
+            return [self.recipient]
+        else:
+            return getattr(self.recipient, "announcement_recipients", [])
+
+    def __str__(self):
+        if hasattr(self.recipient, "short_name") and self.recipient.short_name:
+            return self.recipient.short_name
+        elif hasattr(self.recipient, "name") and self.recipient.name:
+            return self.recipient.name
+        elif hasattr(self.recipient, "full_name") and self.recipient.full_name:
+            return self.recipient.full_name
+        return str(self.recipient)
+
+    class Meta:
+        verbose_name = _("Announcement recipient")
+        verbose_name_plural = _("Announcement recipients")
+
+
 class FreeBusy(CalendarEvent):
     _class_name = ""
 
-- 
GitLab