From 3d70b1090c923d6d718846d337d412249ba094bf Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 7 Jul 2021 11:30:56 +0200 Subject: [PATCH] api: filter statistics by extra_data (#55424) --- chrono/api/views.py | 78 ++++++++++++++++++++++++++++-------- tests/api/test_statistics.py | 40 +++++++++++++++--- 2 files changed, 96 insertions(+), 22 deletions(-) diff --git a/chrono/api/views.py b/chrono/api/views.py index ef4541c..95ad7ff 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -2220,6 +2220,12 @@ class StatisticsList(APIView): agenda_options = [{'id': '_all', 'label': _('All')}] + [ {'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( { 'data': [ @@ -2249,6 +2255,12 @@ class StatisticsList(APIView): 'required': False, 'default': '_all', }, + { + 'id': 'group_by', + 'label': _('Group by'), + 'options': group_by_options, + 'required': False, + }, ], } ] @@ -2265,6 +2277,7 @@ 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) class BookingsStatistics(APIView): @@ -2294,28 +2307,59 @@ class BookingsStatistics(APIView): bookings = bookings.filter(event__agenda__slug=data['agenda']) bookings = bookings.annotate(day=TruncDay('event__start_datetime')) - bookings = bookings.values('day', 'user_was_present').annotate(total=Count('id')).order_by('day') - bookings_by_day = collections.OrderedDict() - for booking in bookings: - totals_by_presence = bookings_by_day.setdefault(booking['day'], {}) - totals_by_presence[booking['user_was_present']] = booking['total'] - - bookings_by_presence = {None: [], True: [], False: []} - for bookings in bookings_by_day.values(): - for presence, data in bookings_by_presence.items(): - data.append(bookings.get(presence)) - - labels = {None: _('Booked'), True: _('Present'), False: _('Absent')} - series = [{'label': labels[k], 'data': data} for k, data in bookings_by_presence.items() if any(data)] - - if len(series) == 1 and series[0]['label'] == _('Booked'): - series[0]['label'] = _('Bookings Count') + if 'group_by' not in data: + bookings = bookings.values('day').annotate(total=Count('id')).order_by('day') + days = [booking['day'] for booking in bookings] + if bookings: + series = [{'label': _('Bookings Count'), 'data': [booking['total'] for booking in bookings]}] + else: + 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') + + days = bookings_by_day = collections.OrderedDict( + # day1: {group1: total_11, group2: total_12}, + # day2: {group1: total_21} + ) + seen_group_values = set( + # group1, group2 + ) + for booking in bookings: + totals_by_group = bookings_by_day.setdefault(booking['day'], {}) + group_value = booking[group_by] + totals_by_group[group_value] = booking['total'] + seen_group_values.add(group_value) + + bookings_by_group = { + value: [] + for value in seen_group_values + # group1: [total_11, total_21], + # group2: [total_12, None], + } + for bookings in bookings_by_day.values(): + for group, data in bookings_by_group.items(): + data.append(bookings.get(group)) + + 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) + ] return Response( { 'data': { - 'x_labels': [day.strftime('%Y-%m-%d') for day in bookings_by_day], + 'x_labels': [day.strftime('%Y-%m-%d') for day in days], 'series': series, }, 'err': 0, diff --git a/tests/api/test_statistics.py b/tests/api/test_statistics.py index 954545a..38a99c4 100644 --- a/tests/api/test_statistics.py +++ b/tests/api/test_statistics.py @@ -9,8 +9,8 @@ pytestmark = pytest.mark.django_db def test_statistics_list(app, user): - Agenda.objects.create(label='Foo bar') - Agenda.objects.create(label='Bar foo') + Agenda.objects.create(label='Foo bar', booking_check_filters='menu,allergies') + Agenda.objects.create(label='Bar foo', booking_check_filters='menu,special') Category.objects.create(label='Category A') Category.objects.create(label='Category B') @@ -23,6 +23,13 @@ def test_statistics_list(app, user): assert len(category_filter['options']) == 3 agenda_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'agenda'][0] 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): @@ -88,12 +95,35 @@ def test_statistics_bookings(app, user, freezer): event4 = Event.objects.create(start_datetime=now().replace(month=11, day=1), places=5, agenda=agenda) Booking.objects.create(event=event4, user_was_present=True) - resp = app.get(url) - assert resp.json['data'] == { + 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': [ + {'label': 'Absent', 'data': [None, None, 5, None]}, {'label': 'Booked', 'data': [10, 1, 1, None]}, {'label': 'Present', 'data': [None, None, 5, 1]}, - {'label': 'Absent', 'data': [None, None, 5, None]}, + ], + } + + # any booking check filter + agenda.booking_check_filters = 'menu' + agenda.save() + + for i in range(9): + Booking.objects.create(event=event3 if i % 2 else event4, extra_data={'menu': 'vegetables'}) + for i in range(5): + Booking.objects.create(event=event3 if i % 2 else event4, extra_data={'menu': 'meet'}) + + 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': [ + {'label': 'None', 'data': [10, 1, 11, 1]}, + {'label': 'meet', 'data': [None, None, 2, 3]}, + {'label': 'vegetables', 'data': [None, None, 4, 5]}, ], } -- 2.20.1