api: exclude slots already booked by user - events (#51341)

This commit is contained in:
Lauréline Guérin 2021-02-25 09:18:43 +01:00
parent ccd27f7619
commit e15439bd3c
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
3 changed files with 106 additions and 4 deletions

View File

@ -513,6 +513,7 @@ class Agenda(models.Model):
include_full=True,
min_start=None,
max_start=None,
excluded_user_external_id=None,
):
assert self.kind == 'events'
@ -556,6 +557,9 @@ class Agenda(models.Model):
else:
entries = entries.filter(start_datetime__lt=max_start)
if excluded_user_external_id and not prefetched_queryset:
entries = Event.annotate_queryset_for_user(entries, excluded_user_external_id)
if annotate_queryset and not prefetched_queryset:
entries = Event.annotate_queryset(entries)
@ -1153,6 +1157,33 @@ class Event(models.Model):
),
)
@staticmethod
def annotate_queryset_for_user(qs, excluded_user_external_id):
if django.VERSION < (2, 0):
return qs.annotate(
user_places_count=Count(
Case(
When(
booking__cancellation_datetime__isnull=True,
booking__in_waiting_list=False,
booking__user_external_id=excluded_user_external_id,
then='booking',
)
)
),
)
else:
return qs.annotate(
user_places_count=Count(
'booking',
filter=Q(
booking__cancellation_datetime__isnull=True,
booking__in_waiting_list=False,
booking__user_external_id=excluded_user_external_id,
),
),
)
@property
def booked_places(self):
if hasattr(self, 'booked_places_count'):

View File

@ -352,6 +352,7 @@ def get_event_detail(request, event, agenda=None, min_places=1):
event.label,
date_format(localtime(event.start_datetime), 'DATETIME_FORMAT'),
)
full = bool(event.remaining_places < min_places and event.remaining_waiting_list_places < min_places)
return {
'id': event.slug,
'slug': event.slug, # kept for compatibility
@ -360,9 +361,7 @@ def get_event_detail(request, event, agenda=None, min_places=1):
'description': event.description,
'pricing': event.pricing,
'url': event.url,
'disabled': bool(
event.remaining_places < min_places and event.remaining_waiting_list_places < min_places
),
'disabled': full or getattr(event, 'user_places_count', 0) > 0,
'api': {
'bookings_url': request.build_absolute_uri(
reverse(
@ -565,7 +564,14 @@ class Datetimes(APIView):
http_status=status.HTTP_400_BAD_REQUEST,
)
entries = agenda.get_open_events(annotate_queryset=True, min_start=date_start, max_start=date_end)
user_external_id = request.GET.get('exclude_user_external_id') or None
entries = agenda.get_open_events(
annotate_queryset=True,
min_start=date_start,
max_start=date_end,
excluded_user_external_id=user_external_id,
)
response = {
'data': [get_event_detail(request, x, agenda=agenda, min_places=min_places) for x in entries],

View File

@ -520,6 +520,71 @@ def test_datetime_api_min_places(app):
assert resp.json['err'] == 1
@pytest.mark.freeze_time('2021-02-23')
def test_datetimes_api_exclude_slots(app):
agenda = Agenda.objects.create(
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=7
)
event = Event.objects.create(
slug='event-slug',
start_datetime=localtime().replace(hour=10, minute=0),
places=5,
agenda=agenda,
)
Booking.objects.create(event=event, user_external_id='42')
cancelled = Booking.objects.create(event=event, user_external_id='35')
cancelled.cancel()
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert resp.json['data'][0]['disabled'] is False
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '35'})
assert resp.json['data'][0]['disabled'] is False
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'})
assert resp.json['data'][0]['disabled'] is True
event.delete()
# recurrent event
event = Event.objects.create(
slug='recurrent',
start_datetime=localtime().replace(hour=12, minute=0),
repeat='weekly',
places=2,
agenda=agenda,
)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert resp.json['data'][0]['id'] == 'recurrent:2021-02-23-1200'
assert resp.json['data'][0]['places']['full'] is False
assert resp.json['data'][0]['disabled'] is False
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'})
assert resp.json['data'][0]['id'] == 'recurrent:2021-02-23-1200'
assert resp.json['data'][0]['places']['full'] is False
assert resp.json['data'][0]['disabled'] is False
first_recurrence = event.get_or_create_event_recurrence(event.start_datetime)
Booking.objects.create(event=first_recurrence, user_external_id='42')
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200'
assert resp.json['data'][0]['places']['full'] is False
assert resp.json['data'][0]['disabled'] is False
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'})
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200'
assert resp.json['data'][0]['places']['full'] is False
assert resp.json['data'][0]['disabled'] is True
Booking.objects.create(event=first_recurrence)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug)
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200'
assert resp.json['data'][0]['places']['full'] is True
assert resp.json['data'][0]['disabled'] is True
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'})
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200'
assert resp.json['data'][0]['places']['full'] is True
assert resp.json['data'][0]['disabled'] is True
def test_datetimes_api_meetings_agenda(app, meetings_agenda):
meeting_type = MeetingType.objects.get(agenda=meetings_agenda)
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug)