diff --git a/AKModel/models.py b/AKModel/models.py index 08cb831a7a09b3f4ce5193b4116aaa6b49e3472b..d247fbb7632e030bc28656e2f0437e2f5fc038a6 100644 --- a/AKModel/models.py +++ b/AKModel/models.py @@ -356,9 +356,6 @@ 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'), help_text=_('AK this message belongs to')) @@ -404,18 +401,25 @@ class ConstraintViolation(models.Model): aks = models.ManyToManyField(to=AK, blank=True, verbose_name=_('AKs'), help_text=_('AK(s) belonging to this constraint')) ak_slots = models.ManyToManyField(to=AKSlot, blank=True, verbose_name=_('AK Slots'), help_text=_('AK Slot(s) belonging to this constraint')) - ak_owner = models.ForeignKey(to=AKOwner, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('AK Owner'), help_text=_('AK Owner belonging to this constraint')) - room = models.ForeignKey(to=Room, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('Room'), help_text=_('Room belonging to this constraint')) - requirement = models.ForeignKey(to=AKRequirement, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('AK Requirement'), help_text=_('AK Requirement belonging to this constraint')) - category = models.ForeignKey(to=AKCategory, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('AK Category'), help_text=_('AK Category belonging to this constraint')) - - comment = models.TextField(verbose_name=_('Comment'), help_text=_('Comment or further details for this violation'), blank=True) + ak_owner = models.ForeignKey(to=AKOwner, on_delete=models.CASCADE, blank=True, null=True, + verbose_name=_('AK Owner'), help_text=_('AK Owner belonging to this constraint')) + room = models.ForeignKey(to=Room, on_delete=models.CASCADE, blank=True, null=True, verbose_name=_('Room'), + help_text=_('Room belonging to this constraint')) + requirement = models.ForeignKey(to=AKRequirement, on_delete=models.CASCADE, blank=True, null=True, + verbose_name=_('AK Requirement'), + help_text=_('AK Requirement belonging to this constraint')) + category = models.ForeignKey(to=AKCategory, on_delete=models.CASCADE, blank=True, null=True, + verbose_name=_('AK Category'), help_text=_('AK Category belonging to this constraint')) + + comment = models.TextField(verbose_name=_('Comment'), help_text=_('Comment or further details for this violation'), + blank=True) timestamp = models.DateTimeField(auto_now_add=True, verbose_name=_('Timestamp'), help_text=_('Time of creation')) - manually_resolved = models.BooleanField(verbose_name=_('Manually Resolved'), default=False, help_text=_('Mark this violation manually as resolved')) + manually_resolved = models.BooleanField(verbose_name=_('Manually Resolved'), default=False, + help_text=_('Mark this violation manually as resolved')) - FIELDS = ['ak_owner', 'room', 'requirement', 'category'] - FIELDS_MM = ['_aks', '_ak_slots'] + fields = ['ak_owner', 'room', 'requirement', 'category'] + fields_mm = ['aks', 'ak_slots'] def get_details(self): """ @@ -425,53 +429,15 @@ class ConstraintViolation(models.Model): """ output = [] # Stringify all ManyToMany fields - for field_mm in self.FIELDS_MM: - output.append(f"{field_mm[1:]}: {', '.join(str(a) for a in getattr(self, field_mm))}") + for field_mm in self.fields_mm: + output.append(f"{field_mm}: {', '.join(str(a) for a in getattr(self, field_mm).all())}") # Stringify all other fields - for field in self.FIELDS: + for field in self.fields: a = getattr(self, field, None) if a is not None: output.append(f"{field}: {a}") return ", ".join(output) get_details.short_description = _('Details') - # 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 990bcd4a74b6efebdc865830abd5cafb8600ae21..6b2021999398416a78191ac543b7e0e34d86bc2c 100644 --- a/AKScheduling/models.py +++ b/AKScheduling/models.py @@ -1,80 +1 @@ -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 +# Create your models here.