Skip to content
Snippets Groups Projects
Commit 293c78c5 authored by Konrad Mohrfeldt's avatar Konrad Mohrfeldt :koala:
Browse files

feat: allow single request scheduling

Until now the steering API client was expected to submit two requests.

1. The initial schedule request, with a `schedule` object
2. A second schedule request, with an additional `solutions` object

This `solutions` object was empty, if no conflicts arose from the
submitted schedule in the first place. But instead of just creating the
requested schedule if no conflicts were detected we still required that
second round-trip, introducing possible race conditions in the client,
if another successful scheduling request was made between requests.

From now on the steering API will create schedules if no conflicts have
been detected and will only require the client to submit solutions if
there really are conflicts to solve.
parent 51be0fbe
No related branches found
No related tags found
1 merge request!22refactor schedule REST API
......@@ -41,6 +41,12 @@ from steering.settings import (
)
class ScheduleConflictError(ValidationError):
def __init__(self, *args, conflicts=None, **kwargs):
super().__init__(*args, **kwargs)
self.conflicts = conflicts
class Type(models.Model):
name = models.CharField(max_length=32)
slug = models.SlugField(max_length=32, unique=True)
......@@ -732,7 +738,7 @@ class Schedule(models.Model):
"""
sdl = data["schedule"]
solutions = data["solutions"]
solutions = data.get("solutions", [])
# Regenerate conflicts
schedule = Schedule.instantiate_upcoming(sdl, show_pk, schedule_pk)
......@@ -756,9 +762,10 @@ class Schedule(models.Model):
)
if len(solutions) != num_conflicts:
raise ValidationError(
raise ScheduleConflictError(
_("Numbers of conflicts and solutions don't match."),
code="one-solution-per-conflict",
conflicts=conflicts,
)
# Projected timeslots to create
......@@ -963,7 +970,11 @@ class Schedule(models.Model):
conflicts["notes"] = data.get("notes")
conflicts["playlists"] = data.get("playlists")
return conflicts
raise ScheduleConflictError(
_("Not all conflicts have been resolved."),
code="unresolved-conflicts",
conflicts=conflicts,
)
# Collect upcoming timeslots to delete which might still remain
del_timeslots = TimeSlot.objects.filter(
......
......@@ -42,6 +42,7 @@ from program.models import (
MusicFocus,
Note,
Schedule,
ScheduleConflictError,
Show,
TimeSlot,
Topic,
......@@ -483,28 +484,17 @@ class APIScheduleViewSet(
if show_pk is None or "schedule" not in request.data:
return Response(status=status.HTTP_400_BAD_REQUEST)
# First create submit -> return projected timeslots and collisions
# TODO: Perhaps directly insert into database if no conflicts found
if "solutions" not in request.data:
try:
resolution = Schedule.resolve_conflicts(request.data, pk, show_pk)
except ScheduleConflictError as exc:
# TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
return Response(
Schedule.make_conflicts(request.data["schedule"], pk, show_pk),
)
# Otherwise try to resolve
resolution = Schedule.resolve_conflicts(request.data, pk, show_pk)
return Response(exc.conflicts)
if all(key in resolution for key in ["create", "update", "delete"]):
# this is a dry-run
return Response(resolution, status=status.HTTP_202_ACCEPTED)
# If resolution went well
if "projected" not in resolution:
return Response(resolution, status=status.HTTP_201_CREATED)
# Otherwise return conflicts
# TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
return Response(resolution)
return Response(resolution, status=status.HTTP_201_CREATED)
def update(self, request, *args, **kwargs):
"""
......@@ -540,26 +530,14 @@ class APIScheduleViewSet(
serializer = ScheduleSerializer(schedule)
return Response(serializer.data)
# First update submit -> return projected timeslots and collisions
if "solutions" not in request.data:
# TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
return Response(
Schedule.make_conflicts(
request.data["schedule"], schedule.pk, schedule.show.pk
)
try:
resolution = Schedule.resolve_conflicts(
request.data, schedule.pk, schedule.show.pk
)
except ScheduleConflictError as exc:
# TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
return Response(exc.conflicts)
# Otherwise try to resolve
resolution = Schedule.resolve_conflicts(
request.data, schedule.pk, schedule.show.pk
)
# If resolution went well
if "projected" not in resolution:
return Response(resolution)
# Otherwise return conflicts
# TODO: respond with status.HTTP_409_CONFLICT when the dashboard can handle it
return Response(resolution)
def destroy(self, request, *args, **kwargs):
......
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