api: use date or datetimes for start/end param in datetimes (#51986)

This commit is contained in:
Lauréline Guérin 2021-03-18 15:13:10 +01:00
parent bc2e447465
commit 07f2af930b
No known key found for this signature in database
GPG Key ID: 1FAB9B9B4F93D473
2 changed files with 134 additions and 85 deletions

View File

@ -24,10 +24,10 @@ from django.db.models import Prefetch, Q
from django.http import Http404, HttpResponse
from django.shortcuts import get_object_or_404
from django.urls import reverse
from django.utils.dateparse import parse_date
from django.utils.dateparse import parse_date, parse_datetime
from django.utils.encoding import force_text
from django.utils.formats import date_format
from django.utils.timezone import now, make_aware, localtime
from django.utils.timezone import now, is_naive, make_aware, localtime
from django.utils.translation import gettext_noop
from django.utils.translation import ugettext_lazy as _
@ -486,6 +486,44 @@ def get_resources_from_request(request, agenda):
return resources
def parse_date_or_datetime(value):
try:
result = parse_datetime(value)
if result:
if is_naive(result):
return make_aware(result)
return result
result = parse_date(value)
if result:
return make_aware(datetime.datetime.combine(result, datetime.time(0, 0)))
except ValueError:
return None
return None
def get_start_and_end_datetime_from_request(request):
start_datetime, end_datetime = request.GET.get('date_start'), request.GET.get('date_end')
if start_datetime:
start_datetime = parse_date_or_datetime(start_datetime)
if not start_datetime:
raise APIError(
_('date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'),
err_class='date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM',
http_status=status.HTTP_400_BAD_REQUEST,
)
if end_datetime:
end_datetime = parse_date_or_datetime(end_datetime)
if not end_datetime:
raise APIError(
_('date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'),
err_class='date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM',
http_status=status.HTTP_400_BAD_REQUEST,
)
return start_datetime, end_datetime
class Agendas(APIView):
permission_classes = ()
@ -580,34 +618,13 @@ class Datetimes(APIView):
http_status=status.HTTP_400_BAD_REQUEST,
)
date_start, date_end = request.GET.get('date_start'), request.GET.get('date_end')
if date_start:
try:
date_start = make_aware(
datetime.datetime.combine(parse_date(date_start), datetime.time(0, 0))
)
except (TypeError, ValueError):
raise APIError(
_('date_start format must be YYYY-MM-DD'),
err_class='date_start format must be YYYY-MM-DD',
http_status=status.HTTP_400_BAD_REQUEST,
)
if date_end:
try:
date_end = make_aware(datetime.datetime.combine(parse_date(date_end), datetime.time(0, 0)))
except (TypeError, ValueError):
raise APIError(
_('date_end format must be YYYY-MM-DD'),
err_class='date_end format must be YYYY-MM-DD',
http_status=status.HTTP_400_BAD_REQUEST,
)
start_datetime, end_datetime = get_start_and_end_datetime_from_request(request)
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,
min_start=start_datetime,
max_start=end_datetime,
excluded_user_external_id=user_external_id,
)
@ -640,33 +657,7 @@ class MeetingDatetimes(APIView):
now_datetime = now()
resources = get_resources_from_request(request, agenda)
start_datetime = None
if 'date_start' in request.GET:
try:
start_datetime = make_aware(
datetime.datetime.combine(parse_date(request.GET['date_start']), datetime.time(0, 0))
)
except (TypeError, ValueError):
raise APIError(
_('date_start format must be YYYY-MM-DD'),
err_class='date_start format must be YYYY-MM-DD',
http_status=status.HTTP_400_BAD_REQUEST,
)
end_datetime = None
if 'date_end' in request.GET:
try:
end_datetime = make_aware(
datetime.datetime.combine(parse_date(request.GET['date_end']), datetime.time(0, 0))
)
except (TypeError, ValueError):
raise APIError(
_('date_end format must be YYYY-MM-DD'),
err_class='date_end format must be YYYY-MM-DD',
http_status=status.HTTP_400_BAD_REQUEST,
)
start_datetime, end_datetime = get_start_and_end_datetime_from_request(request)
user_external_id = request.GET.get('exclude_user_external_id') or None
# Generate an unique slot for each possible meeting [start_datetime,

View File

@ -4,7 +4,6 @@ import datetime
import mock
import urllib.parse as urlparse
import pytest
import sys
from django.contrib.auth import get_user_model
from django.db import connection
@ -26,7 +25,6 @@ from chrono.agendas.models import (
VirtualMember,
BookingColor,
)
import chrono.api.views
pytestmark = pytest.mark.django_db
@ -3672,11 +3670,18 @@ def test_agenda_detail_api(app):
app.get('/api/agenda/whatever/', status=404)
def test_agenda_api_date_range(app, some_data):
@pytest.mark.freeze_time('2017-05-20')
def test_agenda_api_date_range(app):
# test range limitation
agenda2 = Agenda.objects.get(slug='foo-bar-2')
base_date = agenda2.event_set.last().start_datetime.date()
base_date = base_date + datetime.timedelta(days=1)
agenda = Agenda.objects.create(label='Foo bar', kind='events', minimal_booking_delay=0)
first_datetime = localtime(now()).replace(hour=20, minute=0, second=0, microsecond=0)
first_datetime += datetime.timedelta(days=1)
for i in range(2):
event = Event.objects.create(
start_datetime=first_datetime + datetime.timedelta(days=i), places=20, agenda=agenda
)
base_datetime = agenda.event_set.last().start_datetime
base_datetime = base_datetime + datetime.timedelta(days=1)
for idx in range(7, 10):
if idx == 7:
@ -3685,62 +3690,103 @@ def test_agenda_api_date_range(app, some_data):
day_events = ['13:00', '14:00']
else:
day_events = ['8:00']
day = base_date + datetime.timedelta(days=idx)
day = base_datetime.date() + datetime.timedelta(days=idx)
for event in day_events:
event_dt = datetime.datetime.combine(day, datetime.datetime.strptime(event, '%H:%M').time())
Event.objects.create(agenda=agenda2, start_datetime=make_aware(event_dt), places=2)
Event.objects.create(agenda=agenda, start_datetime=make_aware(event_dt), places=2)
for value in ['foo', '2017-05-42']:
params = {'date_start': value}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params, status=400)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD'
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD'
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
for value in ['foo', '2017-05-42']:
params = {'date_end': value}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params, status=400)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD'
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD'
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
params = {'date_start': base_date.isoformat()}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
params = {'date_start': base_datetime.date().isoformat()}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 6
date_end = base_date + datetime.timedelta(days=7)
params = {'date_end': date_end.isoformat()}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
date_endtime = base_datetime + datetime.timedelta(days=7)
params = {'date_end': date_endtime.date().isoformat()}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['datetime'] == '2017-05-21 20:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-22 20:00:00'
params = {
'date_start': base_date + datetime.timedelta(days=8),
'date_end': base_date + datetime.timedelta(days=10),
'date_start': base_datetime.date() + datetime.timedelta(days=8),
'date_end': base_datetime.date() + datetime.timedelta(days=10),
}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-31 13:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-06-01 08:00:00'
# with minimal booking delay changed
agenda2.minimal_booking_delay = 3
agenda2.save()
agenda.minimal_booking_delay = 3
agenda.save()
params = {'date_start': '2017-05-21'}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 6
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-06-01 08:00:00'
# with maximal booking delay changed
agenda2.maximal_booking_delay = 11
agenda2.save()
agenda.maximal_booking_delay = 11
agenda.save()
params = {'date_end': '2017-06-01'}
resp = app.get('/api/agenda/%s/datetimes/' % agenda2.slug, params=params)
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00'
# with time
params = {'date_start': '2017-05-21 foo'}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
for start in ['2017-05-30 09:00', '2017-05-30 09:00:00']:
params = {'date_start': start}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00'
for start in ['2017-05-30 09:01', '2017-05-30 09:00:01', '2017-05-30 09:00:01+02:00']:
params = {'date_start': start}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['datetime'] == '2017-05-30 10:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00'
params = {'date_end': '2017-06-01 foo'}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
for end in ['2017-05-30 11:01', '2017-05-30 11:00:01']:
params = {'date_end': end}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 3
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 11:00:00'
for end in ['2017-05-30 11:00', '2017-05-30 11:00:00', '2017-05-30 11:00:00+02:00']:
params = {'date_end': end}
resp = app.get('/api/agenda/%s/datetimes/' % agenda.slug, params=params)
assert len(resp.json['data']) == 2
assert resp.json['data'][0]['datetime'] == '2017-05-30 09:00:00'
assert resp.json['data'][-1]['datetime'] == '2017-05-30 10:00:00'
def test_agenda_meeting_api_multiple_desk(app, meetings_agenda, user):
app.authorization = ('Basic', ('john.doe', 'password'))
@ -5648,15 +5694,15 @@ def test_meetings_and_virtual_datetimes_date_filter(app):
params = {'date_start': value}
resp = app.get(foo_api_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD'
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD'
assert resp.json['err_class'] == 'date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
assert resp.json['err_desc'] == 'date_start format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
for value in ['foo', '2017-05-42']:
params = {'date_end': value}
resp = app.get(foo_api_url, params=params, status=400)
assert resp.json['err'] == 1
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD'
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD'
assert resp.json['err_class'] == 'date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
assert resp.json['err_desc'] == 'date_end format must be YYYY-MM-DD or YYYY-MM-DD HH:MM'
# exclude weekday1 through date_start, 4 slots each day * 5 days
params = {'date_start': (localtime(now()) + datetime.timedelta(days=2)).date().isoformat()}
@ -5665,6 +5711,12 @@ def test_meetings_and_virtual_datetimes_date_filter(app):
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 20
params = {'date_start': (localtime(now()) + datetime.timedelta(days=2)).isoformat()}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 20
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 20
# minimal_booking_delay (which exclude weekday1 and wekkday2 ) takes precedence
# 4 slots each day * 4 days
agenda_foo.minimal_booking_delay = 3
@ -5690,6 +5742,12 @@ def test_meetings_and_virtual_datetimes_date_filter(app):
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 20
params = {'date_end': (localtime(now()) + datetime.timedelta(days=6)).replace(hour=12).isoformat()}
resp = app.get(foo_api_url, params=params)
assert len(resp.json['data']) == 24
resp = app.get(virtual_api_url, params=params)
assert len(resp.json['data']) == 24
# maximal_booking_delay (which exclude weekday5 and weekday6 ) takes precedence
# 4 slots each day * 4 days
agenda_foo.maximal_booking_delay = 5