0002-api-exclude-slots-already-booked-by-user-events-5134.patch
chrono/agendas/models.py | ||
---|---|---|
513 | 513 |
include_full=True, |
514 | 514 |
min_start=None, |
515 | 515 |
max_start=None, |
516 |
excluded_user_external_id=None, |
|
516 | 517 |
): |
517 | 518 |
assert self.kind == 'events' |
518 | 519 | |
... | ... | |
556 | 557 |
else: |
557 | 558 |
entries = entries.filter(start_datetime__lt=max_start) |
558 | 559 | |
560 |
if excluded_user_external_id and not prefetched_queryset: |
|
561 |
entries = Event.annotate_queryset_for_user(entries, excluded_user_external_id) |
|
562 | ||
559 | 563 |
if annotate_queryset and not prefetched_queryset: |
560 | 564 |
entries = Event.annotate_queryset(entries) |
561 | 565 | |
... | ... | |
1153 | 1157 |
), |
1154 | 1158 |
) |
1155 | 1159 | |
1160 |
@staticmethod |
|
1161 |
def annotate_queryset_for_user(qs, excluded_user_external_id): |
|
1162 |
if django.VERSION < (2, 0): |
|
1163 |
return qs.annotate( |
|
1164 |
user_places_count=Count( |
|
1165 |
Case( |
|
1166 |
When( |
|
1167 |
booking__cancellation_datetime__isnull=True, |
|
1168 |
booking__in_waiting_list=False, |
|
1169 |
booking__user_external_id=excluded_user_external_id, |
|
1170 |
then='booking', |
|
1171 |
) |
|
1172 |
) |
|
1173 |
), |
|
1174 |
) |
|
1175 |
else: |
|
1176 |
return qs.annotate( |
|
1177 |
user_places_count=Count( |
|
1178 |
'booking', |
|
1179 |
filter=Q( |
|
1180 |
booking__cancellation_datetime__isnull=True, |
|
1181 |
booking__in_waiting_list=False, |
|
1182 |
booking__user_external_id=excluded_user_external_id, |
|
1183 |
), |
|
1184 |
), |
|
1185 |
) |
|
1186 | ||
1156 | 1187 |
@property |
1157 | 1188 |
def booked_places(self): |
1158 | 1189 |
if hasattr(self, 'booked_places_count'): |
chrono/api/views.py | ||
---|---|---|
378 | 378 |
'description': event.description, |
379 | 379 |
'pricing': event.pricing, |
380 | 380 |
'url': event.url, |
381 |
'disabled': bool(event.full), |
|
381 |
'disabled': bool(event.full) or getattr(event, 'user_places_count', 0) > 0,
|
|
382 | 382 |
'api': { |
383 | 383 |
'bookings_url': request.build_absolute_uri( |
384 | 384 |
reverse( |
... | ... | |
556 | 556 |
if date_end: |
557 | 557 |
date_end = make_aware(datetime.datetime.combine(parse_date(date_end), datetime.time(0, 0))) |
558 | 558 | |
559 |
entries = agenda.get_open_events(annotate_queryset=True, min_start=date_start, max_start=date_end) |
|
559 |
user_external_id = request.GET.get('exclude_user_external_id') or None |
|
560 | ||
561 |
entries = agenda.get_open_events( |
|
562 |
annotate_queryset=True, |
|
563 |
min_start=date_start, |
|
564 |
max_start=date_end, |
|
565 |
excluded_user_external_id=user_external_id, |
|
566 |
) |
|
560 | 567 | |
561 | 568 |
response = { |
562 | 569 |
'data': [get_event_detail(request, x, agenda=agenda) for x in entries], |
... | ... | |
1287 | 1294 |
'in_waiting_list': self.booking.in_waiting_list, |
1288 | 1295 |
'user_was_present': self.booking.user_was_present, |
1289 | 1296 |
'user_absence_reason': self.booking.user_absence_reason, |
1297 |
'user_external_id': self.booking.user_external_id, |
|
1290 | 1298 |
} |
1291 | 1299 |
return Response(response) |
1292 | 1300 |
tests/test_api.py | ||
---|---|---|
499 | 499 |
) |
500 | 500 | |
501 | 501 | |
502 |
@pytest.mark.freeze_time('2021-02-23') |
|
503 |
def test_datetimes_api_exclude_slots(app): |
|
504 |
agenda = Agenda.objects.create( |
|
505 |
label='Foo bar', kind='events', minimal_booking_delay=0, maximal_booking_delay=7 |
|
506 |
) |
|
507 |
event = Event.objects.create( |
|
508 |
slug='event-slug', |
|
509 |
start_datetime=localtime().replace(hour=10, minute=0), |
|
510 |
places=5, |
|
511 |
agenda=agenda, |
|
512 |
) |
|
513 |
Booking.objects.create(event=event, user_external_id='42') |
|
514 |
cancelled = Booking.objects.create(event=event, user_external_id='35') |
|
515 |
cancelled.cancel() |
|
516 | ||
517 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) |
|
518 |
assert resp.json['data'][0]['disabled'] is False |
|
519 | ||
520 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '35'}) |
|
521 |
assert resp.json['data'][0]['disabled'] is False |
|
522 | ||
523 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'}) |
|
524 |
assert resp.json['data'][0]['disabled'] is True |
|
525 | ||
526 |
event.delete() |
|
527 | ||
528 |
# recurrent event |
|
529 |
event = Event.objects.create( |
|
530 |
slug='recurrent', |
|
531 |
start_datetime=localtime().replace(hour=12, minute=0), |
|
532 |
repeat='weekly', |
|
533 |
places=2, |
|
534 |
agenda=agenda, |
|
535 |
) |
|
536 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) |
|
537 |
assert resp.json['data'][0]['id'] == 'recurrent:2021-02-23-1200' |
|
538 |
assert resp.json['data'][0]['places']['full'] is False |
|
539 |
assert resp.json['data'][0]['disabled'] is False |
|
540 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'}) |
|
541 |
assert resp.json['data'][0]['id'] == 'recurrent:2021-02-23-1200' |
|
542 |
assert resp.json['data'][0]['places']['full'] is False |
|
543 |
assert resp.json['data'][0]['disabled'] is False |
|
544 | ||
545 |
first_recurrence = event.get_or_create_event_recurrence(event.start_datetime) |
|
546 |
Booking.objects.create(event=first_recurrence, user_external_id='42') |
|
547 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) |
|
548 |
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200' |
|
549 |
assert resp.json['data'][0]['places']['full'] is False |
|
550 |
assert resp.json['data'][0]['disabled'] is False |
|
551 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'}) |
|
552 |
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200' |
|
553 |
assert resp.json['data'][0]['places']['full'] is False |
|
554 |
assert resp.json['data'][0]['disabled'] is True |
|
555 | ||
556 |
Booking.objects.create(event=first_recurrence) |
|
557 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug) |
|
558 |
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200' |
|
559 |
assert resp.json['data'][0]['places']['full'] is True |
|
560 |
assert resp.json['data'][0]['disabled'] is True |
|
561 |
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params={'exclude_user_external_id': '42'}) |
|
562 |
assert resp.json['data'][0]['id'] == 'recurrent--2021-02-23-1200' |
|
563 |
assert resp.json['data'][0]['places']['full'] is True |
|
564 |
assert resp.json['data'][0]['disabled'] is True |
|
565 | ||
566 | ||
502 | 567 |
def test_datetimes_api_meetings_agenda(app, meetings_agenda): |
503 | 568 |
meeting_type = MeetingType.objects.get(agenda=meetings_agenda) |
504 | 569 |
api_url = '/api/agenda/%s/meetings/%s/datetimes/' % (meeting_type.agenda.slug, meeting_type.slug) |
505 |
- |