From ee8742f6f6c59e9fcc92df2d98d4d51335952d67 Mon Sep 17 00:00:00 2001 From: Benjamin Dauvergne Date: Thu, 22 Apr 2021 10:12:36 +0200 Subject: [PATCH 2/2] api: restart fillslot on IntegrityError (#53367) --- chrono/api/views.py | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/chrono/api/views.py b/chrono/api/views.py index 6622d8c..7b1c660 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -19,7 +19,7 @@ import datetime import itertools import uuid -from django.db import transaction +from django.db import IntegrityError, transaction from django.db.models import Prefetch, Q from django.http import Http404, HttpResponse from django.shortcuts import get_object_or_404 @@ -918,7 +918,7 @@ class Fillslots(APIView): def post(self, request, agenda_identifier=None, event_identifier=None, format=None): return self.fillslot(request=request, agenda_identifier=agenda_identifier, format=format) - def fillslot(self, request, agenda_identifier=None, slots=[], format=None): + def fillslot(self, request, agenda_identifier=None, slots=[], format=None, retry=False): multiple_booking = bool(not slots) try: agenda = Agenda.objects.get(slug=agenda_identifier) @@ -1138,19 +1138,34 @@ class Fillslots(APIView): # booking requires real Event objects (not lazy Timeslots); # create them now, with data from the slots and the desk we found. events = [] - for start_datetime in datetimes: - event = Event.objects.create( - agenda=available_desk.agenda, - slug=str(uuid.uuid4()), # set slug to avoid queries during slug generation - meeting_type=meeting_type, - start_datetime=start_datetime, - full=False, - places=1, - desk=available_desk, - ) - if resources: - event.resources.add(*resources) - events.append(event) + try: + with transaction.atomic(): + for start_datetime in datetimes: + event = Event.objects.create( + agenda=available_desk.agenda, + slug=str(uuid.uuid4()), # set slug to avoid queries during slug generation + meeting_type=meeting_type, + start_datetime=start_datetime, + full=False, + places=1, + desk=available_desk, + ) + if resources: + event.resources.add(*resources) + events.append(event) + except IntegrityError: + # "optimistic concurrency control", between our availability + # check with get_all_slots() and now, new event can have been + # created and conflict with the events we want to create, and + # so we get an IntegrityError exception. In this case we + # restart the fillslot() from the begginning to redo the + # availability check and return a proper error to the client. + # + # To prevent looping, we still raise the IntegrityError during + # the second run of fillslot(). + if retry: + raise + return self.fillslot(request, agenda_identifier=agenda_identifier, slots=slots, retry=True) else: # convert event recurrence identifiers to real event slugs for i, slot in enumerate(slots.copy()): -- 2.31.0