api: remove useless code in fillslots views (#79300)
gitea/chrono/pipeline/head This commit looks good Details

This commit is contained in:
Lauréline Guérin 2023-07-01 10:13:49 +02:00 committed by Lauréline Guérin
parent 848d014720
commit 747928c680
1 changed files with 162 additions and 378 deletions

View File

@ -1245,159 +1245,29 @@ class EventsAgendaFillslots(APIView):
extra_data = get_extra_data(request, serializer.validated_data)
available_desk = None
color = None
user_external_id = payload.get('user_external_id') or None
exclude_user = payload.get('exclude_user')
events = get_events_from_slots(slots, request, agenda, payload)
if agenda.accept_meetings():
# slots are actually timeslot ids (meeting_type:start_datetime), not events ids.
# split them back to get both parts
meeting_type_id = slots[0].split(':')[0]
datetimes = set()
for slot in slots:
try:
meeting_type_id_, datetime_str = slot.split(':')
except ValueError:
raise APIErrorBadRequest(N_('invalid slot: %s'), slot)
if meeting_type_id_ != meeting_type_id:
raise APIErrorBadRequest(
N_('all slots must have the same meeting type id (%s)'), meeting_type_id
)
try:
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
except ValueError:
raise APIErrorBadRequest(N_('bad datetime format: %s'), datetime_str)
# search free places. Switch to waiting list if necessary.
in_waiting_list = False
for event in events:
if event.start_datetime > now():
if payload.get('force_waiting_list') and not event.waiting_list_places:
raise APIError(N_('no waiting list'))
resources = get_resources_from_request(request, agenda)
# get all free slots and separate them by desk
try:
try:
meeting_type = agenda.get_meetingtype(slug=meeting_type_id)
except MeetingType.DoesNotExist:
# legacy access by id
meeting_type = agenda.get_meetingtype(id_=meeting_type_id)
except (MeetingType.DoesNotExist, ValueError):
raise APIErrorBadRequest(N_('invalid meeting type id: %s'), meeting_type_id)
all_slots = sorted(
agenda.get_all_slots(
meeting_type,
resources=resources,
user_external_id=user_external_id if exclude_user else None,
start_datetime=min(datetimes),
end_datetime=max(datetimes) + datetime.timedelta(minutes=meeting_type.duration),
),
key=lambda slot: slot.start_datetime,
)
all_free_slots = [slot for slot in all_slots if not slot.full]
datetimes_by_desk = collections.defaultdict(set)
for slot in all_free_slots:
datetimes_by_desk[slot.desk.id].add(slot.start_datetime)
color_label = payload.get('use_color_for')
if color_label:
color = BookingColor.objects.get_or_create(label=color_label)[0]
available_desk = None
if agenda.kind == 'virtual':
# Compute fill_rate by agenda/date
fill_rates = collections.defaultdict(dict)
for slot in all_slots:
ref_date = slot.start_datetime.date()
if ref_date not in fill_rates[slot.desk.agenda]:
date_dict = fill_rates[slot.desk.agenda][ref_date] = {'free': 0, 'full': 0}
else:
date_dict = fill_rates[slot.desk.agenda][ref_date]
if slot.full:
date_dict['full'] += 1
else:
date_dict['free'] += 1
for dd in fill_rates.values():
for date_dict in dd.values():
date_dict['fill_rate'] = date_dict['full'] / (date_dict['full'] + date_dict['free'])
# select a desk on the agenda with min fill_rate on the given date
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
desk = Desk.objects.get(id=available_desk_id)
if available_desk is None:
available_desk = desk
available_desk_rate = 0
for dt in datetimes:
available_desk_rate += fill_rates[available_desk.agenda][dt.date()][
'fill_rate'
]
else:
for dt in datetimes:
desk_rate = 0
for dt in datetimes:
desk_rate += fill_rates[desk.agenda][dt.date()]['fill_rate']
if desk_rate < available_desk_rate:
available_desk = desk
available_desk_rate = desk_rate
else:
# meeting agenda
# search first desk where all requested slots are free
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
available_desk = Desk.objects.get(id=available_desk_id)
break
if available_desk is None:
raise APIError(N_('no more desk available'))
# all datetimes are free, book them in order
datetimes = list(datetimes)
datetimes.sort()
# get a real meeting_type for virtual agenda
if agenda.kind == 'virtual':
meeting_type = MeetingType.objects.get(agenda=available_desk.agenda, slug=meeting_type.slug)
# 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:
events.append(
Event(
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,
)
)
in_waiting_list = False
else:
events = get_events_from_slots(slots, request, agenda, payload)
# search free places. Switch to waiting list if necessary.
in_waiting_list = False
for event in events:
if event.start_datetime > now():
if payload.get('force_waiting_list') and not event.waiting_list_places:
raise APIError(N_('no waiting list'))
if event.waiting_list_places:
if (
payload.get('force_waiting_list')
or (event.booked_places + places_count) > event.places
or event.booked_waiting_list_places
):
# if this is full or there are people waiting, put new bookings
# in the waiting list.
in_waiting_list = True
if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
raise APIError(N_('sold out'))
else:
if (event.booked_places + places_count) > event.places:
if event.waiting_list_places:
if (
payload.get('force_waiting_list')
or (event.booked_places + places_count) > event.places
or event.booked_waiting_list_places
):
# if this is full or there are people waiting, put new bookings
# in the waiting list.
in_waiting_list = True
if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
raise APIError(N_('sold out'))
else:
if (event.booked_places + places_count) > event.places:
raise APIError(N_('sold out'))
try:
with transaction.atomic():
@ -1408,13 +1278,13 @@ class EventsAgendaFillslots(APIView):
# now we have a list of events, book them.
primary_booking = None
for event in events:
if agenda.accept_meetings():
event.save()
if resources:
event.resources.add(*resources)
for dummy in range(places_count):
new_booking = make_booking(
event, payload, extra_data, primary_booking, in_waiting_list, color
event=event,
payload=payload,
extra_data=extra_data,
primary_booking=primary_booking,
in_waiting_list=in_waiting_list,
)
new_booking.save()
if primary_booking is None:
@ -1457,23 +1327,17 @@ class EventsAgendaFillslots(APIView):
'anonymize_url': request.build_absolute_uri(
reverse('api-anonymize-booking', kwargs={'booking_pk': primary_booking.id})
),
'accept_url': request.build_absolute_uri(
reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.pk})
),
'suspend_url': request.build_absolute_uri(
reverse('api-suspend-booking', kwargs={'booking_pk': primary_booking.pk})
),
},
}
if agenda.kind == 'events':
response['api']['accept_url'] = request.build_absolute_uri(
reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.pk})
)
response['api']['suspend_url'] = request.build_absolute_uri(
reverse('api-suspend-booking', kwargs={'booking_pk': primary_booking.pk})
)
if agenda.accept_meetings():
response['end_datetime'] = format_response_datetime(events[-1].end_datetime)
response['duration'] = (events[-1].end_datetime - events[-1].start_datetime).seconds // 60
if available_desk:
response['desk'] = {'label': available_desk.label, 'slug': available_desk.slug}
if to_cancel_booking:
response['cancelled_booking_id'] = cancelled_booking_id
if agenda.kind == 'events' and not multiple_booking:
if not multiple_booking:
event = events[0]
# event.full is not up to date, it might have been changed by previous new_booking.save().
event.refresh_from_db()
@ -1482,7 +1346,7 @@ class EventsAgendaFillslots(APIView):
response['end_datetime'] = format_response_datetime(event.end_datetime)
else:
response['end_datetime'] = None
if agenda.kind == 'events' and multiple_booking:
else:
response['events'] = [
{
'slug': x.slug,
@ -1493,8 +1357,6 @@ class EventsAgendaFillslots(APIView):
}
for x in events
]
if agenda.kind == 'meetings':
response['resources'] = [r.slug for r in resources]
return Response(response)
@ -1515,7 +1377,6 @@ class MeetingsAgendaFillslots(APIView):
def fillslot(self, request, agenda, slots=None, retry=False):
slots = slots or []
multiple_booking = bool(not slots)
known_body_params = set(request.query_params).intersection(
{'label', 'user_name', 'backoffice_url', 'user_display_label'}
@ -1534,20 +1395,6 @@ class MeetingsAgendaFillslots(APIView):
if 'slots' in payload:
slots = payload['slots']
if 'count' in payload:
places_count = payload['count']
elif 'count' in request.query_params:
# legacy: count in the query string
try:
places_count = int(request.query_params['count'])
except ValueError:
raise APIErrorBadRequest(N_('invalid value for count (%s)'), request.query_params['count'])
else:
places_count = 1
if places_count <= 0:
raise APIErrorBadRequest(N_('count cannot be less than or equal to zero'))
to_cancel_booking = None
cancel_booking_id = None
if payload.get('cancel_booking_id'):
@ -1562,13 +1409,6 @@ class MeetingsAgendaFillslots(APIView):
to_cancel_booking = Booking.objects.get(pk=cancel_booking_id)
if to_cancel_booking.cancellation_datetime:
cancel_error = N_('cancel booking: booking already cancelled')
else:
to_cancel_places_count = (
to_cancel_booking.secondary_booking_set.filter(event=to_cancel_booking.event).count()
+ 1
)
if places_count != to_cancel_places_count:
cancel_error = N_('cancel booking: count is different')
except Booking.DoesNotExist:
cancel_error = N_('cancel booking: booking does no exist')
@ -1582,154 +1422,126 @@ class MeetingsAgendaFillslots(APIView):
user_external_id = payload.get('user_external_id') or None
exclude_user = payload.get('exclude_user')
if agenda.accept_meetings():
# slots are actually timeslot ids (meeting_type:start_datetime), not events ids.
# split them back to get both parts
meeting_type_id = slots[0].split(':')[0]
datetimes = set()
for slot in slots:
try:
meeting_type_id_, datetime_str = slot.split(':')
except ValueError:
raise APIErrorBadRequest(N_('invalid slot: %s'), slot)
if meeting_type_id_ != meeting_type_id:
raise APIErrorBadRequest(
N_('all slots must have the same meeting type id (%s)'), meeting_type_id
)
try:
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
except ValueError:
raise APIErrorBadRequest(N_('bad datetime format: %s'), datetime_str)
resources = get_resources_from_request(request, agenda)
# get all free slots and separate them by desk
# slots are actually timeslot ids (meeting_type:start_datetime), not events ids.
# split them back to get both parts
meeting_type_id = slots[0].split(':')[0]
datetimes = set()
for slot in slots:
try:
try:
meeting_type = agenda.get_meetingtype(slug=meeting_type_id)
except MeetingType.DoesNotExist:
# legacy access by id
meeting_type = agenda.get_meetingtype(id_=meeting_type_id)
except (MeetingType.DoesNotExist, ValueError):
raise APIErrorBadRequest(N_('invalid meeting type id: %s'), meeting_type_id)
all_slots = sorted(
agenda.get_all_slots(
meeting_type,
resources=resources,
user_external_id=user_external_id if exclude_user else None,
start_datetime=min(datetimes),
end_datetime=max(datetimes) + datetime.timedelta(minutes=meeting_type.duration),
),
key=lambda slot: slot.start_datetime,
)
all_free_slots = [slot for slot in all_slots if not slot.full]
datetimes_by_desk = collections.defaultdict(set)
for slot in all_free_slots:
datetimes_by_desk[slot.desk.id].add(slot.start_datetime)
color_label = payload.get('use_color_for')
if color_label:
color = BookingColor.objects.get_or_create(label=color_label)[0]
available_desk = None
if agenda.kind == 'virtual':
# Compute fill_rate by agenda/date
fill_rates = collections.defaultdict(dict)
for slot in all_slots:
ref_date = slot.start_datetime.date()
if ref_date not in fill_rates[slot.desk.agenda]:
date_dict = fill_rates[slot.desk.agenda][ref_date] = {'free': 0, 'full': 0}
else:
date_dict = fill_rates[slot.desk.agenda][ref_date]
if slot.full:
date_dict['full'] += 1
else:
date_dict['free'] += 1
for dd in fill_rates.values():
for date_dict in dd.values():
date_dict['fill_rate'] = date_dict['full'] / (date_dict['full'] + date_dict['free'])
# select a desk on the agenda with min fill_rate on the given date
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
desk = Desk.objects.get(id=available_desk_id)
if available_desk is None:
available_desk = desk
available_desk_rate = 0
for dt in datetimes:
available_desk_rate += fill_rates[available_desk.agenda][dt.date()][
'fill_rate'
]
else:
for dt in datetimes:
desk_rate = 0
for dt in datetimes:
desk_rate += fill_rates[desk.agenda][dt.date()]['fill_rate']
if desk_rate < available_desk_rate:
available_desk = desk
available_desk_rate = desk_rate
else:
# meeting agenda
# search first desk where all requested slots are free
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
available_desk = Desk.objects.get(id=available_desk_id)
break
if available_desk is None:
raise APIError(N_('no more desk available'))
# all datetimes are free, book them in order
datetimes = list(datetimes)
datetimes.sort()
# get a real meeting_type for virtual agenda
if agenda.kind == 'virtual':
meeting_type = MeetingType.objects.get(agenda=available_desk.agenda, slug=meeting_type.slug)
# 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:
events.append(
Event(
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,
)
meeting_type_id_, datetime_str = slot.split(':')
except ValueError:
raise APIErrorBadRequest(N_('invalid slot: %s'), slot)
if meeting_type_id_ != meeting_type_id:
raise APIErrorBadRequest(
N_('all slots must have the same meeting type id (%s)'), meeting_type_id
)
in_waiting_list = False
else:
events = get_events_from_slots(slots, request, agenda, payload)
try:
datetimes.add(make_aware(datetime.datetime.strptime(datetime_str, '%Y-%m-%d-%H%M')))
except ValueError:
raise APIErrorBadRequest(N_('bad datetime format: %s'), datetime_str)
# search free places. Switch to waiting list if necessary.
in_waiting_list = False
for event in events:
if event.start_datetime > now():
if payload.get('force_waiting_list') and not event.waiting_list_places:
raise APIError(N_('no waiting list'))
resources = get_resources_from_request(request, agenda)
if event.waiting_list_places:
if (
payload.get('force_waiting_list')
or (event.booked_places + places_count) > event.places
or event.booked_waiting_list_places
):
# if this is full or there are people waiting, put new bookings
# in the waiting list.
in_waiting_list = True
if (event.booked_waiting_list_places + places_count) > event.waiting_list_places:
raise APIError(N_('sold out'))
# get all free slots and separate them by desk
try:
try:
meeting_type = agenda.get_meetingtype(slug=meeting_type_id)
except MeetingType.DoesNotExist:
# legacy access by id
meeting_type = agenda.get_meetingtype(id_=meeting_type_id)
except (MeetingType.DoesNotExist, ValueError):
raise APIErrorBadRequest(N_('invalid meeting type id: %s'), meeting_type_id)
all_slots = sorted(
agenda.get_all_slots(
meeting_type,
resources=resources,
user_external_id=user_external_id if exclude_user else None,
start_datetime=min(datetimes),
end_datetime=max(datetimes) + datetime.timedelta(minutes=meeting_type.duration),
),
key=lambda slot: slot.start_datetime,
)
all_free_slots = [slot for slot in all_slots if not slot.full]
datetimes_by_desk = collections.defaultdict(set)
for slot in all_free_slots:
datetimes_by_desk[slot.desk.id].add(slot.start_datetime)
color_label = payload.get('use_color_for')
if color_label:
color = BookingColor.objects.get_or_create(label=color_label)[0]
available_desk = None
if agenda.kind == 'virtual':
# Compute fill_rate by agenda/date
fill_rates = collections.defaultdict(dict)
for slot in all_slots:
ref_date = slot.start_datetime.date()
if ref_date not in fill_rates[slot.desk.agenda]:
date_dict = fill_rates[slot.desk.agenda][ref_date] = {'free': 0, 'full': 0}
else:
date_dict = fill_rates[slot.desk.agenda][ref_date]
if slot.full:
date_dict['full'] += 1
else:
date_dict['free'] += 1
for dd in fill_rates.values():
for date_dict in dd.values():
date_dict['fill_rate'] = date_dict['full'] / (date_dict['full'] + date_dict['free'])
# select a desk on the agenda with min fill_rate on the given date
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
desk = Desk.objects.get(id=available_desk_id)
if available_desk is None:
available_desk = desk
available_desk_rate = 0
for dt in datetimes:
available_desk_rate += fill_rates[available_desk.agenda][dt.date()]['fill_rate']
else:
if (event.booked_places + places_count) > event.places:
raise APIError(N_('sold out'))
for dt in datetimes:
desk_rate = 0
for dt in datetimes:
desk_rate += fill_rates[desk.agenda][dt.date()]['fill_rate']
if desk_rate < available_desk_rate:
available_desk = desk
available_desk_rate = desk_rate
else:
# meeting agenda
# search first desk where all requested slots are free
for available_desk_id in sorted(datetimes_by_desk.keys()):
if datetimes.issubset(datetimes_by_desk[available_desk_id]):
available_desk = Desk.objects.get(id=available_desk_id)
break
if available_desk is None:
raise APIError(N_('no more desk available'))
# all datetimes are free, book them in order
datetimes = list(datetimes)
datetimes.sort()
# get a real meeting_type for virtual agenda
if agenda.kind == 'virtual':
meeting_type = MeetingType.objects.get(agenda=available_desk.agenda, slug=meeting_type.slug)
# 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:
events.append(
Event(
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,
)
)
try:
with transaction.atomic():
@ -1744,13 +1556,16 @@ class MeetingsAgendaFillslots(APIView):
event.save()
if resources:
event.resources.add(*resources)
for dummy in range(places_count):
new_booking = make_booking(
event, payload, extra_data, primary_booking, in_waiting_list, color
)
new_booking.save()
if primary_booking is None:
primary_booking = new_booking
new_booking = make_booking(
event=event,
payload=payload,
extra_data=extra_data,
primary_booking=primary_booking,
color=color,
)
new_booking.save()
if primary_booking is None:
primary_booking = new_booking
except IntegrityError as e:
if 'tstzrange_constraint' in str(e):
# "optimistic concurrency control", between our availability
@ -1769,13 +1584,16 @@ class MeetingsAgendaFillslots(APIView):
response = {
'err': 0,
'in_waiting_list': in_waiting_list,
'booking_id': primary_booking.id,
'datetime': format_response_datetime(events[0].start_datetime),
'agenda': {
'label': primary_booking.event.agenda.label,
'slug': primary_booking.event.agenda.slug,
},
'end_datetime': format_response_datetime(events[-1].end_datetime),
'duration': (events[-1].end_datetime - events[-1].start_datetime).seconds // 60,
'resources': [r.slug for r in resources],
'desk': {'label': available_desk.label, 'slug': available_desk.slug},
'api': {
'booking_url': request.build_absolute_uri(
reverse('api-booking', kwargs={'booking_pk': primary_booking.id})
@ -1791,42 +1609,8 @@ class MeetingsAgendaFillslots(APIView):
),
},
}
if agenda.kind == 'events':
response['api']['accept_url'] = request.build_absolute_uri(
reverse('api-accept-booking', kwargs={'booking_pk': primary_booking.pk})
)
response['api']['suspend_url'] = request.build_absolute_uri(
reverse('api-suspend-booking', kwargs={'booking_pk': primary_booking.pk})
)
if agenda.accept_meetings():
response['end_datetime'] = format_response_datetime(events[-1].end_datetime)
response['duration'] = (events[-1].end_datetime - events[-1].start_datetime).seconds // 60
if available_desk:
response['desk'] = {'label': available_desk.label, 'slug': available_desk.slug}
if to_cancel_booking:
response['cancelled_booking_id'] = cancelled_booking_id
if agenda.kind == 'events' and not multiple_booking:
event = events[0]
# event.full is not up to date, it might have been changed by previous new_booking.save().
event.refresh_from_db()
response['places'] = get_event_places(event)
if event.end_datetime:
response['end_datetime'] = format_response_datetime(event.end_datetime)
else:
response['end_datetime'] = None
if agenda.kind == 'events' and multiple_booking:
response['events'] = [
{
'slug': x.slug,
'text': str(x),
'datetime': format_response_datetime(x.start_datetime),
'end_datetime': format_response_datetime(x.end_datetime) if x.end_datetime else None,
'description': x.description,
}
for x in events
]
if agenda.kind == 'meetings':
response['resources'] = [r.slug for r in resources]
return Response(response)