diff --git a/aleksis/core/admin.py b/aleksis/core/admin.py index bdcda1e35e4bbd427e35e32b414e6931cba00ae6..e3258ed33bea0fc4dbcb452c2f2f57573212dbe2 100644 --- a/aleksis/core/admin.py +++ b/aleksis/core/admin.py @@ -1,6 +1,15 @@ from django.contrib import admin -from .models import Group, Person, School, SchoolTerm, Activity, Notification, Announcement +from .models import ( + Group, + Person, + School, + SchoolTerm, + Activity, + Notification, + Announcement, + AnnouncementRecipient, +) admin.site.register(Person) admin.site.register(Group) @@ -8,4 +17,16 @@ admin.site.register(School) admin.site.register(SchoolTerm) admin.site.register(Activity) admin.site.register(Notification) -admin.site.register(Announcement) + + +class AnnouncementRecipientInline(admin.StackedInline): + model = AnnouncementRecipient + + +class AnnouncementAdmin(admin.ModelAdmin): + inlines = [ + AnnouncementRecipientInline, + ] + + +admin.site.register(Announcement, AnnouncementAdmin) diff --git a/aleksis/core/migrations/0013_multiple_recipients_announcement.py b/aleksis/core/migrations/0013_multiple_recipients_announcement.py new file mode 100644 index 0000000000000000000000000000000000000000..76574ecd1210f16c1b265eab61f582184faca310 --- /dev/null +++ b/aleksis/core/migrations/0013_multiple_recipients_announcement.py @@ -0,0 +1,51 @@ +# Generated by Django 3.0.3 on 2020-02-19 18:14 + +import aleksis.core.models +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('contenttypes', '0002_remove_content_type_name'), + ('core', '0012_announcement'), + ] + + operations = [ + migrations.AlterModelOptions( + name='announcement', + options={'verbose_name': 'Announcement', 'verbose_name_plural': 'Announcements'}, + ), + migrations.RemoveField( + model_name='announcement', + name='content_type', + ), + migrations.RemoveField( + model_name='announcement', + name='recipient_id', + ), + migrations.AlterField( + model_name='announcement', + name='description', + field=models.TextField(blank=True, max_length=500, verbose_name='Description'), + ), + migrations.AlterField( + model_name='announcement', + name='valid_until', + field=models.DateTimeField(default=aleksis.core.models.now_plus_one_day, verbose_name='Date and time until when to show'), + ), + migrations.CreateModel( + name='AnnouncementRecipient', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('recipient_id', models.PositiveIntegerField()), + ('announcement', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='recipients', to='core.Announcement')), + ('content_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='contenttypes.ContentType')), + ], + ), + migrations.AlterModelOptions( + name='announcementrecipient', + options={'verbose_name': 'Announcement recipient', 'verbose_name_plural': 'Announcement recipients'}, + ), + ] diff --git a/aleksis/core/models.py b/aleksis/core/models.py index e1dae558196ec24093496f7ccdd588bdcdcbca43..bcc445fa8ab7da58e2c44a1d42a33de85651e6a5 100644 --- a/aleksis/core/models.py +++ b/aleksis/core/models.py @@ -230,7 +230,7 @@ class Group(ExtensibleModel): @property def announcement_recipients(self): - return list(self.members) + list(self.owners) + return list(self.members.all()) + list(self.owners.all()) def __str__(self) -> str: return "%s (%s)" % (self.name, self.short_name) @@ -280,6 +280,10 @@ class Notification(models.Model): verbose_name_plural = _("Notifications") +def now_plus_one_day(): + return timezone.datetime.now() + timedelta(days=1) + + class Announcement(models.Model): title = models.CharField(max_length=150, verbose_name=_("Title")) description = models.TextField(max_length=500, verbose_name=_("Description"), blank=True) @@ -290,13 +294,9 @@ class Announcement(models.Model): ) valid_until = models.DateTimeField( verbose_name=_("Date and time until when to show"), - default=lambda: timezone.datetime.now() + timedelta(days=1), + default=now_plus_one_day, ) - content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) - recipient_id = models.PositiveIntegerField() - recipient = GenericForeignKey("content_type", "recipient_id") - @classmethod def relevant_for(cls, obj: Union[models.Model, models.QuerySet]) -> models.QuerySet: """ Get a QuerySet with all announcements relevant for a certain Model (e.g. a Group) @@ -310,7 +310,7 @@ class Announcement(models.Model): ct = ContentType.objects.get_for_model(obj) pks = [obj.pk] - return cls.objects.filter(content_type=ct, recipient_id__in=pks) + return cls.objects.filter(recipients__content_type=ct, recipients__recipient_id__in=pks) @classmethod def for_person_at_time(cls, person: Person, when: Optional[datetime] = None) -> List: @@ -329,10 +329,34 @@ class Announcement(models.Model): return announcements_for_person @property - def recipient_persons(self) -> Union[models.QuerySet, Sequence[models.Model]]: - """ Return a list of Persons this announcement is relevant for + 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 - If the recipient is a Person, return that object. If not, it returns the QUerySet + def __str__(self): + return self.title + + class Meta: + verbose_name = _("Announcement") + verbose_name_plural = _("Announcements") + + +class AnnouncementRecipient(models.Model): + 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. """ @@ -342,11 +366,11 @@ class Announcement(models.Model): return getattr(self.recipient, "announcement_recipients", []) def __str__(self): - return self.title + return str(self.recipient) class Meta: - verbose_name = _("Announcement") - verbose_name_plural = _("Announcements") + verbose_name = _("Announcement recipient") + verbose_name_plural = _("Announcement recipients") class DashboardWidget(PolymorphicModel):