From 17ae90884b1e8fa912d8670d55439b14a9e3edc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Benjamin=20H=C3=A4ttasch?= <benjamin.haettasch@fachschaft.informatik.tu-darmstadt.de> Date: Wed, 4 Nov 2020 14:23:39 +0100 Subject: [PATCH] Prepare automatic constraint checking Add helper fields and methods to ConstraintViolation model Introduce helper method do determine whether two AKSlots overlap Add receivers to AKScheduling Implement stub for OWNER_TWO_SLOTS violation --- AKModel/models.py | 46 +++++++++++++++++++++++- AKScheduling/models.py | 81 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 125 insertions(+), 2 deletions(-) diff --git a/AKModel/models.py b/AKModel/models.py index a49e87f4..972953a9 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -325,7 +325,8 @@ class AKSlot(models.Model): duration = models.DecimalField(max_digits=4, decimal_places=2, default=2, verbose_name=_('Duration'), help_text=_('Length in hours')) - fixed = models.BooleanField(default=False, verbose_name=_('Scheduling fixed'), help_text=_('Length and time of this AK should not be changed')) + fixed = models.BooleanField(default=False, verbose_name=_('Scheduling fixed'), + help_text=_('Length and time of this AK should not be changed')) event = models.ForeignKey(to=Event, on_delete=models.CASCADE, verbose_name=_('Event'), help_text=_('Associated event')) @@ -388,6 +389,9 @@ class AKSlot(models.Model): """ return (timezone.now() - self.updated).total_seconds() + def overlaps(self, other: "AKSlot"): + return self.start <= other.end <= self.end or self.start <= other.start <= self.end + class AKOrgaMessage(models.Model): ak = models.ForeignKey(to=AK, on_delete=models.CASCADE, verbose_name=_('AK'), @@ -496,5 +500,45 @@ class ConstraintViolation(models.Model): def timestamp_display(self): return self.timestamp.astimezone(self.event.timezone).strftime('%d.%m.%y %H:%M') + # TODO Automatically save this + aks_tmp = set() + + @property + def _aks(self): + """ + Get all AKs belonging to this constraint violation + + The distinction between real and tmp relationships is needed since many to many + relations only work for objects already persisted in the database + + :return: set of all AKs belonging to this constraint violation + :rtype: set(AK) + """ + if self.pk and self.pk > 0: + return set(self.aks.all()) + return self.aks_tmp + + # TODO Automatically save this + ak_slots_tmp = set() + + @property + def _ak_slots(self): + """ + Get all AK Slots belonging to this constraint violation + + The distinction between real and tmp relationships is needed since many to many + relations only work for objects already persisted in the database + + :return: set of all AK Slots belonging to this constraint violation + :rtype: set(AKSlot) + """ + if self.pk and self.pk > 0: + return set(self.ak_slots.all()) + return self.ak_slots_tmp + def __str__(self): return f"{self.get_level_display()}: {self.get_type_display()} [{self.get_details()}]" + + def __eq__(self, other): + # TODO Check if FIELDS and FIELDS_MM are equal + return super().__eq__(other) diff --git a/AKScheduling/models.py b/AKScheduling/models.py index 6b202199..990bcd4a 100644 --- a/AKScheduling/models.py +++ b/AKScheduling/models.py @@ -1 +1,80 @@ -# Create your models here. +from django.db.models.signals import post_save +from django.dispatch import receiver + +from AKModel.availability.models import Availability +from AKModel.models import AK, AKSlot, Room, Event, AKOwner, ConstraintViolation + + +@receiver(post_save, sender=AK) +def ak_changed_handler(sender, instance: AK, **kwargs): + # Changes might affect: Owner(s), Requirements, Conflicts, Prerequisites, Category, Interest + print(f"{instance} changed") + + event = instance.event + + # Owner might have changed: Might affect multiple AKs by the same owner at the same time + conflicts = [] + type = ConstraintViolation.ViolationType.OWNER_TWO_SLOTS + # For all owners... + for owner in instance.owners.all(): + # ...find overlapping AKs... + slots_by_owner : [AKSlot] = [] + slots_by_owner_this_ak : [AKSlot] = [] + aks_by_owner = owner.ak_set.all() + for ak in aks_by_owner: + if ak != instance: + slots_by_owner.extend(ak.akslot_set.filter(start__isnull=False)) + else: + # ToDo Fill this outside of loop? + slots_by_owner_this_ak.extend(ak.akslot_set.filter(start__isnull=False)) + for slot in slots_by_owner_this_ak: + for other_slot in slots_by_owner: + if slot.overlaps(other_slot): + # TODO Create ConstraintViolation here + c = ConstraintViolation( + type=type, + level=ConstraintViolation.ViolationLevel.VIOLATION, + event=event, + ak_owner=owner + ) + c.aks_tmp.add(instance) + c.aks_tmp.add(other_slot.ak) + c.ak_slots_tmp.add(slot) + c.ak_slots_tmp.add(other_slot) + conflicts.append(c) + print(f"{owner} has the following conflicts: {conflicts}") + # ... and compare to/update list of existing violations of this type: + current_violations = instance.constraintviolation_set.filter(type=type) + for conflict in conflicts: + pass + # TODO Remove from list of current_violations if an equal new one is found + # TODO Otherwise, store this conflict in db + # TODO Remove all violations still in current_violations + + +@receiver(post_save, sender=AKSlot) +def akslot_changed_handler(sender, instance, **kwargs): + # Changes might affect: Duplicate parallel, Two in room + print(f"{sender} changed") + # TODO Replace with real handling + + +@receiver(post_save, sender=Room) +def room_changed_handler(sender, **kwargs): + # Changes might affect: Room size, Requirement + print(f"{sender} changed") + # TODO Replace with real handling + + +@receiver(post_save, sender=Availability) +def availability_changed_handler(sender, **kwargs): + # Changes might affect: category availability, AK availability, Room availability + print(f"{sender} changed") + # TODO Replace with real handling + + +@receiver(post_save, sender=Event) +def room_changed_handler(sender, **kwargs): + # Changes might affect: Reso-Deadline + print(f"{sender} changed") + # TODO Replace with real handling -- GitLab