Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • feature-type-filters
  • komasolver
  • main
  • renovate/django-5.x
  • renovate/django_csp-4.x
  • renovate/jsonschema-4.x
  • renovate/uwsgi-2.x
7 results

Target

Select target project
  • konstantin/akplanning
  • matedealer/akplanning
  • kif/akplanning
  • mirco/akplanning
  • lordofthevoid/akplanning
  • voidptr/akplanning
  • xayomer/akplanning-fork
  • mollux/akplanning
  • neumantm/akplanning
  • mmarx/akplanning
  • nerf/akplanning
  • felix_bonn/akplanning
  • sebastian.uschmann/akplanning
13 results
Select Git revision
  • ak-import
  • feature/clear-schedule-button
  • feature/json-export-via-rest-framework
  • feature/json-schedule-import-tests
  • feature/preference-polling-form
  • fix/add-room-import-only-once
  • main
  • renovate/django-5.x
  • renovate/django-debug-toolbar-4.x
  • renovate/django-simple-history-3.x
  • renovate/mysqlclient-2.x
11 results
Show changes
Showing
with 1842 additions and 8 deletions
......@@ -13,14 +13,19 @@
{% block content %}
<h4 class="mt-4 mb-4">{% trans "AKs with public notes" %}</h4>
{% for ak in aks_with_comment %}
<a href="{{ ak.edit_url }}">{{ ak }}</a><br>{{ ak.notes }}<br><br>
<a href="{{ ak.detail_url }}">{{ ak }}</a>
<a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a>
<a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br>
{{ ak.notes }}<br><br>
{% empty %}
-
{% endfor %}
<h4 class="mt-4 mb-4">{% trans "AKs without availabilities" %}</h4>
{% for ak in aks_without_availabilities %}
<a href="{{ ak.edit_url }}">{{ ak }}</a><br>
<a href="{{ ak.detail_url }}">{{ ak }}</a>
<a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a>
<a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br>
{% empty %}
-<br>
{% endfor %}
......@@ -30,7 +35,10 @@
<h4 class="mt-4 mb-4">{% trans "AK wishes with slots" %}</h4>
{% for ak in ak_wishes_with_slots %}
<a href="{{ ak.detail_url }}">{{ ak }}</a> <a href="{% url "admin:AKModel_akslot_changelist" %}?ak={{ ak.pk }}">({{ ak.akslot__count }})</a><br>
<a href="{% url "admin:AKModel_akslot_changelist" %}?ak={{ ak.pk }}">({{ ak.akslot__count }})</a>
<a href="{{ ak.detail_url }}">{{ ak }}</a>
<a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a>
<a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br>
{% empty %}
-<br>
{% endfor %}
......@@ -39,7 +47,9 @@
<h4 class="mt-4 mb-4">{% trans "AKs without slots" %}</h4>
{% for ak in aks_without_slots %}
<a href="{{ ak.detail_url }}">{{ ak }}</a><br>
<a href="{{ ak.detail_url }}">{{ ak }}</a>
<a href="{{ ak.edit_url }}">{% fa6_icon "pen-to-square" %}</a>
<a class="link-warning" href="{% url "admin:AKModel_ak_change" object_id=ak.pk %}">{% fa6_icon "pen-to-square" %}</a><br>
{% empty %}
-<br>
{% endfor %}
......
......@@ -4,7 +4,7 @@ from datetime import timedelta
from django.test import TestCase
from django.utils import timezone
from AKModel.tests import BasicViewTests
from AKModel.tests.test_views import BasicViewTests
from AKModel.models import AKSlot, Event, Room
class ModelViewTests(BasicViewTests, TestCase):
......
......@@ -41,7 +41,9 @@ class SchedulingAdminView(AdminViewMixin, FilterByEventSlugMixin, ListView):
context_object_name = "slots_unscheduled"
def get_queryset(self):
return super().get_queryset().filter(start__isnull=True).select_related('event', 'ak').order_by('ak__track')
return super().get_queryset().filter(start__isnull=True).select_related('event', 'ak', 'ak__track',
'ak__category').prefetch_related('ak__types', 'ak__owners', 'ak__conflicts', 'ak__prerequisites',
'ak__requirements', 'ak__conflict').order_by('ak__track', 'ak')
def get_context_data(self, *, object_list=None, **kwargs):
context = super().get_context_data(object_list=object_list, **kwargs)
......@@ -152,6 +154,13 @@ class InterestEnteringAdminView(SuccessMessageMixin, AdminViewMixin, EventSlugMi
def get_success_url(self):
return self.request.path
def form_valid(self, form):
# Don't create a history entry for this change
form.instance.skip_history_when_saving = True
r = super().form_valid(form)
del form.instance.skip_history_when_saving
return r
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["title"] = f"{_('Enter interest')}"
......
from rest_framework import permissions, viewsets
from rest_framework.response import Response
from AKModel.models import Event
from AKSolverInterface.serializers import ExportEventSerializer
class ExportEventForSolverViewSet(viewsets.GenericViewSet):
"""
API View: Current event, formatted to be consumed by a solver.
Read-only
Follows the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
permission_classes = (permissions.DjangoModelPermissions,)
serializer_class = ExportEventSerializer
lookup_url_kwarg = "event_slug"
lookup_field = "slug"
# only allow exporting of active events
queryset = Event.objects.filter(active=True)
# rename view name to avoid 'List' suffix
def get_view_name(self):
return "JSON-Export for scheduling solver"
# somewhat hacky solution: TODO: FIXME
def list(self, request, *args, **kwargs):
"""Construct HTTP response showing serialized event export."""
# Below is the code of mixins.RetrieveModelMixin::retrieve
# which would usually be used to serve this detail API page.
# However, we do not specify the event to serve at the end of the URL
# but instead prepend the URL with it, i.e.
# `<slug>/api/<export_endpoint>` instead of the usual `api/<export_endpoint>/<slug>`.
# To make this work with the DefaultRouter, we serve this page as a list instead
# of a detail page. Hence, we use the method `list` instead of `retrieve`.
instance = self.get_object()
serializer = self.get_serializer(instance)
return Response(serializer.data)
from django.apps import AppConfig
class AksolverinterfaceConfig(AppConfig):
"""
App configuration for the solver interface (default)
"""
name = "AKSolverInterface"
import json
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from jsonschema.exceptions import best_match
from AKModel.forms import AdminIntermediateForm
from AKSolverInterface.utils import construct_schema_validator
class JSONScheduleImportForm(AdminIntermediateForm):
"""Form to import an AK schedule from a json file."""
json_data = forms.CharField(
required=False,
widget=forms.Textarea,
label=_("JSON data"),
help_text=_("JSON data from the scheduling solver"),
)
json_file = forms.FileField(
required=False,
label=_("File with JSON data"),
help_text=_("File with JSON data from the scheduling solver"),
)
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.json_schema_validator = construct_schema_validator(
schema="solver-output.schema.json"
)
def _check_json_data(self, data: str):
"""Validate `data` against our JSON schema.
:param data: The JSON string to validate using `self.json_schema_validator`.
:type data: str
:raises ValidationError: if the validation fails, with a description of the cause.
:return: The parsed JSON dict, if validation is successful.
"""
try:
schedule = json.loads(data)
except json.JSONDecodeError as ex:
raise ValidationError(_("Cannot decode as JSON"), "invalid") from ex
error = best_match(self.json_schema_validator.iter_errors(schedule))
if error:
raise ValidationError(
_("Invalid JSON format: %(msg)s at %(error_path)s"),
"invalid",
params={"msg": error.message, "error_path": error.json_path},
) from error
return schedule
def clean(self):
"""Extract and validate entered JSON data.
We allow entering of the schedule from two sources:
1. from an uploaded file
2. from a text field.
This function checks that data is entered from exactly one source.
If so, the entered JSON string is validated against our schema.
Any errors are reported at the corresponding form field.
"""
cleaned_data = super().clean()
if cleaned_data.get("json_file") and cleaned_data.get("json_data"):
err = ValidationError(
_("Please enter data as a file OR via text, not both."), "invalid"
)
self.add_error("json_data", err)
self.add_error("json_file", err)
elif not (cleaned_data.get("json_file") or cleaned_data.get("json_data")):
err = ValidationError(
_("No data entered. Please enter data as a file or via text."),
"invalid",
)
self.add_error("json_data", err)
self.add_error("json_file", err)
else:
source_field = "json_data"
data = cleaned_data.get(source_field)
if not data:
source_field = "json_file"
with cleaned_data.get(source_field).open() as ff:
data = ff.read()
try:
cleaned_data["data"] = self._check_json_data(data)
except ValidationError as ex:
self.add_error(source_field, ex)
return cleaned_data
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2025-03-25 12:03+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"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
#: AKSolverInterface/forms.py:18
msgid "JSON data"
msgstr "JSON-Daten"
#: AKSolverInterface/forms.py:19
msgid "JSON data from the scheduling solver"
msgstr "JSON-Daten, die der scheduling-solver produziert hat"
#: AKSolverInterface/forms.py:24
msgid "File with JSON data"
msgstr "Datei mit JSON-Daten"
#: AKSolverInterface/forms.py:25
msgid "File with JSON data from the scheduling solver"
msgstr "Datei mit JSON-Daten, die der scheduling-solver produziert hat"
#: AKSolverInterface/forms.py:38
msgid "Cannot decode as JSON"
msgstr "Dekodierung als JSON fehlgeschlagen"
#: AKSolverInterface/forms.py:43
#, python-format
msgid "Invalid JSON format: %(msg)s at %(error_path)s"
msgstr "Ungültige JSON-Eingabe: %(msg)s bei %(error_path)s"
#: AKSolverInterface/forms.py:54
msgid "Please enter data as a file OR via text, not both."
msgstr "Gib die Daten bitte als Datei oder als Text ein, nicht beides."
#: AKSolverInterface/forms.py:60
msgid "No data entered. Please enter data as a file or via text."
msgstr ""
"Keine Daten eingegeben. Gib die Daten bitte als Datei oder als Text ein."
#: AKSolverInterface/templates/admin/AKSolverInterface/import_json.html:23
msgid "Confirm"
msgstr "Bestätigen"
#: AKSolverInterface/templates/admin/AKSolverInterface/import_json.html:27
msgid "Cancel"
msgstr "Abbrechen"
#: AKSolverInterface/views.py:26
msgid "AK JSON Export"
msgstr "AK-JSON-Export"
#: AKSolverInterface/views.py:40
msgid "Exporting AKs for the solver failed! Reason: "
msgstr "Daten für den Solver exportieren fehlgeschlagen! Grund: "
#: AKSolverInterface/views.py:48
msgid "AK Schedule JSON Import"
msgstr "AK-Plan JSON-Import"
#: AKSolverInterface/views.py:58
#, python-brace-format
msgid "Successfully imported {n} slot(s)"
msgstr "Erfolgreich {n} Slot(s) importiert"
#: AKSolverInterface/views.py:66
msgid "Importing an AK schedule failed! Reason: "
msgstr "AK-Plan importieren fehlgeschlagen! Grund: "
#, python-format
#~ msgid "Invalid JSON format: field '%(field)s' is missing"
#~ msgstr "Ungültige JSON-Eingabe: das Feld '%(field)s' fehlt"
from rest_framework import serializers
from AKModel.models import (
AK,
AKPreference,
AKSlot,
Event,
EventParticipant,
Room,
)
class StringListField(serializers.ListField):
"""List field containing strings."""
child = serializers.CharField()
class IntListField(serializers.ListField):
"""List field containing integers."""
child = serializers.IntegerField()
class ExportRoomInfoSerializer(serializers.ModelSerializer):
"""Serializer of Room objects for the 'info' field.
Used in `ExportRoomSerializer` to serialize Room objects
for the export to a solver. Part of the implementation of the
format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
class Meta:
model = Room
fields = ["name"]
read_only_fields = ["name"]
class ExportRoomSerializer(serializers.ModelSerializer):
"""Export serializer for Room objects.
Used to serialize Room objects for the export to a solver.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
time_constraints = StringListField(source="get_time_constraints", read_only=True)
fulfilled_room_constraints = StringListField(
source="get_fulfilled_room_constraints", read_only=True
)
info = ExportRoomInfoSerializer(source="*")
class Meta:
model = Room
fields = [
"id",
"capacity",
"time_constraints",
"fulfilled_room_constraints",
"info",
]
read_only_fields = [
"id",
"capacity",
"time_constraints",
"fulfilled_room_constraints",
"info",
]
class ExportAKSlotInfoSerializer(serializers.ModelSerializer):
"""Serializer of AKSlot objects for the 'info' field.
Used in `ExportAKSlotSerializer` to serialize AKSlot objects
for the export to a solver. Part of the implementation of the
format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
name = serializers.CharField(source="ak.name")
head = serializers.SerializerMethodField()
reso = serializers.BooleanField(source="ak.reso")
description = serializers.CharField(source="ak.description")
duration_in_hours = serializers.FloatField(source="duration")
django_ak_id = serializers.IntegerField(source="ak.pk")
types = StringListField(source="type_names")
def get_head(self, slot: AKSlot) -> str:
"""Get string representation for 'head' field."""
return ", ".join([str(owner) for owner in slot.ak.owners.all()])
class Meta:
model = AKSlot
fields = [
"name",
"head",
"description",
"reso",
"duration_in_hours",
"django_ak_id",
"types",
]
read_only_fields = [
"name",
"head",
"description",
"reso",
"duration_in_hours",
"django_ak_id",
"types",
]
class ExportAKSlotPropertiesSerializer(serializers.ModelSerializer):
"""Serializer of AKSlot objects for the 'properties' field.
Used in `ExportAKSlotSerializer` to serialize AKSlot objects
for the export to a solver. Part of the implementation of the
format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
conflicts = IntListField(source="conflict_pks")
dependencies = IntListField(source="depencency_pks")
class Meta:
model = AKSlot
fields = ["conflicts", "dependencies"]
read_only_fields = ["conflicts", "dependencies"]
class ExportAKSlotSerializer(serializers.ModelSerializer):
"""Export serializer for AKSlot objects.
Used to serialize AKSlot objects for the export to a solver.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
duration = serializers.IntegerField(source="export_duration")
room_constraints = StringListField(source="get_room_constraints")
time_constraints = StringListField(source="get_time_constraints")
info = ExportAKSlotInfoSerializer(source="*")
properties = ExportAKSlotPropertiesSerializer(source="*")
class Meta:
model = AKSlot
fields = [
"id",
"duration",
"properties",
"room_constraints",
"time_constraints",
"info",
]
read_only_fields = [
"id",
"duration",
"properties",
"room_constraints",
"time_constraints",
"info",
]
class ExportAKPreferenceSerializer(serializers.ModelSerializer):
"""Export serializer for AKPreference objects.
Used to serialize AKPreference objects for the export to a solver.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
ak_id = serializers.IntegerField(source="slot.pk")
class Meta:
model = AKPreference
fields = ["ak_id", "required", "preference_score"]
read_only_fields = ["ak_id", "required", "preference_score"]
class ExportParticipantInfoSerializer(serializers.ModelSerializer):
"""Serializer of EventParticipant objects for the 'info' field.
Used in `ExportParticipantSerializer` to serialize EventParticipant objects
for the export to a solver. Part of the implementation of the
format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
name = serializers.CharField(source="__str__")
class Meta:
model = EventParticipant
fields = ["name"]
read_only_fields = ["name"]
class ExportParticipantSerializer(serializers.ModelSerializer):
"""Export serializer for EventParticipant objects.
Used to serialize EventParticipant objects for the export to a solver.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
room_constraints = StringListField(source="get_room_constraints")
time_constraints = StringListField(source="get_time_constraints")
preferences = ExportAKPreferenceSerializer(source="export_preferences", many=True)
info = ExportParticipantInfoSerializer(source="*")
class Meta:
model = EventParticipant
fields = ["id", "info", "room_constraints", "time_constraints", "preferences"]
read_only_fields = ["id", "info", "room_constraints", "time_constraints", "preferences"]
class ExportParticipantAndDummiesSerializer(serializers.BaseSerializer):
"""Export serializer for EventParticipant objects that includes 'dummy' participants.
This serializer is a work-around to make the solver compatible with the AKOwner model.
Internally, `ExportParticipantSerializer` is used to serialize all EventParticipants of
the event to serialize. To avoid scheduling conflicts, a 'dummy' participant is then added
to the list for each AKOwner of the event. These dummy participants only have 'required'
preference for all AKs of the owner, so the target of the optimization is not impacted.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
def create(self, validated_data):
raise ValueError("`ExportParticipantAndDummiesSerializer` is read-only.")
def to_internal_value(self, data):
raise ValueError("`ExportParticipantAndDummiesSerializer` is read-only.")
def update(self, instance, validated_data):
raise ValueError("`ExportParticipantAndDummiesSerializer` is read-only.")
def to_representation(self, instance: Event):
event = instance
real_participants = ExportParticipantSerializer(event.participants, many=True).data
dummies = []
if EventParticipant.objects.exists():
next_participant_pk = EventParticipant.objects.latest("pk").pk + 1
else:
next_participant_pk = 1
# add one dummy participant per owner
# this ensures that the hard constraints from each owner are considered
for new_pk, owner in enumerate(event.owners, next_participant_pk):
owned_slots = event.slots.filter(ak__owners=owner).order_by().all()
if not owned_slots:
continue
new_participant_data = {
"id": new_pk,
"info": {"name": f"{owner} [AKOwner]"},
"room_constraints": [],
"time_constraints": [],
"preferences": [
{"ak_id": slot.pk, "required": True, "preference_score": -1}
for slot in owned_slots
]
}
dummies.append(new_participant_data)
return real_participants + dummies
class ExportEventInfoSerializer(serializers.ModelSerializer):
"""Serializer of an Event object for the 'info' field.
Used in `ExportEventSerializer` to serialize an Event object
for the export to a solver. Part of the implementation of the
format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
title = serializers.CharField(source="name")
contact_email = serializers.EmailField(required=False)
place = serializers.CharField(required=False)
class Meta:
model = Event
fields = ["title", "slug", "contact_email", "place"]
class ExportTimeslotBlockSerializer(serializers.BaseSerializer):
"""Read-only serializer for timeslots.
Used to serialize timeslots for the export to a solver.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
def create(self, validated_data):
raise ValueError("`ExportTimeslotBlockSerializer` is read-only.")
def to_internal_value(self, data):
raise ValueError("`ExportTimeslotBlockSerializer` is read-only.")
def update(self, instance, validated_data):
raise ValueError("`ExportTimeslotBlockSerializer` is read-only.")
def to_representation(self, instance: Event):
"""Construct serialized representation of the timeslots of an event."""
# pylint: disable=import-outside-toplevel
from AKModel.availability.models import Availability
event = instance
blocks = list(event.discretize_timeslots())
def _check_event_not_covered(availabilities: list[Availability]) -> bool:
"""Test if event is not covered by availabilities."""
return not Availability.is_event_covered(event, availabilities)
def _check_akslot_fixed_in_timeslot(
ak_slot: AKSlot, timeslot: Availability
) -> bool:
"""Test if an AKSlot is fixed to overlap a timeslot slot."""
if not ak_slot.fixed or ak_slot.start is None:
return False
fixed_avail = Availability(
event=event, start=ak_slot.start, end=ak_slot.end
)
return fixed_avail.overlaps(timeslot, strict=True)
def _check_add_constraint(
slot: Availability, availabilities: list[Availability]
) -> bool:
"""Test if object is not available for whole event and may happen during slot."""
return _check_event_not_covered(availabilities) and slot.is_covered(
availabilities
)
def _generate_time_constraints(
avail_label: str,
avail_dict: dict,
timeslot_avail: Availability,
prefix: str = "availability",
) -> list[str]:
return [
f"{prefix}-{avail_label}-{pk}"
for pk, availabilities in avail_dict.items()
if _check_add_constraint(timeslot_avail, availabilities)
]
timeslots = {
"info": {"duration": float(event.export_slot)},
"blocks": [],
}
ak_availabilities = {
ak.pk: Availability.union(ak.availabilities.all())
for ak in AK.objects.filter(event=event).all()
}
room_availabilities = {
room.pk: Availability.union(room.availabilities.all()) for room in event.rooms
}
person_availabilities = {
person.pk: Availability.union(person.availabilities.all())
for person in event.owners
}
participant_availabilities = {
participant.pk: Availability.union(participant.availabilities.all())
for participant in event.participants
}
block_names = []
for block_idx, block in enumerate(blocks):
current_block = []
if not block:
continue
block_start = block[0].avail.start.astimezone(event.timezone)
block_end = block[-1].avail.end.astimezone(event.timezone)
start_day = block_start.strftime("%A, %d. %b")
if block_start.date() == block_end.date():
# same day
time_str = (
block_start.strftime("%H:%M") + " - " + block_end.strftime("%H:%M")
)
else:
# different days
time_str = (
block_start.strftime("%a %H:%M")
+ " - "
+ block_end.strftime("%a %H:%M")
)
block_names.append([start_day, time_str])
block_timeconstraints = [
f"notblock{idx}" for idx in range(len(blocks)) if idx != block_idx
]
for timeslot in block:
time_constraints = []
# if reso_deadline is set and timeslot ends before it,
# add fulfilled time constraint 'resolution'
if (
event.reso_deadline is None
or timeslot.avail.end < event.reso_deadline
):
time_constraints.append("resolution")
# add fulfilled time constraints for all AKs that cannot happen during full event
time_constraints.extend(
_generate_time_constraints("ak", ak_availabilities, timeslot.avail)
)
# add fulfilled time constraints for all persons that are not available for full event
time_constraints.extend(
_generate_time_constraints(
"person", person_availabilities, timeslot.avail
)
)
# add fulfilled time constraints for all rooms that are not available for full event
time_constraints.extend(
_generate_time_constraints(
"room", room_availabilities, timeslot.avail
)
)
# add fulfilled time constraints for all participants that are not available for full event
time_constraints.extend(
_generate_time_constraints(
"participant", participant_availabilities, timeslot.avail
)
)
# add fulfilled time constraints for all AKSlots fixed to happen during timeslot
time_constraints.extend(
[
f"fixed-akslot-{slot.id}"
for slot in AKSlot.objects.filter(
event=event, fixed=True
).exclude(start__isnull=True)
if _check_akslot_fixed_in_timeslot(slot, timeslot.avail)
]
)
time_constraints.extend(timeslot.constraints)
time_constraints.extend(block_timeconstraints)
time_constraints.sort()
current_block.append(
{
"id": timeslot.idx,
"info": {
"start": timeslot.avail.start.astimezone(
event.timezone
).strftime("%Y-%m-%d %H:%M"),
"end": timeslot.avail.end.astimezone(
event.timezone
).strftime("%Y-%m-%d %H:%M"),
},
"fulfilled_time_constraints": time_constraints,
}
)
timeslots["blocks"].append(current_block)
timeslots["info"]["blocknames"] = block_names
return timeslots
class ExportEventSerializer(serializers.ModelSerializer):
"""Export serializer for an Event object.
Used to serialize an Event for the export to a solver.
Part of the implementation of the format of the KoMa solver:
https://github.com/Die-KoMa/ak-plan-optimierung/wiki/Input-&-output-format#input--output-format
"""
info = ExportEventInfoSerializer(source="*")
rooms = ExportRoomSerializer(many=True)
aks = ExportAKSlotSerializer(source="slots", many=True)
participants = ExportParticipantAndDummiesSerializer(source="*")
timeslots = ExportTimeslotBlockSerializer(source="*")
class Meta:
model = Event
fields = ["participants", "rooms", "timeslots", "info", "aks"]
read_only_fields = ["participants", "rooms", "timeslots", "info", "aks"]
{% extends "admin/base_site.html" %}
{% load tz %}
{% block content %}
<div class="mb-3">
<label for="export_single_line" class="form-label">Exported JSON:</label>
<input readonly type="text" name="export_single_line" class="form-control form-control-sm" value="{{ json_data_oneline }}" style="font-family:var(--font-family-monospace);">
</div>
<div class="mb-3">
<label class="form-label">Exported JSON (indented for better readability):</label>
<pre class="prettyprint border rounded p-2">{{ json_data }}</pre>
</div>
<script>
$(document).ready(function() {
// JSON highlighting.
prettyPrint();
}
)
</script>
{% endblock %}
{% extends "admin/base_site.html" %}
{% load tags_AKModel %}
{% load i18n %}
{% load django_bootstrap5 %}
{% load fontawesome_6 %}
{% block title %}{{event}}: {{ title }}{% endblock %}
{% block content %}
{% block action_preview %}
<p>
{{ preview|linebreaksbr }}
</p>
{% endblock %}
<form enctype="multipart/form-data" method="post">{% csrf_token %}
{% bootstrap_form form %}
<div class="float-end">
<button type="submit" class="save btn btn-success" value="Submit">
{% fa6_icon "check" 'fas' %} {% trans "Confirm" %}
</button>
</div>
<a href="javascript:history.back()" class="btn btn-info">
{% fa6_icon "times" 'fas' %} {% trans "Cancel" %}
</a>
</form>
{% endblock %}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.