Skip to content
Snippets Groups Projects
Commit d28a6d89 authored by Nadja Geisler's avatar Nadja Geisler :sunny:
Browse files

Merge branch 'master' into improve-scheduling-2

parents 8cabb3a4 5b51d754
No related branches found
No related tags found
No related merge requests found
......@@ -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)
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.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment