Project

General

Profile

0001-api-filter-statistics-by-extra_data-55424.patch

Valentin Deniaud, 07 Jul 2021 11:49 AM

Download (8.48 KB)

View differences:

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(-)
chrono/api/views.py
2220 2220
        agenda_options = [{'id': '_all', 'label': _('All')}] + [
2221 2221
            {'id': x.slug, 'label': x.label} for x in agendas
2222 2222
        ]
2223
        booking_check_filters = set()
2224
        for agenda in Agenda.objects.exclude(booking_check_filters=''):
2225
            booking_check_filters.update(agenda.get_booking_check_filters())
2226
        group_by_options = [{'id': 'user_was_present', 'label': _('Presence/Absence')}] + [
2227
            {'id': x, 'label': x.capitalize()} for x in sorted(list(booking_check_filters))
2228
        ]
2223 2229
        return Response(
2224 2230
            {
2225 2231
                'data': [
......
2249 2255
                                'required': False,
2250 2256
                                'default': '_all',
2251 2257
                            },
2258
                            {
2259
                                'id': 'group_by',
2260
                                'label': _('Group by'),
2261
                                'options': group_by_options,
2262
                                'required': False,
2263
                            },
2252 2264
                        ],
2253 2265
                    }
2254 2266
                ]
......
2265 2277
    end = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d'])
2266 2278
    category = serializers.SlugField(required=False, allow_blank=False, max_length=256)
2267 2279
    agenda = serializers.SlugField(required=False, allow_blank=False, max_length=256)
2280
    group_by = serializers.SlugField(required=False, allow_blank=False, max_length=256)
2268 2281

  
2269 2282

  
2270 2283
class BookingsStatistics(APIView):
......
2294 2307
            bookings = bookings.filter(event__agenda__slug=data['agenda'])
2295 2308

  
2296 2309
        bookings = bookings.annotate(day=TruncDay('event__start_datetime'))
2297
        bookings = bookings.values('day', 'user_was_present').annotate(total=Count('id')).order_by('day')
2298 2310

  
2299
        bookings_by_day = collections.OrderedDict()
2300
        for booking in bookings:
2301
            totals_by_presence = bookings_by_day.setdefault(booking['day'], {})
2302
            totals_by_presence[booking['user_was_present']] = booking['total']
2303

  
2304
        bookings_by_presence = {None: [], True: [], False: []}
2305
        for bookings in bookings_by_day.values():
2306
            for presence, data in bookings_by_presence.items():
2307
                data.append(bookings.get(presence))
2308

  
2309
        labels = {None: _('Booked'), True: _('Present'), False: _('Absent')}
2310
        series = [{'label': labels[k], 'data': data} for k, data in bookings_by_presence.items() if any(data)]
2311

  
2312
        if len(series) == 1 and series[0]['label'] == _('Booked'):
2313
            series[0]['label'] = _('Bookings Count')
2311
        if 'group_by' not in data:
2312
            bookings = bookings.values('day').annotate(total=Count('id')).order_by('day')
2313
            days = [booking['day'] for booking in bookings]
2314
            if bookings:
2315
                series = [{'label': _('Bookings Count'), 'data': [booking['total'] for booking in bookings]}]
2316
            else:
2317
                series = []
2318
        else:
2319
            group_by = data['group_by']
2320
            if group_by not in ('user_was_present',):
2321
                group_by = 'extra_data__%s' % group_by
2322
            bookings = bookings.values('day', group_by).annotate(total=Count('id')).order_by('day')
2323

  
2324
            days = bookings_by_day = collections.OrderedDict(
2325
                # day1: {group1: total_11, group2: total_12},
2326
                # day2: {group1: total_21}
2327
            )
2328
            seen_group_values = set(
2329
                # group1, group2
2330
            )
2331
            for booking in bookings:
2332
                totals_by_group = bookings_by_day.setdefault(booking['day'], {})
2333
                group_value = booking[group_by]
2334
                totals_by_group[group_value] = booking['total']
2335
                seen_group_values.add(group_value)
2336

  
2337
            bookings_by_group = {
2338
                value: []
2339
                for value in seen_group_values
2340
                # group1: [total_11, total_21],
2341
                # group2: [total_12, None],
2342
            }
2343
            for bookings in bookings_by_day.values():
2344
                for group, data in bookings_by_group.items():
2345
                    data.append(bookings.get(group))
2346

  
2347
            if group_by == 'user_was_present':
2348
                labels = {None: _('Booked'), True: _('Present'), False: _('Absent')}
2349
                series = [
2350
                    {'label': labels[k], 'data': data} for k, data in bookings_by_group.items() if any(data)
2351
                ]
2352
            else:
2353
                series = [
2354
                    {'label': k or _('None'), 'data': data}
2355
                    for k, data in bookings_by_group.items()
2356
                    if any(data)
2357
                ]
2314 2358

  
2315 2359
        return Response(
2316 2360
            {
2317 2361
                'data': {
2318
                    'x_labels': [day.strftime('%Y-%m-%d') for day in bookings_by_day],
2362
                    'x_labels': [day.strftime('%Y-%m-%d') for day in days],
2319 2363
                    'series': series,
2320 2364
                },
2321 2365
                'err': 0,
tests/api/test_statistics.py
9 9

  
10 10

  
11 11
def test_statistics_list(app, user):
12
    Agenda.objects.create(label='Foo bar')
13
    Agenda.objects.create(label='Bar foo')
12
    Agenda.objects.create(label='Foo bar', booking_check_filters='menu,allergies')
13
    Agenda.objects.create(label='Bar foo', booking_check_filters='menu,special')
14 14
    Category.objects.create(label='Category A')
15 15
    Category.objects.create(label='Category B')
16 16

  
......
23 23
    assert len(category_filter['options']) == 3
24 24
    agenda_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'agenda'][0]
25 25
    assert len(agenda_filter['options']) == 3
26
    group_by_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'group_by'][0]
27
    assert group_by_filter['options'] == [
28
        {'id': 'user_was_present', 'label': 'Presence/Absence'},
29
        {'id': 'allergies', 'label': 'Allergies'},
30
        {'id': 'menu', 'label': 'Menu'},
31
        {'id': 'special', 'label': 'Special'},
32
    ]
26 33

  
27 34

  
28 35
def test_statistics_bookings(app, user, freezer):
......
88 95
    event4 = Event.objects.create(start_datetime=now().replace(month=11, day=1), places=5, agenda=agenda)
89 96
    Booking.objects.create(event=event4, user_was_present=True)
90 97

  
91
    resp = app.get(url)
92
    assert resp.json['data'] == {
98
    resp = app.get(url + '?group_by=user_was_present')
99
    data = resp.json['data']
100
    data['series'].sort(key=lambda x: x['label'])
101
    assert data == {
93 102
        'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
94 103
        'series': [
104
            {'label': 'Absent', 'data': [None, None, 5, None]},
95 105
            {'label': 'Booked', 'data': [10, 1, 1, None]},
96 106
            {'label': 'Present', 'data': [None, None, 5, 1]},
97
            {'label': 'Absent', 'data': [None, None, 5, None]},
107
        ],
108
    }
109

  
110
    # any booking check filter
111
    agenda.booking_check_filters = 'menu'
112
    agenda.save()
113

  
114
    for i in range(9):
115
        Booking.objects.create(event=event3 if i % 2 else event4, extra_data={'menu': 'vegetables'})
116
    for i in range(5):
117
        Booking.objects.create(event=event3 if i % 2 else event4, extra_data={'menu': 'meet'})
118

  
119
    resp = app.get(url + '?group_by=menu')
120
    data = resp.json['data']
121
    data['series'].sort(key=lambda x: x['label'])
122
    assert data == {
123
        'x_labels': ['2020-10-10', '2020-10-15', '2020-10-25', '2020-11-01'],
124
        'series': [
125
            {'label': 'None', 'data': [10, 1, 11, 1]},
126
            {'label': 'meet', 'data': [None, None, 2, 3]},
127
            {'label': 'vegetables', 'data': [None, None, 4, 5]},
98 128
        ],
99 129
    }
100
-