Projet

Général

Profil

0001-api-add-suppot-for-statistics-on-cards-62029.patch

Frédéric Péters, 03 mai 2022 21:58

Télécharger (9,73 ko)

Voir les différences:

Subject: [PATCH] api: add suppot for statistics on cards (#62029)

 tests/api/test_statistics.py | 25 +++++++++++-
 wcs/sql.py                   | 11 ++++--
 wcs/statistics/views.py      | 73 ++++++++++++++++++++++++++++++++----
 wcs/urls.py                  |  5 +++
 4 files changed, 103 insertions(+), 11 deletions(-)
tests/api/test_statistics.py
5 5

  
6 6
from wcs import fields
7 7
from wcs.blocks import BlockDef
8
from wcs.carddef import CardDef
8 9
from wcs.categories import Category
9 10
from wcs.formdef import FormDef
10 11
from wcs.qommon.http_request import HTTPRequest
......
129 130
    resp = get_app(pub).get(sign_uri('/api/statistics/'))
130 131
    form_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'form'][0]
131 132
    assert form_filter['options'] == [
132
        {'id': '_all', 'label': 'All'},
133
        {'id': '_all', 'label': 'All Forms'},
133 134
        {'id': 'test-1', 'label': 'test 1'},
134 135
        {'id': 'test-2', 'label': 'test 2'},
135 136
    ]
......
569 570
    # invalid field
570 571
    resp = get_app(pub).get(sign_uri(url + '&group-by=xxx'))
571 572
    assert resp.json['data']['series'] == [{'data': [16, 0, 4], 'label': 'Forms Count'}]
573

  
574

  
575
def test_statistics_cards_count(pub):
576
    carddef = CardDef()
577
    carddef.name = 'test 1'
578
    carddef.fields = []
579
    carddef.store()
580
    carddef.data_class().wipe()
581

  
582
    for _i in range(20):
583
        carddata = carddef.data_class()()
584
        carddata.just_created()
585
        carddata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple()
586
        carddata.store()
587

  
588
    # apply (required) card filter
589
    resp = get_app(pub).get(sign_uri('/api/statistics/cards/count/?form=%s' % carddef.url_name))
590
    assert resp.json['data']['series'] == [{'data': [20], 'label': 'Cards Count'}]
591
    assert resp.json['data']['x_labels'] == ['2021-01']
592

  
593
    resp = get_app(pub).get(sign_uri('/api/statistics/cards/count/?card=%s' % 'invalid'), status=400)
594
    assert resp.text == 'invalid form'
wcs/sql.py
3801 3801
    clause = [NotNull('receipt_time')]
3802 3802
    table_name = 'wcs_all_forms'
3803 3803
    if criterias:
3804
        from wcs.formdef import FormDef
3805

  
3806
        formdef_class = FormDef
3804 3807
        for criteria in criterias:
3808
            if criteria.__class__.__name__ == 'Equal' and criteria.attribute == 'formdef_klass':
3809
                formdef_class = criteria.value
3810
                continue
3811

  
3805 3812
            if criteria.__class__.__name__ == 'Equal' and criteria.attribute == 'formdef_id':
3806 3813
                # if there's a formdef_id specified, switch to using the
3807 3814
                # specific table so we have access to all fields
3808
                from wcs.formdef import FormDef
3809

  
3810
                table_name = get_formdef_table_name(FormDef.get(criteria.value))
3815
                table_name = get_formdef_table_name(formdef_class.get(criteria.value))
3811 3816
                continue
3812 3817
            clause.append(criteria)
3813 3818
    if period_start:
wcs/statistics/views.py
23 23

  
24 24
from wcs import sql
25 25
from wcs.api_utils import is_url_signed
26
from wcs.backoffice.data_management import CardPage
26 27
from wcs.backoffice.management import FormPage
28
from wcs.carddef import CardDef
27 29
from wcs.categories import Category
28 30
from wcs.formdef import FormDef
29 31
from wcs.qommon import _, misc
......
47 49
        category_options = [{'id': '_all', 'label': C_('categories|All')}] + [
48 50
            {'id': x.url_name, 'label': x.name} for x in categories
49 51
        ]
50
        forms = FormDef.select()
52
        forms = FormDef.select(lightweight=True)
51 53
        forms.sort(key=lambda x: misc.simplify(x.name))
52
        form_options = [{'id': '_all', 'label': _('All')}] + [
54
        form_options = [{'id': '_all', 'label': _('All Forms')}] + [
53 55
            {'id': x.url_name, 'label': x.name} for x in forms
54 56
        ]
57
        cards = CardDef.select(lightweight=True)
58
        cards.sort(key=lambda x: misc.simplify(x.name))
59
        card_options = [{'id': x.url_name, 'label': x.name} for x in cards]
55 60
        return JsonResponse(
56 61
            {
57 62
                'data': [
......
100 105
                                'has_subfilters': True,
101 106
                            },
102 107
                        ],
103
                    }
108
                    },
109
                    {
110
                        'name': _('Cards Count'),
111
                        'url': request.build_absolute_uri(reverse('api-statistics-cards-count')),
112
                        'id': 'cards_counts',
113
                        'filters': [
114
                            {
115
                                'id': 'time_interval',
116
                                'label': _('Interval'),
117
                                'options': [
118
                                    {
119
                                        'id': 'month',
120
                                        'label': _('Month'),
121
                                    },
122
                                    {
123
                                        'id': 'year',
124
                                        'label': _('Year'),
125
                                    },
126
                                    {
127
                                        'id': 'weekday',
128
                                        'label': _('Week day'),
129
                                    },
130
                                    {
131
                                        'id': 'hour',
132
                                        'label': _('Hour'),
133
                                    },
134
                                ],
135
                                'required': True,
136
                                'default': 'month',
137
                            },
138
                            {
139
                                'id': 'form',
140
                                'label': _('Card'),
141
                                'options': card_options,
142
                                'required': True,
143
                                'default': '',
144
                                'has_subfilters': True,
145
                            },
146
                        ],
147
                    },
104 148
                ]
105 149
            }
106 150
        )
107 151

  
108 152

  
109 153
class FormsCountView(RestrictedView):
154
    formdef_class = FormDef
155
    formpage_class = FormPage
156
    has_global_count_support = True
157
    label = _('Forms Count')
158

  
110 159
    def get(self, request, *args, **kwargs):
111 160
        time_interval = request.GET.get('time_interval', 'month')
112 161
        totals_kwargs = {
......
115 164
            'criterias': [],
116 165
        }
117 166
        category_slug = request.GET.get('category', '_all')
118
        formdef_slug = request.GET.get('form', '_all')
167
        formdef_slug = request.GET.get('form', '_all' if self.has_global_count_support else '_nothing')
119 168
        group_by = request.GET.get('group-by')
120 169
        subfilters = []
121 170
        if formdef_slug != '_all':
122 171
            try:
123
                formdef = FormDef.get_by_urlname(formdef_slug, ignore_migration=True)
172
                formdef = self.formdef_class.get_by_urlname(formdef_slug, ignore_migration=True)
124 173
            except KeyError:
125 174
                return HttpResponseBadRequest('invalid form')
126
            form_page = FormPage(formdef=formdef, update_breadcrumbs=False)
175
            form_page = self.formpage_class(formdef=formdef, update_breadcrumbs=False)
127 176

  
177
            # formdef_klass is a fake criteria, it will be used in time interval functions
178
            # to switch to appropriate class, it must appear before formdef_id.
179
            totals_kwargs['criterias'].append(Equal('formdef_klass', self.formdef_class))
128 180
            totals_kwargs['criterias'].append(Equal('formdef_id', formdef.id))
129 181
            totals_kwargs['criterias'].extend(self.get_filters_criterias(formdef, form_page))
130 182
            if group_by:
......
161 213

  
162 214
        if 'group_by' not in totals_kwargs:
163 215
            x_labels = [x[0] for x in totals]
164
            series = [{'label': _('Forms Count'), 'data': [x[1] for x in totals]}]
216
            series = [{'label': self.label, 'data': [x[1] for x in totals]}]
165 217
        else:
166 218
            x_labels, series = self.get_grouped_data(totals, group_by_field, formdef, form_page)
167 219

  
......
307 359
        ]
308 360
        series.sort(key=lambda x: x['label'].lower())
309 361
        return x_labels, series
362

  
363

  
364
class CardsCountView(FormsCountView):
365
    formdef_class = CardDef
366
    formpage_class = CardPage
367
    has_global_count_support = False
368
    label = _('Cards Count')
wcs/urls.py
51 51
        statistics_views.FormsCountView.as_view(),
52 52
        name='api-statistics-forms-count',
53 53
    ),
54
    url(
55
        r'^api/statistics/cards/count/$',
56
        statistics_views.CardsCountView.as_view(),
57
        name='api-statistics-cards-count',
58
    ),
54 59
    # provide django.contrib.auth view names for compatibility with
55 60
    # templates created for classic django applications.
56 61
    url(r'^login/$', compat.quixote, name='auth_login'),
57
-