From 31630982cbfedfb7c24cb0b3c981a3981f06d6ca Mon Sep 17 00:00:00 2001 From: Jonathan Weth <git@jonathanweth.de> Date: Mon, 1 May 2023 15:21:03 +0200 Subject: [PATCH] Fix several things in models --- aleksis/apps/lesrooster/admin.py | 2 +- aleksis/apps/lesrooster/managers.py | 7 + .../lesrooster/migrations/0001_initial.py | 358 ++++++++++++++++++ ...lidityrange_managers_slot_name_and_more.py | 42 ++ aleksis/apps/lesrooster/model_extensions.py | 9 + aleksis/apps/lesrooster/models.py | 43 ++- 6 files changed, 442 insertions(+), 19 deletions(-) create mode 100644 aleksis/apps/lesrooster/managers.py create mode 100644 aleksis/apps/lesrooster/migrations/0001_initial.py create mode 100644 aleksis/apps/lesrooster/migrations/0002_alter_validityrange_managers_slot_name_and_more.py create mode 100644 aleksis/apps/lesrooster/model_extensions.py diff --git a/aleksis/apps/lesrooster/admin.py b/aleksis/apps/lesrooster/admin.py index 72775563..7d87fd0a 100644 --- a/aleksis/apps/lesrooster/admin.py +++ b/aleksis/apps/lesrooster/admin.py @@ -17,7 +17,7 @@ admin.site.register(ValidityRange, ValidityRangeAdmin) class SlotAdmin(admin.ModelAdmin): - list_display = ("school_term", "weekday", "period", "time_start", "time_end") + list_display = ("validity_range", "name", "weekday", "period", "time_start", "time_end") list_display_links = ("weekday", "period") diff --git a/aleksis/apps/lesrooster/managers.py b/aleksis/apps/lesrooster/managers.py new file mode 100644 index 00000000..c8207723 --- /dev/null +++ b/aleksis/apps/lesrooster/managers.py @@ -0,0 +1,7 @@ +from django.db.models import QuerySet + +from aleksis.core.managers import DateRangeQuerySetMixin + + +class ValidityRangeQuerySet(QuerySet, DateRangeQuerySetMixin): + """Custom query set for validity ranges.""" diff --git a/aleksis/apps/lesrooster/migrations/0001_initial.py b/aleksis/apps/lesrooster/migrations/0001_initial.py new file mode 100644 index 00000000..2088ef43 --- /dev/null +++ b/aleksis/apps/lesrooster/migrations/0001_initial.py @@ -0,0 +1,358 @@ +# Generated by Django 4.1.6 on 2023-02-10 19:54 + +import aleksis.core.managers +import django.contrib.sites.managers +from django.db import migrations, models +import django.db.models.deletion +import recurrence.fields + + +class Migration(migrations.Migration): + initial = True + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("cursus", "0001_initial"), + ("sites", "0002_alter_domain_unique"), + ("core", "0048_delete_personalicalurl"), + ] + + operations = [ + migrations.CreateModel( + name="Slot", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("extended_data", models.JSONField(default=dict, editable=False)), + ( + "weekday", + models.PositiveSmallIntegerField( + choices=[ + (0, "Monday"), + (1, "Tuesday"), + (2, "Wednesday"), + (3, "Thursday"), + (4, "Friday"), + (5, "Saturday"), + (6, "Sunday"), + ], + verbose_name="Week day", + ), + ), + ( + "period", + models.PositiveSmallIntegerField( + blank=True, null=True, verbose_name="Number of period" + ), + ), + ("time_start", models.TimeField(verbose_name="Start time")), + ("time_end", models.TimeField(verbose_name="End time")), + ( + "polymorphic_ctype", + models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), + ), + ( + "site", + models.ForeignKey( + default=1, + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="sites.site", + ), + ), + ], + options={ + "verbose_name": "Slot", + "verbose_name_plural": "Slots", + "ordering": ["weekday", "period"], + }, + managers=[ + ("objects", aleksis.core.managers.PolymorphicCurrentSiteManager()), + ], + ), + migrations.CreateModel( + name="Break", + fields=[ + ( + "slot_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="lesrooster.slot", + ), + ), + ], + options={ + "verbose_name": "Break", + "verbose_name_plural": "Breaks", + }, + bases=("lesrooster.slot",), + managers=[ + ("objects", aleksis.core.managers.PolymorphicCurrentSiteManager()), + ], + ), + migrations.CreateModel( + name="ValidityRange", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("extended_data", models.JSONField(default=dict, editable=False)), + ( + "name", + models.CharField(blank=True, max_length=255, verbose_name="Name"), + ), + ("date_start", models.DateField(verbose_name="Start date")), + ("date_end", models.DateField(verbose_name="End date")), + ( + "school_term", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lr_validity_ranges", + to="core.schoolterm", + verbose_name="School term", + ), + ), + ( + "site", + models.ForeignKey( + default=1, + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="sites.site", + ), + ), + ], + options={ + "verbose_name": "Validity range", + "verbose_name_plural": "Validity ranges", + }, + managers=[ + ("objects", django.contrib.sites.managers.CurrentSiteManager()), + ], + ), + migrations.CreateModel( + name="Supervision", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("extended_data", models.JSONField(default=dict, editable=False)), + ( + "rooms", + models.ManyToManyField( + related_name="lr_supervisions", + to="core.room", + verbose_name="Rooms", + ), + ), + ( + "site", + models.ForeignKey( + default=1, + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="sites.site", + ), + ), + ( + "teachers", + models.ManyToManyField( + related_name="lr_supervisions", + to="core.person", + verbose_name="Teachers", + ), + ), + ], + options={ + "verbose_name": "Supervision", + "verbose_name_plural": "Supervisions", + }, + managers=[ + ("objects", django.contrib.sites.managers.CurrentSiteManager()), + ], + ), + migrations.AddField( + model_name="slot", + name="validity_range", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lr_slots", + to="lesrooster.validityrange", + verbose_name="Linked validity range", + ), + ), + migrations.CreateModel( + name="Lesson", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("extended_data", models.JSONField(default=dict, editable=False)), + ( + "recurrence", + recurrence.fields.RecurrenceField( + blank=True, + help_text="Leave empty for a single lesson.", + null=True, + verbose_name="Recurrence", + ), + ), + ( + "course", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="cursus.course", + verbose_name="Course", + ), + ), + ( + "rooms", + models.ManyToManyField( + blank=True, + null=True, + related_name="lr_lessons", + to="core.room", + verbose_name="Rooms", + ), + ), + ( + "site", + models.ForeignKey( + default=1, + editable=False, + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="sites.site", + ), + ), + ( + "slot_end", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="lesrooster.slot", + verbose_name="End slot", + ), + ), + ( + "slot_start", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="+", + to="lesrooster.slot", + verbose_name="Start slot", + ), + ), + ( + "subject", + models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="lr_lessons", + to="cursus.subject", + verbose_name="Subject", + ), + ), + ( + "teachers", + models.ManyToManyField( + blank=True, + null=True, + related_name="lr_lessons_as_teacher", + to="core.person", + verbose_name="Teachers", + ), + ), + ], + options={ + "verbose_name": "Lesson", + "verbose_name_plural": "Lessons", + "ordering": [ + "slot_start__validity_range__date_start", + "slot_start__weekday", + "slot_start__time_start", + "subject", + ], + }, + managers=[ + ("objects", django.contrib.sites.managers.CurrentSiteManager()), + ], + ), + migrations.AddIndex( + model_name="validityrange", + index=models.Index( + fields=["date_start", "date_end"], name="lr_validity_date_start_end" + ), + ), + migrations.AddConstraint( + model_name="validityrange", + constraint=models.UniqueConstraint( + fields=("school_term", "date_start", "date_end"), + name="lr_unique_dates_per_term", + ), + ), + migrations.AddField( + model_name="supervision", + name="slot", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="lr_supervisions", + to="lesrooster.break", + verbose_name="Slot", + ), + ), + migrations.AddIndex( + model_name="slot", + index=models.Index( + fields=["time_start", "time_end"], name="lesrooster__time_st_8ec433_idx" + ), + ), + migrations.AddConstraint( + model_name="slot", + constraint=models.UniqueConstraint( + fields=("weekday", "period", "validity_range"), + name="lr_unique_period_per_range", + ), + ), + ] diff --git a/aleksis/apps/lesrooster/migrations/0002_alter_validityrange_managers_slot_name_and_more.py b/aleksis/apps/lesrooster/migrations/0002_alter_validityrange_managers_slot_name_and_more.py new file mode 100644 index 00000000..f97132f1 --- /dev/null +++ b/aleksis/apps/lesrooster/migrations/0002_alter_validityrange_managers_slot_name_and_more.py @@ -0,0 +1,42 @@ +# Generated by Django 4.1.6 on 2023-02-12 18:49 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + dependencies = [ + ("core", "0050_alter_custommenuitem_icon_alter_notification_icon"), + ("lesrooster", "0001_initial"), + ] + + operations = [ + migrations.AlterModelManagers( + name="validityrange", + managers=[], + ), + migrations.AddField( + model_name="slot", + name="name", + field=models.CharField(blank=True, max_length=255, verbose_name="Name"), + ), + migrations.AlterField( + model_name="lesson", + name="rooms", + field=models.ManyToManyField( + blank=True, + related_name="lr_lessons", + to="core.room", + verbose_name="Rooms", + ), + ), + migrations.AlterField( + model_name="lesson", + name="teachers", + field=models.ManyToManyField( + blank=True, + related_name="lr_lessons_as_teacher", + to="core.person", + verbose_name="Teachers", + ), + ), + ] diff --git a/aleksis/apps/lesrooster/model_extensions.py b/aleksis/apps/lesrooster/model_extensions.py new file mode 100644 index 00000000..7401fac7 --- /dev/null +++ b/aleksis/apps/lesrooster/model_extensions.py @@ -0,0 +1,9 @@ +from django.utils.translation import gettext as _ + +from jsonstore import BooleanField + +from aleksis.core.models import Room + +Room.field( + is_supervision_area=BooleanField(verbose_name=_("Is supervision area"), null=True, blank=True) +) diff --git a/aleksis/apps/lesrooster/models.py b/aleksis/apps/lesrooster/models.py index 2fcfc4e4..b1130e25 100644 --- a/aleksis/apps/lesrooster/models.py +++ b/aleksis/apps/lesrooster/models.py @@ -13,18 +13,23 @@ from calendarweek.django import i18n_day_abbr_choices_lazy, i18n_day_name_choice from recurrence.fields import RecurrenceField from aleksis.apps.cursus.models import Course, Subject +from aleksis.core.managers import CurrentSiteManagerWithoutMigrations from aleksis.core.mixins import ExtensibleModel, ExtensiblePolymorphicModel from aleksis.core.models import Person, Room, SchoolTerm +from .managers import ValidityRangeQuerySet + class ValidityRange(ExtensibleModel): """A validity range is a date range in which certain data are valid.""" + objects = CurrentSiteManagerWithoutMigrations.from_queryset(ValidityRangeQuerySet)() + school_term = models.ForeignKey( SchoolTerm, on_delete=models.CASCADE, verbose_name=_("School term"), - related_name="validity_ranges", + related_name="lr_validity_ranges", ) name = models.CharField(verbose_name=_("Name"), max_length=255, blank=True) @@ -75,11 +80,11 @@ class ValidityRange(ExtensibleModel): constraints = [ # Heads up: Uniqueness per term implies uniqueness per site models.UniqueConstraint( - fields=["school_term", "date_start", "date_end"], name="unique_dates_per_term" + fields=["school_term", "date_start", "date_end"], name="lr_unique_dates_per_term" ), ] indexes = [ - models.Index(fields=["date_start", "date_end"], name="validity_date_start_date_end") + models.Index(fields=["date_start", "date_end"], name="lr_validity_date_start_end") ] @@ -90,12 +95,14 @@ class Slot(ExtensiblePolymorphicModel): WEEKDAY_CHOICES_SHORT = i18n_day_abbr_choices_lazy() validity_range = models.ForeignKey( - "chronos.ValidityRange", + ValidityRange, on_delete=models.CASCADE, - related_name="slots", + related_name="lr_slots", verbose_name=_("Linked validity range"), ) + name = models.CharField(verbose_name=_("Name"), max_length=255, blank=True) + weekday = models.PositiveSmallIntegerField( verbose_name=_("Week day"), choices=i18n_day_name_choices_lazy() ) @@ -107,7 +114,9 @@ class Slot(ExtensiblePolymorphicModel): time_end = models.TimeField(verbose_name=_("End time")) def __str__(self) -> str: - if self.period: + if self.name: + suffix = self.name + elif self.period: suffix = f"{self.period}." else: suffix = f"{time_format(self.time_start)}–{time_format(self.time_end)}" @@ -137,7 +146,7 @@ class Slot(ExtensiblePolymorphicModel): constraints = [ # Heads up: Uniqueness per validity range implies validity per site models.UniqueConstraint( - fields=["weekday", "period", "validity"], name="unique_period_per_range" + fields=["weekday", "period", "validity_range"], name="lr_unique_period_per_range" ), ] ordering = ["weekday", "period"] @@ -161,13 +170,13 @@ class Lesson(ExtensibleModel): Slot, on_delete=models.CASCADE, verbose_name=_("Start slot"), - related_name="lessons", + related_name="+", ) slot_end = models.ForeignKey( Slot, on_delete=models.CASCADE, verbose_name=_("End slot"), - related_name="lessons", + related_name="+", ) # Recurrence rules allow to define a series of lessons @@ -184,22 +193,20 @@ class Lesson(ExtensibleModel): rooms = models.ManyToManyField( Room, verbose_name=_("Rooms"), - related_name="lessons", + related_name="lr_lessons", blank=True, - null=True, ) teachers = models.ManyToManyField( Person, verbose_name=_("Teachers"), - related_name="lessons", + related_name="lr_lessons_as_teacher", blank=True, - null=True, ) subject = models.ForeignKey( Subject, on_delete=models.CASCADE, verbose_name=_("Subject"), - related_name="lessons", + related_name="lr_lessons", blank=True, null=True, ) @@ -212,7 +219,7 @@ class Lesson(ExtensibleModel): class Meta: # Heads up: Link to slot implies uniqueness per site ordering = [ - "slot_start__validity__date_start", + "slot_start__validity_range__date_start", "slot_start__weekday", "slot_start__time_start", "subject", @@ -233,18 +240,18 @@ class Supervision(ExtensibleModel): rooms = models.ManyToManyField( Room, verbose_name=_("Rooms"), - related_name="supervisions", + related_name="lr_supervisions", ) teachers = models.ManyToManyField( Person, verbose_name=_("Teachers"), - related_name="supervisions", + related_name="lr_supervisions", ) slot = models.ForeignKey( Break, on_delete=models.CASCADE, verbose_name=_("Slot"), - related_name="supervisions", + related_name="lr_supervisions", ) class Meta: -- GitLab