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

Merge branch 'feature-remaining-constraint-validation' into 'main'

Implement additional constraint validation checks

See merge request !110
parents 645c2bf3 b963f1a1
Branches
No related tags found
No related merge requests found
......@@ -2,7 +2,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-15 14:34+0000\n"
"POT-Creation-Date: 2021-10-28 20:45+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -519,8 +519,8 @@ msgid "Capacity"
msgstr "Kapazität"
#: AKModel/models.py:331
msgid "Maximum number of people"
msgstr "Maximale Personenzahl"
msgid "Maximum number of people (-1 for unlimited)."
msgstr "Maximale Personenzahl (-1 wenn unbeschränkt)."
#: AKModel/models.py:332
msgid "Properties"
......@@ -652,10 +652,8 @@ msgid "Two AK Slots for the same AK scheduled at the same time"
msgstr "Zwei AK Slots eines AKs wurden zur selben Zeit platziert"
#: AKModel/models.py:466
msgid "AK Slot is scheduled in a room with less space than interest"
msgstr ""
"AK Slot wurde in einem Raum mit weniger Plätzen als am AK Interessierten "
"platziert"
msgid "Room does not have enough space for interest in scheduled AK Slot"
msgstr "Room hat nicht genug Platz für das Interesse am geplanten AK-Slot"
#: AKModel/models.py:467
msgid "AK Slot is scheduled outside the event's availabilities"
......@@ -1007,6 +1005,11 @@ msgstr "Resolutionsabsicht?"
msgid "Category (for Wishes)"
msgstr "Kategorie (für Wünsche)"
#~ msgid "AK Slot is scheduled in a room with less space than interest"
#~ msgstr ""
#~ "AK Slot wurde in einem Raum mit weniger Plätzen als am AK Interessierten "
#~ "platziert"
#~ msgid "Confirm"
#~ msgstr "Bestätigen"
......
# Generated by Django 3.1.8 on 2021-10-28 16:34
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('AKModel', '0046_present_by_default'),
]
operations = [
migrations.AlterField(
model_name='room',
name='capacity',
field=models.IntegerField(help_text='Maximum number of people (-1 for unlimited).', verbose_name='Capacity'),
),
]
# Generated by Django 3.1.8 on 2021-10-28 20:47
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('AKModel', '0047_room_capacity_help'),
]
operations = [
migrations.AlterField(
model_name='constraintviolation',
name='type',
field=models.CharField(choices=[('ots', 'Owner has two parallel slots'), ('soa', "AK Slot was scheduled outside the AK's availabilities"), ('rts', 'Room has two AK slots scheduled at the same time'), ('rng', 'Room does not satisfy the requirement of the scheduled AK'), ('acc', 'AK Slot is scheduled at the same time as an AK listed as a conflict'), ('abp', 'AK Slot is scheduled before an AK listed as a prerequisite'), ('aar', 'AK Slot for AK with intention to submit a resolution is scheduled after resolution deadline'), ('acm', 'AK Slot in a category is outside that categories availabilities'), ('asc', 'Two AK Slots for the same AK scheduled at the same time'), ('rce', 'Room does not have enough space for interest in scheduled AK Slot'), ('soe', "AK Slot is scheduled outside the event's availabilities")], help_text='Type of violation, i.e. what kind of constraint was violated', max_length=3, verbose_name='Type'),
),
]
......@@ -328,7 +328,7 @@ class Room(models.Model):
name = models.CharField(max_length=64, verbose_name=_('Name'), help_text=_('Name or number of the room'))
location = models.CharField(max_length=256, blank=True, verbose_name=_('Location'),
help_text=_('Name or number of the location'))
capacity = models.IntegerField(verbose_name=_('Capacity'), help_text=_('Maximum number of people'))
capacity = models.IntegerField(verbose_name=_('Capacity'), help_text=_('Maximum number of people (-1 for unlimited).'))
properties = models.ManyToManyField(to=AKRequirement, blank=True, verbose_name=_('Properties'),
help_text=_('AK requirements fulfilled by the room'))
......@@ -463,7 +463,7 @@ class ConstraintViolation(models.Model):
'AK Slot for AK with intention to submit a resolution is scheduled after resolution deadline')
AK_CATEGORY_MISMATCH = 'acm', _('AK Slot in a category is outside that categories availabilities')
AK_SLOT_COLLISION = 'asc', _('Two AK Slots for the same AK scheduled at the same time')
ROOM_CAPACITY_EXCEEDED = 'rce', _('AK Slot is scheduled in a room with less space than interest')
ROOM_CAPACITY_EXCEEDED = 'rce', _('Room does not have enough space for interest in scheduled AK Slot')
SLOT_OUTSIDE_EVENT = 'soe', _('AK Slot is scheduled outside the event\'s availabilities')
class ViolationLevel(models.IntegerChoices):
......@@ -499,7 +499,7 @@ class ConstraintViolation(models.Model):
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 = ['ak_owner', 'room', 'requirement', 'category', 'comment']
fields_mm = ['_aks', '_ak_slots']
def __init__(self, *args, **kwargs):
......
......@@ -8,7 +8,7 @@ msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2021-05-09 18:23+0000\n"
"POT-Creation-Date: 2021-10-28 20:45+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
......@@ -17,6 +17,24 @@ msgstr ""
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: AKScheduling/models.py:507
#, python-format
msgid ""
"Not enough space for AK interest (Interest: %(interest)d, Capacity: "
"%(capacity)d)"
msgstr ""
"Nicht genug Platz für AK-Interesse (Interesse: %(interest)d, Kapazität: "
"%(capacity)d)"
#: AKScheduling/models.py:519
#, python-format
msgid ""
"Space is too close to AK interest (Interest: %(interest)d, Capacity: "
"%(capacity)d)"
msgstr ""
"Verfügbarer Platz zu dicht an Interesse (Interesse: %(interest)d, Kapazität: "
"%(capacity)d)"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:11
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:11
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:10
......@@ -62,7 +80,7 @@ msgstr "Seit"
#: AKScheduling/templates/admin/AKScheduling/constraint_violations.html:134
#: AKScheduling/templates/admin/AKScheduling/manage_tracks.html:243
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:197
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:208
#: AKScheduling/templates/admin/AKScheduling/unscheduled.html:34
msgid "Event Status"
msgstr "Event-Status"
......@@ -103,23 +121,23 @@ msgstr "AK-Track hinzufügen"
msgid "AKs without track"
msgstr "AKs ohne Track"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:87
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:91
msgid "Day (Horizontal)"
msgstr "Tag (horizontal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:94
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:98
msgid "Day (Vertical)"
msgstr "Tag (vertikal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:105
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:109
msgid "Event (Horizontal)"
msgstr "Event (horizontal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:114
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:118
msgid "Event (Vertical)"
msgstr "Event (vertikal)"
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:141
#: AKScheduling/templates/admin/AKScheduling/scheduling.html:146
msgid "Room"
msgstr "Raum"
......
from django.db.models.signals import post_save, m2m_changed
from django.dispatch import receiver
from django.utils.translation import gettext_lazy as _
from AKModel.availability.models import Availability
from AKModel.models import AK, AKSlot, Room, Event, AKOwner, ConstraintViolation
......@@ -43,6 +44,8 @@ def update_cv_reso_deadline_for_slot(slot):
"""
event = slot.event
if slot.ak.reso and slot.event.reso_deadline and slot.start:
# Update only if reso_deadline exists
# if event was changed and reso_deadline is removed, CVs will be deleted by event changed handler
violation_type = ConstraintViolation.ViolationType.AK_AFTER_RESODEADLINE
new_violations = []
if slot.end > event.reso_deadline:
......@@ -57,14 +60,61 @@ def update_cv_reso_deadline_for_slot(slot):
update_constraint_violations(new_violations, list(slot.constraintviolation_set.filter(type=violation_type)))
def check_capacity_for_slot(slot: AKSlot):
"""
Check whether this slot violates the capacity requirement
:param slot: slot to check
:type slot: AKSlot
:return: Violation (if any) or None
:rtype: ConstraintViolation or None
"""
if slot.room:
if slot.room.capacity >= 0:
if slot.room.capacity < slot.ak.interest:
c = ConstraintViolation(
type=ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED,
level=ConstraintViolation.ViolationLevel.VIOLATION,
event=slot.event,
room=slot.room,
comment=_("Not enough space for AK interest (Interest: %(interest)d, Capacity: %(capacity)d)")
% {'interest': slot.ak.interest, 'capacity': slot.room.capacity},
)
c.ak_slots_tmp.add(slot)
c.aks_tmp.add(slot.ak)
return c
elif slot.room.capacity < slot.ak.interest + 5 or slot.room.capacity < slot.ak.interest * 1.25:
c = ConstraintViolation(
type=ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED,
level=ConstraintViolation.ViolationLevel.WARNING,
event=slot.event,
room=slot.room,
comment=_("Space is too close to AK interest (Interest: %(interest)d, Capacity: %(capacity)d)")
% {'interest': slot.ak.interest, 'capacity': slot.room.capacity}
)
c.ak_slots_tmp.add(slot)
c.aks_tmp.add(slot.ak)
return c
return None
@receiver(post_save, sender=AK)
def ak_changed_handler(sender, instance: AK, **kwargs):
# Changes might affect: Reso intention, Category, Interest
# TODO Reso intention changes
pass
# Check room capacities
violation_type = ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED
new_violations = []
for slot in instance.akslot_set.all():
cv = check_capacity_for_slot(slot)
if cv is not None:
new_violations.append(cv)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations(new_violations, existing_violations_to_check)
# TODO adapt for Room's reauirements
@receiver(m2m_changed, sender=AK.owners.through)
def ak_owners_changed_handler(sender, instance: AK, action: str, **kwargs):
"""
......@@ -492,11 +542,45 @@ def akslot_changed_handler(sender, instance: AKSlot, **kwargs):
# print(existing_violations_to_check)
update_constraint_violations(new_violations, existing_violations_to_check)
# == Check for room capacity ==
cv = check_capacity_for_slot(instance)
new_violations = [cv] if cv is not None else []
# Compare to/update list of existing violations of this type for this slot
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED))
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(post_save, sender=Room)
def room_changed_handler(sender, **kwargs):
def room_changed_handler(sender, instance: Room, **kwargs):
# Changes might affect: Room size
print(f"{sender} changed")
# Check room capacities
violation_type = ConstraintViolation.ViolationType.ROOM_CAPACITY_EXCEEDED
new_violations = []
for slot in instance.akslot_set.all():
cv = check_capacity_for_slot(slot)
if cv is not None:
new_violations.append(cv)
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations(new_violations, existing_violations_to_check)
@receiver(m2m_changed, sender=Room.properties.through)
def room_requirements_changed_handler(sender, instance: Room, action: str, **kwargs):
"""
Requirements of room changed
"""
# Only signal after change (post_add, post_delete, post_clear) are relevant
if not action.startswith("post"):
return
# print(f"{instance} changed")
event = instance.event
# TODO React to changes
@receiver(post_save, sender=Availability)
......@@ -530,7 +614,7 @@ def availability_changed_handler(sender, instance: Availability, **kwargs):
c.ak_slots_tmp.add(slot)
new_violations.append(c)
print(f"{instance.ak} has the following slots putside availabilities: {new_violations}")
print(f"{instance.ak} has the following slots outside availabilities: {new_violations}")
# ... and compare to/update list of existing violations of this type
# belonging to the AK that was recently changed (important!)
......@@ -540,8 +624,13 @@ def availability_changed_handler(sender, instance: Availability, **kwargs):
@receiver(post_save, sender=Event)
def event_changed_handler(sender, instance, **kwargs):
def event_changed_handler(sender, instance: Event, **kwargs):
# == Check for reso ak after reso deadline (which might have changed) ==
if instance.reso_deadline:
for slot in instance.akslot_set.filter(start__isnull=False, ak__reso=True):
update_cv_reso_deadline_for_slot(slot)
else:
# No reso deadline, delete all violations
violation_type = ConstraintViolation.ViolationType.AK_AFTER_RESODEADLINE
existing_violations_to_check = list(instance.constraintviolation_set.filter(type=violation_type))
update_constraint_violations([], existing_violations_to_check)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment