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