api: build group_by options after filtering by agenda (#63369)
This commit is contained in:
parent
be39f78438
commit
a24e0eba3c
|
@ -22,7 +22,7 @@ import json
|
||||||
import uuid
|
import uuid
|
||||||
|
|
||||||
from django.db import IntegrityError, transaction
|
from django.db import IntegrityError, transaction
|
||||||
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Prefetch, Q
|
from django.db.models import BooleanField, Count, ExpressionWrapper, F, Func, Prefetch, Q
|
||||||
from django.db.models.expressions import RawSQL
|
from django.db.models.expressions import RawSQL
|
||||||
from django.db.models.functions import TruncDay
|
from django.db.models.functions import TruncDay
|
||||||
from django.http import Http404, HttpResponse
|
from django.http import Http404, HttpResponse
|
||||||
|
@ -3055,12 +3055,6 @@ class StatisticsList(APIView):
|
||||||
agenda_options = [{'id': '_all', 'label': pgettext('agendas', 'All')}] + [
|
agenda_options = [{'id': '_all', 'label': pgettext('agendas', 'All')}] + [
|
||||||
{'id': x.slug, 'label': x.label} for x in agendas
|
{'id': x.slug, 'label': x.label} for x in agendas
|
||||||
]
|
]
|
||||||
booking_check_filters = set()
|
|
||||||
for agenda in Agenda.objects.exclude(booking_check_filters=''):
|
|
||||||
booking_check_filters.update(agenda.get_booking_check_filters())
|
|
||||||
group_by_options = [{'id': 'user_was_present', 'label': _('Presence/Absence')}] + [
|
|
||||||
{'id': x, 'label': x.capitalize()} for x in sorted(list(booking_check_filters))
|
|
||||||
]
|
|
||||||
return Response(
|
return Response(
|
||||||
{
|
{
|
||||||
'data': [
|
'data': [
|
||||||
|
@ -3083,6 +3077,7 @@ class StatisticsList(APIView):
|
||||||
'options': category_options,
|
'options': category_options,
|
||||||
'required': True,
|
'required': True,
|
||||||
'default': '_all',
|
'default': '_all',
|
||||||
|
'has_subfilters': True,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
'id': 'agenda',
|
'id': 'agenda',
|
||||||
|
@ -3090,13 +3085,7 @@ class StatisticsList(APIView):
|
||||||
'options': agenda_options,
|
'options': agenda_options,
|
||||||
'required': True,
|
'required': True,
|
||||||
'default': '_all',
|
'default': '_all',
|
||||||
},
|
'has_subfilters': True,
|
||||||
{
|
|
||||||
'id': 'group_by',
|
|
||||||
'label': _('Group by'),
|
|
||||||
'options': group_by_options,
|
|
||||||
'required': False,
|
|
||||||
'multiple': True,
|
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
@ -3117,6 +3106,7 @@ class BookingsStatistics(APIView):
|
||||||
if not serializer.is_valid():
|
if not serializer.is_valid():
|
||||||
raise APIErrorBadRequest(N_('invalid statistics filters'), errors=serializer.errors)
|
raise APIErrorBadRequest(N_('invalid statistics filters'), errors=serializer.errors)
|
||||||
data = serializer.validated_data
|
data = serializer.validated_data
|
||||||
|
subfilters = []
|
||||||
|
|
||||||
bookings = Booking.objects.filter(cancellation_datetime__isnull=True)
|
bookings = Booking.objects.filter(cancellation_datetime__isnull=True)
|
||||||
if 'start' in data:
|
if 'start' in data:
|
||||||
|
@ -3126,9 +3116,11 @@ class BookingsStatistics(APIView):
|
||||||
|
|
||||||
if 'category' in data and data['category'] != '_all':
|
if 'category' in data and data['category'] != '_all':
|
||||||
bookings = bookings.filter(event__agenda__category__slug=data['category'])
|
bookings = bookings.filter(event__agenda__category__slug=data['category'])
|
||||||
|
subfilters = self.get_subfilters(agendas=Agenda.objects.filter(category__slug=data['category']))
|
||||||
|
|
||||||
if 'agenda' in data and data['agenda'] != '_all':
|
if 'agenda' in data and data['agenda'] != '_all':
|
||||||
bookings = bookings.filter(event__agenda__slug=data['agenda'])
|
bookings = bookings.filter(event__agenda__slug=data['agenda'])
|
||||||
|
subfilters = self.get_subfilters(agendas=Agenda.objects.filter(slug=data['agenda']))
|
||||||
|
|
||||||
bookings = bookings.annotate(day=TruncDay('event__start_datetime'))
|
bookings = bookings.annotate(day=TruncDay('event__start_datetime'))
|
||||||
|
|
||||||
|
@ -3191,10 +3183,33 @@ class BookingsStatistics(APIView):
|
||||||
'data': {
|
'data': {
|
||||||
'x_labels': [day.strftime('%Y-%m-%d') for day in days],
|
'x_labels': [day.strftime('%Y-%m-%d') for day in days],
|
||||||
'series': series,
|
'series': series,
|
||||||
|
'subfilters': subfilters,
|
||||||
},
|
},
|
||||||
'err': 0,
|
'err': 0,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_subfilters(self, agendas):
|
||||||
|
extra_data_keys = (
|
||||||
|
Booking.objects.filter(event__agenda__in=agendas)
|
||||||
|
.annotate(extra_data_keys=Func('extra_data', function='jsonb_object_keys'))
|
||||||
|
.distinct('extra_data_keys')
|
||||||
|
.order_by('extra_data_keys')
|
||||||
|
.values_list('extra_data_keys', flat=True)
|
||||||
|
)
|
||||||
|
|
||||||
|
group_by_options = [{'id': 'user_was_present', 'label': _('Presence/Absence')}] + [
|
||||||
|
{'id': x, 'label': x.capitalize()} for x in extra_data_keys
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
'id': 'group_by',
|
||||||
|
'label': _('Group by'),
|
||||||
|
'options': group_by_options,
|
||||||
|
'required': False,
|
||||||
|
'multiple': True,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
bookings_statistics = BookingsStatistics.as_view()
|
bookings_statistics = BookingsStatistics.as_view()
|
||||||
|
|
|
@ -7,8 +7,8 @@ pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
def test_statistics_list(app, user):
|
def test_statistics_list(app, user):
|
||||||
Agenda.objects.create(label='Foo bar', booking_check_filters='menu,allergies')
|
Agenda.objects.create(label='Foo bar')
|
||||||
Agenda.objects.create(label='Bar foo', booking_check_filters='menu,special')
|
Agenda.objects.create(label='Bar foo')
|
||||||
Category.objects.create(label='Category A')
|
Category.objects.create(label='Category A')
|
||||||
Category.objects.create(label='Category B')
|
Category.objects.create(label='Category B')
|
||||||
|
|
||||||
|
@ -21,13 +21,6 @@ def test_statistics_list(app, user):
|
||||||
assert len(category_filter['options']) == 3
|
assert len(category_filter['options']) == 3
|
||||||
agenda_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'agenda'][0]
|
agenda_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'agenda'][0]
|
||||||
assert len(agenda_filter['options']) == 3
|
assert len(agenda_filter['options']) == 3
|
||||||
group_by_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'group_by'][0]
|
|
||||||
assert group_by_filter['options'] == [
|
|
||||||
{'id': 'user_was_present', 'label': 'Presence/Absence'},
|
|
||||||
{'id': 'allergies', 'label': 'Allergies'},
|
|
||||||
{'id': 'menu', 'label': 'Menu'},
|
|
||||||
{'id': 'special', 'label': 'Special'},
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def test_statistics_bookings(app, user, freezer):
|
def test_statistics_bookings(app, user, freezer):
|
||||||
|
@ -51,17 +44,13 @@ def test_statistics_bookings(app, user, freezer):
|
||||||
Booking.objects.create(event=event2)
|
Booking.objects.create(event=event2)
|
||||||
|
|
||||||
resp = app.get(url + '?time_interval=day')
|
resp = app.get(url + '?time_interval=day')
|
||||||
assert resp.json['data'] == {
|
assert resp.json['data']['x_labels'] == ['2020-10-10', '2020-10-15']
|
||||||
'x_labels': ['2020-10-10', '2020-10-15'],
|
assert resp.json['data']['series'] == [{'label': 'Bookings Count', 'data': [10, 1]}]
|
||||||
'series': [{'label': 'Bookings Count', 'data': [10, 1]}],
|
|
||||||
}
|
|
||||||
|
|
||||||
# period filter
|
# period filter
|
||||||
resp = app.get(url + '?start=2020-10-14&end=2020-10-16')
|
resp = app.get(url + '?start=2020-10-14&end=2020-10-16')
|
||||||
assert resp.json['data'] == {
|
assert resp.json['data']['x_labels'] == ['2020-10-15']
|
||||||
'x_labels': ['2020-10-15'],
|
assert resp.json['data']['series'] == [{'label': 'Bookings Count', 'data': [1]}]
|
||||||
'series': [{'label': 'Bookings Count', 'data': [1]}],
|
|
||||||
}
|
|
||||||
|
|
||||||
category = Category.objects.create(label='Category A', slug='category-a')
|
category = Category.objects.create(label='Category A', slug='category-a')
|
||||||
agenda = Agenda.objects.create(label='Bar foo', kind='events', category=category)
|
agenda = Agenda.objects.create(label='Bar foo', kind='events', category=category)
|
||||||
|
@ -70,17 +59,13 @@ def test_statistics_bookings(app, user, freezer):
|
||||||
|
|
||||||
# category filter
|
# category filter
|
||||||
resp = app.get(url + '?category=category-a')
|
resp = app.get(url + '?category=category-a')
|
||||||
assert resp.json['data'] == {
|
assert resp.json['data']['x_labels'] == ['2020-10-25']
|
||||||
'x_labels': ['2020-10-25'],
|
assert resp.json['data']['series'] == [{'label': 'Bookings Count', 'data': [1]}]
|
||||||
'series': [{'label': 'Bookings Count', 'data': [1]}],
|
|
||||||
}
|
|
||||||
|
|
||||||
# agenda filter
|
# agenda filter
|
||||||
resp = app.get(url + '?agenda=bar-foo')
|
resp = app.get(url + '?agenda=bar-foo')
|
||||||
assert resp.json['data'] == {
|
assert resp.json['data']['x_labels'] == ['2020-10-25']
|
||||||
'x_labels': ['2020-10-25'],
|
assert resp.json['data']['series'] == [{'label': 'Bookings Count', 'data': [1]}]
|
||||||
'series': [{'label': 'Bookings Count', 'data': [1]}],
|
|
||||||
}
|
|
||||||
|
|
||||||
# invalid time_interval
|
# invalid time_interval
|
||||||
resp = app.get(url + '?time_interval=month', status=400)
|
resp = app.get(url + '?time_interval=month', status=400)
|
||||||
|
@ -95,19 +80,12 @@ def test_statistics_bookings(app, user, freezer):
|
||||||
Booking.objects.create(event=event4, user_was_present=True)
|
Booking.objects.create(event=event4, user_was_present=True)
|
||||||
|
|
||||||
resp = app.get(url + '?group_by=user_was_present')
|
resp = app.get(url + '?group_by=user_was_present')
|
||||||
data = resp.json['data']
|
assert resp.json['data']['x_labels'] == ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01']
|
||||||
assert data == {
|
assert resp.json['data']['series'] == [
|
||||||
'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
|
|
||||||
'series': [
|
|
||||||
{'label': 'Absent', 'data': [None, None, 5, None]},
|
{'label': 'Absent', 'data': [None, None, 5, None]},
|
||||||
{'label': 'Booked', 'data': [10, 1, 1, None]},
|
{'label': 'Booked', 'data': [10, 1, 1, None]},
|
||||||
{'label': 'Present', 'data': [None, None, 5, 1]},
|
{'label': 'Present', 'data': [None, None, 5, 1]},
|
||||||
],
|
]
|
||||||
}
|
|
||||||
|
|
||||||
# any booking check filter
|
|
||||||
agenda.booking_check_filters = 'menu'
|
|
||||||
agenda.save()
|
|
||||||
|
|
||||||
for i in range(9):
|
for i in range(9):
|
||||||
Booking.objects.create(
|
Booking.objects.create(
|
||||||
|
@ -119,21 +97,16 @@ def test_statistics_bookings(app, user, freezer):
|
||||||
)
|
)
|
||||||
|
|
||||||
resp = app.get(url + '?group_by=menu')
|
resp = app.get(url + '?group_by=menu')
|
||||||
data = resp.json['data']
|
assert resp.json['data']['x_labels'] == ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01']
|
||||||
assert data == {
|
assert resp.json['data']['series'] == [
|
||||||
'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
|
|
||||||
'series': [
|
|
||||||
{'label': 'None', 'data': [10, 1, 11, 1]},
|
{'label': 'None', 'data': [10, 1, 11, 1]},
|
||||||
{'label': 'meet', 'data': [None, None, 2, 3]},
|
{'label': 'meet', 'data': [None, None, 2, 3]},
|
||||||
{'label': 'vegetables', 'data': [None, None, 4, 5]},
|
{'label': 'vegetables', 'data': [None, None, 4, 5]},
|
||||||
],
|
]
|
||||||
}
|
|
||||||
|
|
||||||
resp = app.get(url + '?group_by=user_was_present&group_by=menu')
|
resp = app.get(url + '?group_by=user_was_present&group_by=menu')
|
||||||
data = resp.json['data']
|
assert resp.json['data']['x_labels'] == ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01']
|
||||||
assert data == {
|
assert resp.json['data']['series'] == [
|
||||||
'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
|
|
||||||
'series': [
|
|
||||||
{'label': 'Absent / None', 'data': [None, None, 5, None]},
|
{'label': 'Absent / None', 'data': [None, None, 5, None]},
|
||||||
{'label': 'Absent / meet', 'data': [None, None, 1, 1]},
|
{'label': 'Absent / meet', 'data': [None, None, 1, 1]},
|
||||||
{'label': 'Absent / vegetables', 'data': [None, None, 1, 2]},
|
{'label': 'Absent / vegetables', 'data': [None, None, 1, 2]},
|
||||||
|
@ -141,5 +114,49 @@ def test_statistics_bookings(app, user, freezer):
|
||||||
{'label': 'Present / None', 'data': [None, None, 5, 1]},
|
{'label': 'Present / None', 'data': [None, None, 5, 1]},
|
||||||
{'label': 'Present / meet', 'data': [None, None, 1, 2]},
|
{'label': 'Present / meet', 'data': [None, None, 1, 2]},
|
||||||
{'label': 'Present / vegetables', 'data': [None, None, 3, 3]},
|
{'label': 'Present / vegetables', 'data': [None, None, 3, 3]},
|
||||||
],
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def test_statistics_bookings_subfilters_list(app, user):
|
||||||
|
category = Category.objects.create(label='Category A')
|
||||||
|
agenda = Agenda.objects.create(label='Foo bar', kind='events', category=category)
|
||||||
|
|
||||||
|
app.authorization = ('Basic', ('john.doe', 'password'))
|
||||||
|
resp = app.get('/api/statistics/')
|
||||||
|
url = [x for x in resp.json['data'] if x['id'] == 'bookings_count'][0]['url']
|
||||||
|
|
||||||
|
resp = app.get(url)
|
||||||
|
assert resp.json['data']['subfilters'] == []
|
||||||
|
|
||||||
|
resp = app.get(url + '?agenda=foo-bar')
|
||||||
|
assert len(resp.json['data']['subfilters']) == 1
|
||||||
|
assert resp.json['data']['subfilters'][0]['id'] == 'group_by'
|
||||||
|
assert len(resp.json['data']['subfilters'][0]['options']) == 1
|
||||||
|
assert resp.json['data']['subfilters'][0]['options'][0] == {
|
||||||
|
'id': 'user_was_present',
|
||||||
|
'label': 'Presence/Absence',
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# extra data is shown in subfilters
|
||||||
|
event = Event.objects.create(start_datetime=now(), places=5, agenda=agenda)
|
||||||
|
Booking.objects.create(event=event, extra_data={'test': 'xxx'})
|
||||||
|
|
||||||
|
resp = app.get(url + '?agenda=foo-bar')
|
||||||
|
assert len(resp.json['data']['subfilters'][0]['options']) == 2
|
||||||
|
assert resp.json['data']['subfilters'][0]['options'][0] == {
|
||||||
|
'id': 'user_was_present',
|
||||||
|
'label': 'Presence/Absence',
|
||||||
|
}
|
||||||
|
assert resp.json['data']['subfilters'][0]['options'][1] == {'id': 'test', 'label': 'Test'}
|
||||||
|
|
||||||
|
resp = app.get(url + '?category=category-a')
|
||||||
|
assert len(resp.json['data']['subfilters'][0]['options']) == 2
|
||||||
|
|
||||||
|
Category.objects.create(label='Category B')
|
||||||
|
Agenda.objects.create(label='Other', kind='events', category=category)
|
||||||
|
|
||||||
|
resp = app.get(url + '?agenda=other-agenda')
|
||||||
|
assert len(resp.json['data']['subfilters'][0]['options']) == 1
|
||||||
|
|
||||||
|
resp = app.get(url + '?category=category-b')
|
||||||
|
assert len(resp.json['data']['subfilters'][0]['options']) == 1
|
||||||
|
|
Loading…
Reference in New Issue