api: allow multiple grouping in statistics (#57817)

This commit is contained in:
Valentin Deniaud 2021-10-13 11:54:24 +02:00
parent e9a85c134c
commit e7adcb1828
3 changed files with 50 additions and 21 deletions

View File

@ -154,7 +154,9 @@ class StatisticsFiltersSerializer(serializers.Serializer):
end = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d'])
category = serializers.SlugField(required=False, allow_blank=False, max_length=256)
agenda = serializers.SlugField(required=False, allow_blank=False, max_length=256)
group_by = serializers.SlugField(required=False, allow_blank=False, max_length=256)
group_by = serializers.ListField(
required=False, child=serializers.SlugField(allow_blank=False, max_length=256)
)
class DateRangeSerializer(serializers.Serializer):

View File

@ -32,7 +32,7 @@ from django.utils.dates import WEEKDAYS
from django.utils.encoding import force_text
from django.utils.formats import date_format
from django.utils.timezone import localtime, make_aware, now
from django.utils.translation import gettext_noop
from django.utils.translation import gettext, gettext_noop
from django.utils.translation import ugettext_lazy as _
from django_filters import rest_framework as filters
from rest_framework import permissions, status
@ -2478,6 +2478,7 @@ class StatisticsList(APIView):
'label': _('Group by'),
'options': group_by_options,
'required': False,
'multiple': True,
},
],
}
@ -2527,9 +2528,13 @@ class BookingsStatistics(APIView):
series = []
else:
group_by = data['group_by']
if group_by not in ('user_was_present',):
group_by = 'extra_data__%s' % group_by
bookings = bookings.values('day', group_by).annotate(total=Count('id')).order_by('day')
if not isinstance(group_by, list): # legacy support
group_by = [group_by]
lookups = [
'extra_data__%s' % field if field != 'user_was_present' else field for field in group_by
]
bookings = bookings.values('day', *lookups).annotate(total=Count('id')).order_by('day')
days = bookings_by_day = collections.OrderedDict(
# day1: {group1: total_11, group2: total_12},
@ -2540,7 +2545,7 @@ class BookingsStatistics(APIView):
)
for booking in bookings:
totals_by_group = bookings_by_day.setdefault(booking['day'], {})
group_value = booking[group_by]
group_value = tuple(booking[field] for field in lookups)
totals_by_group[group_value] = booking['total']
seen_group_values.add(group_value)
@ -2551,17 +2556,22 @@ class BookingsStatistics(APIView):
for group in seen_group_values:
bookings_by_group[group] = [bookings.get(group) for bookings in bookings_by_day.values()]
if group_by == 'user_was_present':
labels = {None: _('Booked'), True: _('Present'), False: _('Absent')}
series = [
{'label': labels[k], 'data': data} for k, data in bookings_by_group.items() if any(data)
]
else:
series = [
{'label': k or _('None'), 'data': data}
for k, data in bookings_by_group.items()
if any(data)
]
def build_label(group):
group_labels = []
for field, value in zip(group_by, group):
if field == 'user_was_present':
label = {None: gettext('Booked'), True: gettext('Present'), False: gettext('Absent')}[
value
]
else:
label = value or gettext('None')
group_labels.append(label)
return ' / '.join(group_labels)
series = [
{'label': build_label(k), 'data': data} for k, data in bookings_by_group.items() if any(data)
]
series.sort(key=lambda x: x['label'])
return Response(
{

View File

@ -95,7 +95,6 @@ def test_statistics_bookings(app, user, freezer):
resp = app.get(url + '?group_by=user_was_present')
data = resp.json['data']
data['series'].sort(key=lambda x: x['label'])
assert data == {
'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
'series': [
@ -110,13 +109,16 @@ def test_statistics_bookings(app, user, freezer):
agenda.save()
for i in range(9):
Booking.objects.create(event=event3 if i % 2 else event4, extra_data={'menu': 'vegetables'})
Booking.objects.create(
event=event3 if i % 2 else event4, extra_data={'menu': 'vegetables'}, user_was_present=bool(i % 3)
)
for i in range(5):
Booking.objects.create(event=event3 if i % 2 else event4, extra_data={'menu': 'meet'})
Booking.objects.create(
event=event3 if i % 2 else event4, extra_data={'menu': 'meet'}, user_was_present=bool(i % 3)
)
resp = app.get(url + '?group_by=menu')
data = resp.json['data']
data['series'].sort(key=lambda x: x['label'])
assert data == {
'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
'series': [
@ -125,3 +127,18 @@ def test_statistics_bookings(app, user, freezer):
{'label': 'vegetables', 'data': [None, None, 4, 5]},
],
}
resp = app.get(url + '?group_by=user_was_present&group_by=menu')
data = resp.json['data']
assert data == {
'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 / meet', 'data': [None, None, 1, 1]},
{'label': 'Absent / vegetables', 'data': [None, None, 1, 2]},
{'label': 'Booked / None', 'data': [10, 1, 1, None]},
{'label': 'Present / None', 'data': [None, None, 5, 1]},
{'label': 'Present / meet', 'data': [None, None, 1, 2]},
{'label': 'Present / vegetables', 'data': [None, None, 3, 3]},
],
}