api: allow multiple grouping in statistics (#57817)
This commit is contained in:
parent
e9a85c134c
commit
e7adcb1828
|
@ -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):
|
||||
|
|
|
@ -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(
|
||||
{
|
||||
|
|
|
@ -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]},
|
||||
],
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue