diff --git a/chrono/api/serializers.py b/chrono/api/serializers.py index 641dcfee..4563c04d 100644 --- a/chrono/api/serializers.py +++ b/chrono/api/serializers.py @@ -293,7 +293,7 @@ class StatisticsFiltersSerializer(serializers.Serializer): start = serializers.DateTimeField(required=False, input_formats=['iso-8601', '%Y-%m-%d']) 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) + agenda = serializers.CharField(required=False, allow_blank=False, max_length=256) group_by = serializers.ListField( required=False, child=serializers.SlugField(allow_blank=False, max_length=256) ) diff --git a/chrono/api/views.py b/chrono/api/views.py index b2178644..a5edb4ab 100644 --- a/chrono/api/views.py +++ b/chrono/api/views.py @@ -3051,10 +3051,6 @@ class StatisticsList(APIView): category_options = [{'id': '_all', 'label': pgettext('categories', 'All')}] + [ {'id': x.slug, 'label': x.label} for x in categories ] - agendas = Agenda.objects.all() - agenda_options = [{'id': '_all', 'label': pgettext('agendas', 'All')}] + [ - {'id': x.slug, 'label': x.label} for x in agendas - ] return Response( { 'data': [ @@ -3078,11 +3074,15 @@ class StatisticsList(APIView): 'required': True, 'default': '_all', 'has_subfilters': True, + 'deprecated': True, + 'deprecation_hint': _( + 'Category should now be selected using the Agenda field below.' + ), }, { 'id': 'agenda', 'label': _('Agenda'), - 'options': agenda_options, + 'options': self.get_agenda_options(), 'required': True, 'default': '_all', 'has_subfilters': True, @@ -3093,6 +3093,34 @@ class StatisticsList(APIView): } ) + @staticmethod + def get_agenda_options(): + all_agendas_option = [{'id': '_all', 'label': pgettext('agendas', 'All')}] + + agendas = Agenda.objects.all().order_by('category__name') + agendas_with_category = [x for x in agendas if x.category] + if not agendas_with_category: + return all_agendas_option + [{'id': x.slug, 'label': x.label} for x in agendas] + + agenda_options = {None: all_agendas_option} + for agenda in agendas_with_category: + if agenda.category.label not in agenda_options: + agenda_options[agenda.category.label] = [ + { + 'id': 'category:' + agenda.category.slug, + 'label': _('All agendas of category %s') % agenda.category.label, + } + ] + agenda_options[agenda.category.label].append({'id': agenda.slug, 'label': agenda.label}) + + agendas_without_category_options = [ + {'id': x.slug, 'label': x.label} for x in agendas if not x.category + ] + if agendas_without_category_options: + agenda_options[_('Misc')] = agendas_without_category_options + + return list(agenda_options.items()) + statistics_list = StatisticsList.as_view() @@ -3114,13 +3142,17 @@ class BookingsStatistics(APIView): if 'end' in data: bookings = bookings.filter(event__start_datetime__lte=data['end']) - if 'category' in data and data['category'] != '_all': - bookings = bookings.filter(event__agenda__category__slug=data['category']) - subfilters = self.get_subfilters(agendas=Agenda.objects.filter(category__slug=data['category'])) + agenda_slug = data.get('agenda', '_all') + category_slug = data.get('category', '_all') + if agenda_slug.startswith('category:'): + category_slug = agenda_slug.split(':', 1)[1] - if 'agenda' in data and data['agenda'] != '_all': - bookings = bookings.filter(event__agenda__slug=data['agenda']) - subfilters = self.get_subfilters(agendas=Agenda.objects.filter(slug=data['agenda'])) + if category_slug != '_all': + bookings = bookings.filter(event__agenda__category__slug=category_slug) + subfilters = self.get_subfilters(agendas=Agenda.objects.filter(category__slug=category_slug)) + elif agenda_slug != '_all': + bookings = bookings.filter(event__agenda__slug=agenda_slug) + subfilters = self.get_subfilters(agendas=Agenda.objects.filter(slug=agenda_slug)) bookings = bookings.annotate(day=TruncDay('event__start_datetime')) diff --git a/tests/api/test_statistics.py b/tests/api/test_statistics.py index 83a4612c..9a7d726c 100644 --- a/tests/api/test_statistics.py +++ b/tests/api/test_statistics.py @@ -7,10 +7,10 @@ pytestmark = pytest.mark.django_db def test_statistics_list(app, user): - Agenda.objects.create(label='Foo bar') - Agenda.objects.create(label='Bar foo') - Category.objects.create(label='Category A') - Category.objects.create(label='Category B') + agenda = Agenda.objects.create(label='Foo bar') + agenda2 = Agenda.objects.create(label='Bar foo') + category = Category.objects.create(label='Category A') + category2 = Category.objects.create(label='Category B') # unauthorized app.get('/api/statistics/', status=401) @@ -20,7 +20,51 @@ def test_statistics_list(app, user): category_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'category'][0] 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 + assert agenda_filter['options'] == [ + {'id': '_all', 'label': 'All'}, + {'id': 'bar-foo', 'label': 'Bar foo'}, + {'id': 'foo-bar', 'label': 'Foo bar'}, + ] + + agenda.category = category + agenda.save() + + resp = app.get('/api/statistics/') + agenda_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'agenda'][0] + assert agenda_filter['options'] == [ + [None, [{'id': '_all', 'label': 'All'}]], + [ + 'Category A', + [ + {'id': 'category:category-a', 'label': 'All agendas of category Category A'}, + {'id': 'foo-bar', 'label': 'Foo bar'}, + ], + ], + ['Misc', [{'id': 'bar-foo', 'label': 'Bar foo'}]], + ] + + agenda2.category = category2 + agenda2.save() + + resp = app.get('/api/statistics/') + agenda_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'agenda'][0] + assert agenda_filter['options'] == [ + [None, [{'id': '_all', 'label': 'All'}]], + [ + 'Category A', + [ + {'id': 'category:category-a', 'label': 'All agendas of category Category A'}, + {'id': 'foo-bar', 'label': 'Foo bar'}, + ], + ], + [ + 'Category B', + [ + {'id': 'category:category-b', 'label': 'All agendas of category Category B'}, + {'id': 'bar-foo', 'label': 'Bar foo'}, + ], + ], + ] def test_statistics_bookings(app, user, freezer): @@ -58,6 +102,11 @@ def test_statistics_bookings(app, user, freezer): Booking.objects.create(event=event3) # category filter + resp = app.get(url + '?agenda=category:category-a') + assert resp.json['data']['x_labels'] == ['2020-10-25'] + assert resp.json['data']['series'] == [{'label': 'Bookings Count', 'data': [1]}] + + # legacy, category filter resp = app.get(url + '?category=category-a') assert resp.json['data']['x_labels'] == ['2020-10-25'] assert resp.json['data']['series'] == [{'label': 'Bookings Count', 'data': [1]}]