0001-statistics-filter-forms-count-by-fields-60777.patch
tests/admin_pages/test_form.py | ||
---|---|---|
3144 | 3144 |
] |
3145 | 3145 |
resp = resp.click('edit page fields', index=0) |
3146 | 3146 |
assert '<h2>form title - page 1 - first page</h2>' in resp.text |
3147 | ||
3148 | ||
3149 |
def test_field_display_locations_statistics_choice(pub): |
|
3150 |
create_superuser(pub) |
|
3151 |
create_role(pub) |
|
3152 | ||
3153 |
FormDef.wipe() |
|
3154 |
formdef = FormDef() |
|
3155 |
formdef.name = 'form title' |
|
3156 |
formdef.fields = [ |
|
3157 |
fields.StringField(id='0', label='String field', varname='var_1'), |
|
3158 |
fields.ItemField(id='1', label='Item field', type='item'), |
|
3159 |
fields.ItemsField(id='2', label='Items field', type='items'), |
|
3160 |
fields.BoolField(id='3', label='Bool field', type='bool'), |
|
3161 |
] |
|
3162 |
formdef.store() |
|
3163 |
formdef.data_class().wipe() |
|
3164 | ||
3165 |
app = login(get_app(pub)) |
|
3166 |
resp = app.get('/backoffice/forms/%s/fields/0/' % formdef.id) |
|
3167 |
assert 'Statistics' not in resp.text |
|
3168 | ||
3169 |
for i in range(1, 4): |
|
3170 |
resp = app.get('/backoffice/forms/%s/fields/%s/' % (formdef.id, i)) |
|
3171 |
assert 'Statistics' in resp.text |
|
3172 | ||
3173 |
resp.form['display_locations$element3'] = True |
|
3174 |
resp = resp.form.submit('submit') |
|
3175 |
assert 'Field must have a varname in order to be displayed in statistics.' in resp.text |
|
3176 |
assert 'statistics' not in FormDef.get(formdef.id).fields[i].display_locations |
|
3177 | ||
3178 |
resp.form['varname'] = 'var_%s' % i |
|
3179 |
resp = resp.form.submit('submit') |
|
3180 |
assert 'statistics' in FormDef.get(formdef.id).fields[i].display_locations |
tests/api/test_statistics.py | ||
---|---|---|
3 | 3 | |
4 | 4 |
import pytest |
5 | 5 | |
6 |
from wcs import fields |
|
7 |
from wcs.blocks import BlockDef |
|
6 | 8 |
from wcs.categories import Category |
7 | 9 |
from wcs.formdef import FormDef |
8 | 10 |
from wcs.qommon.http_request import HTTPRequest |
11 |
from wcs.wf.jump import JumpWorkflowStatusItem |
|
12 |
from wcs.workflows import Workflow, WorkflowBackofficeFieldsFormDef |
|
9 | 13 | |
10 | 14 |
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app |
11 | 15 |
from .utils import sign_uri |
... | ... | |
14 | 18 |
@pytest.fixture |
15 | 19 |
def pub(): |
16 | 20 |
pub = create_temporary_pub(sql_mode=True) |
21 |
BlockDef.wipe() |
|
17 | 22 |
Category.wipe() |
18 | 23 |
FormDef.wipe() |
24 |
Workflow.wipe() |
|
19 | 25 | |
20 | 26 |
req = HTTPRequest(None, {'SCRIPT_NAME': '/', 'SERVER_NAME': 'example.net'}) |
21 | 27 |
pub.set_app_dir(req) |
... | ... | |
34 | 40 |
return pub |
35 | 41 | |
36 | 42 | |
43 |
@pytest.fixture |
|
44 |
def formdef(pub): |
|
45 |
workflow = Workflow(name='Workflow One') |
|
46 |
new_status = workflow.add_status(name='New status') |
|
47 |
workflow.add_status(name='End status') |
|
48 |
jump = JumpWorkflowStatusItem() |
|
49 |
jump.id = '_jump' |
|
50 |
jump.status = '2' |
|
51 |
jump.timeout = 86400 |
|
52 |
new_status.items.append(jump) |
|
53 |
jump.parent = new_status |
|
54 |
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow) |
|
55 |
workflow.backoffice_fields_formdef.fields = [ |
|
56 |
fields.BoolField( |
|
57 |
id='1', varname='checkbox', label='Checkbox', type='bool', display_locations=['statistics'] |
|
58 |
), |
|
59 |
] |
|
60 |
workflow.store() |
|
61 | ||
62 |
block = BlockDef() |
|
63 |
block.name = 'foobar' |
|
64 |
block.fields = [ |
|
65 |
fields.BoolField(id='1', label='Bool', type='bool', varname='bool', display_locations=['statistics']) |
|
66 |
] |
|
67 |
block.store() |
|
68 | ||
69 |
formdef = FormDef() |
|
70 |
formdef.name = 'test' |
|
71 |
formdef.workflow_id = workflow.id |
|
72 |
item_field = fields.ItemField( |
|
73 |
id='2', varname='test-item', label='Test item', type='item', items=['foo', 'bar', 'baz'] |
|
74 |
) |
|
75 |
item_field.display_locations = ['statistics'] |
|
76 |
items_field = fields.ItemsField( |
|
77 |
id='3', varname='test-items', label='Test items', type='items', items=['foo', 'bar', 'baz'] |
|
78 |
) |
|
79 |
items_field.display_locations = ['statistics'] |
|
80 |
block_field = fields.BlockField(id='4', label='Block Data', varname='blockdata', type='block:foobar') |
|
81 |
formdef.fields = [item_field, items_field, block_field] |
|
82 |
formdef.store() |
|
83 |
formdef.data_class().wipe() |
|
84 |
return formdef |
|
85 | ||
86 | ||
37 | 87 |
def teardown_module(module): |
38 | 88 |
clean_temporary_pub() |
39 | 89 | |
... | ... | |
58 | 108 |
assert len(category_filter['options']) == 3 |
59 | 109 | |
60 | 110 | |
111 |
def test_statistics_index_forms(pub): |
|
112 |
formdef = FormDef() |
|
113 |
formdef.name = 'test 1' |
|
114 |
formdef.fields = [] |
|
115 |
formdef.store() |
|
116 |
formdef.data_class().wipe() |
|
117 | ||
118 |
formdef2 = FormDef() |
|
119 |
formdef2.name = 'test 2' |
|
120 |
formdef2.fields = [] |
|
121 |
formdef2.store() |
|
122 |
formdef2.data_class().wipe() |
|
123 | ||
124 |
resp = get_app(pub).get(sign_uri('/api/statistics/')) |
|
125 |
form_filter = [x for x in resp.json['data'][0]['filters'] if x['id'] == 'form'][0] |
|
126 |
assert form_filter['options'] == [ |
|
127 |
{'id': '_all', 'label': 'All'}, |
|
128 |
{'id': 'test-1', 'label': 'test 1'}, |
|
129 |
{'id': 'test-2', 'label': 'test 2'}, |
|
130 |
] |
|
131 | ||
132 | ||
61 | 133 |
def test_statistics_forms_count(pub): |
62 | 134 |
category_a = Category(name='Category A') |
63 | 135 |
category_a.store() |
... | ... | |
95 | 167 |
'data': { |
96 | 168 |
'series': [{'data': [20, 0, 30], 'label': 'Forms Count'}], |
97 | 169 |
'x_labels': ['2021-01', '2021-02', '2021-03'], |
170 |
'subfilters': [], |
|
98 | 171 |
}, |
99 | 172 |
'err': 0, |
100 | 173 |
} |
... | ... | |
104 | 177 |
'data': { |
105 | 178 |
'series': [{'data': [50], 'label': 'Forms Count'}], |
106 | 179 |
'x_labels': ['2021'], |
180 |
'subfilters': [], |
|
107 | 181 |
}, |
108 | 182 |
'err': 0, |
109 | 183 |
} |
... | ... | |
113 | 187 |
'data': { |
114 | 188 |
'series': [{'data': [30, 0, 0, 0, 20, 0, 0], 'label': 'Forms Count'}], |
115 | 189 |
'x_labels': ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'], |
190 |
'subfilters': [], |
|
116 | 191 |
}, |
117 | 192 |
'err': 0, |
118 | 193 |
} |
... | ... | |
127 | 202 |
} |
128 | 203 |
], |
129 | 204 |
'x_labels': list(range(24)), |
205 |
'subfilters': [], |
|
130 | 206 |
}, |
131 | 207 |
'err': 0, |
132 | 208 |
} |
... | ... | |
140 | 216 |
'data': { |
141 | 217 |
'series': [{'data': [20], 'label': 'Forms Count'}], |
142 | 218 |
'x_labels': ['2021-01'], |
219 |
'subfilters': [], |
|
143 | 220 |
}, |
144 | 221 |
'err': 0, |
145 | 222 |
} |
146 | 223 | |
224 |
# apply form filter |
|
225 |
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
226 |
assert resp.json['data']['series'] == [{'data': [20], 'label': 'Forms Count'}] |
|
227 |
assert resp.json['data']['x_labels'] == ['2021-01'] |
|
228 | ||
229 |
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % 'invalid'), status=400) |
|
230 |
assert resp.text == 'invalid form' |
|
231 | ||
147 | 232 |
# apply period filter |
148 | 233 |
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?end=2021-02-01')) |
149 | 234 |
assert resp.json == { |
150 | 235 |
'data': { |
151 | 236 |
'series': [{'data': [20], 'label': 'Forms Count'}], |
152 | 237 |
'x_labels': ['2021-01'], |
238 |
'subfilters': [], |
|
153 | 239 |
}, |
154 | 240 |
'err': 0, |
155 | 241 |
} |
242 | ||
243 | ||
244 |
def test_statistics_forms_count_subfilters(pub, formdef): |
|
245 |
for i in range(2): |
|
246 |
formdata = formdef.data_class()() |
|
247 |
formdata.data['2'] = 'foo' if i % 2 else 'baz' |
|
248 |
formdata.data['2_display'] = 'Foo' if i % 2 else 'Baz' |
|
249 |
formdata.data['3'] = ['foo'] if i % 2 else ['bar', 'baz'] |
|
250 |
formdata.data['3_display'] = 'Foo' if i % 2 else 'Bar, Baz' |
|
251 |
formdata.just_created() |
|
252 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
253 |
formdata.store() |
|
254 | ||
255 |
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
256 | ||
257 |
# check item field subfilter |
|
258 |
assert resp.json['data']['subfilters'][0] == { |
|
259 |
'id': 'filter-test-item', |
|
260 |
'label': 'Test item', |
|
261 |
'options': [{'id': 'baz', 'label': 'Baz'}, {'id': 'foo', 'label': 'Foo'}], |
|
262 |
'required': False, |
|
263 |
} |
|
264 | ||
265 |
# check items field subfilter |
|
266 |
assert resp.json['data']['subfilters'][1] == { |
|
267 |
'id': 'filter-test-items', |
|
268 |
'label': 'Test items', |
|
269 |
'options': [ |
|
270 |
{'id': 'bar', 'label': 'Bar'}, |
|
271 |
{'id': 'baz', 'label': 'Baz'}, |
|
272 |
{'id': 'foo', 'label': 'Foo'}, |
|
273 |
], |
|
274 |
'required': False, |
|
275 |
} |
|
276 | ||
277 |
# check block boolean field subfilter |
|
278 |
assert resp.json['data']['subfilters'][2] == { |
|
279 |
'id': 'filter-blockdata_bool', |
|
280 |
'label': 'Bool', |
|
281 |
'options': [{'id': 'true', 'label': 'Yes'}, {'id': 'false', 'label': 'No'}], |
|
282 |
'required': False, |
|
283 |
} |
|
284 | ||
285 |
# check boolean backoffice field subfilter |
|
286 |
assert resp.json['data']['subfilters'][3] == { |
|
287 |
'id': 'filter-checkbox', |
|
288 |
'label': 'Checkbox', |
|
289 |
'options': [{'id': 'true', 'label': 'Yes'}, {'id': 'false', 'label': 'No'}], |
|
290 |
'required': False, |
|
291 |
} |
|
292 | ||
293 |
# check status subfilter |
|
294 |
assert resp.json['data']['subfilters'][-1] == { |
|
295 |
'default': '_all', |
|
296 |
'id': 'filter-status', |
|
297 |
'label': 'Status', |
|
298 |
'options': [ |
|
299 |
{'id': '_all', 'label': 'All'}, |
|
300 |
{'id': 'pending', 'label': 'Open'}, |
|
301 |
{'id': 'done', 'label': 'Done'}, |
|
302 |
{'id': '1', 'label': 'New status'}, |
|
303 |
{'id': '2', 'label': 'End status'}, |
|
304 |
], |
|
305 |
'required': True, |
|
306 |
} |
|
307 | ||
308 |
# add item field with no formdata, it should not appear |
|
309 |
item_field = fields.ItemField( |
|
310 |
id='20', |
|
311 |
varname='test-item-no-formdata', |
|
312 |
label='Test item no formdata', |
|
313 |
type='item', |
|
314 |
items=['foo', 'bar', 'baz'], |
|
315 |
display_locations=['statistics'], |
|
316 |
) |
|
317 |
formdef.fields.append(item_field) |
|
318 |
formdef.store() |
|
319 |
new_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
320 |
assert new_resp.json == resp.json |
|
321 | ||
322 |
# add boolean field with no varname, it should not appear |
|
323 |
bool_field = fields.BoolField(id='21', label='Checkbox', type='bool', display_locations=['statistics']) |
|
324 |
formdef.fields.append(bool_field) |
|
325 |
formdef.store() |
|
326 |
new_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
327 |
assert new_resp.json == resp.json |
|
328 | ||
329 |
# add boolean field with no display location, it should not appear |
|
330 |
bool_field = fields.BoolField( |
|
331 |
id='22', varname='checkbox', label='Checkbox', type='bool', display_locations=['validation'] |
|
332 |
) |
|
333 |
formdef.fields.append(bool_field) |
|
334 |
formdef.store() |
|
335 |
new_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
336 |
assert new_resp.json == resp.json |
|
337 | ||
338 |
# add not filterable field, it should not appear |
|
339 |
formdef.fields.append(fields.StringField(id='23', varname='test string', label='Test', type='string')) |
|
340 |
formdef.store() |
|
341 |
new_resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
342 |
assert new_resp.json == resp.json |
|
343 | ||
344 |
# remove fields and statuses |
|
345 |
workflow = Workflow(name='Empty wf') |
|
346 |
workflow.store() |
|
347 |
formdef.workflow = workflow |
|
348 |
formdef.fields.clear() |
|
349 |
formdef.store() |
|
350 |
formdef.data_class().wipe() |
|
351 | ||
352 |
resp = get_app(pub).get(sign_uri('/api/statistics/forms/count/?form=%s' % formdef.url_name)) |
|
353 |
assert resp.json['data'] == { |
|
354 |
'series': [{'data': [], 'label': 'Forms Count'}], |
|
355 |
'subfilters': [], |
|
356 |
'x_labels': [], |
|
357 |
} |
|
358 | ||
359 | ||
360 |
def test_statistics_forms_count_subfilters_query(pub, formdef): |
|
361 |
for i in range(20): |
|
362 |
formdata = formdef.data_class()() |
|
363 |
formdata.just_created() |
|
364 |
if i % 3: |
|
365 |
formdata.data['1'] = True |
|
366 |
formdata.data['2'] = 'foo' |
|
367 |
formdata.data['3'] = ['bar', 'baz'] |
|
368 |
formdata.data['4'] = {'data': [{'1': True}]} |
|
369 |
elif i % 2: |
|
370 |
formdata.data['1'] = False |
|
371 |
formdata.data['2'] = 'baz' |
|
372 |
formdata.data['3'] = ['baz'] |
|
373 |
formdata.data['4'] = {'data': [{'1': False}]} |
|
374 |
formdata.jump_status('2') |
|
375 |
formdata.receipt_time = datetime.datetime(2021, 1, 1, 0, 0).timetuple() |
|
376 |
formdata.store() |
|
377 | ||
378 |
# query all formdata |
|
379 |
url = '/api/statistics/forms/count/?form=%s' % formdef.url_name |
|
380 |
resp = get_app(pub).get(sign_uri(url)) |
|
381 |
assert resp.json['data']['series'][0]['data'][0] == 20 |
|
382 | ||
383 |
# filter on boolean field |
|
384 |
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=true')) |
|
385 |
assert resp.json['data']['series'][0]['data'][0] == 13 |
|
386 | ||
387 |
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=false')) |
|
388 |
assert resp.json['data']['series'][0]['data'][0] == 3 |
|
389 | ||
390 |
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=')) |
|
391 |
assert resp.json['data']['series'][0]['data'][0] == 20 |
|
392 | ||
393 |
resp = get_app(pub).get(sign_uri(url + '&filter-checkbox=xxx'), status=400) |
|
394 |
assert resp.text == 'Invalid value "xxx" for "filter-checkbox"' |
|
395 | ||
396 |
# filter on item field |
|
397 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=foo')) |
|
398 |
assert resp.json['data']['series'][0]['data'][0] == 13 |
|
399 | ||
400 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=baz')) |
|
401 |
assert resp.json['data']['series'][0]['data'][0] == 3 |
|
402 | ||
403 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=bar')) |
|
404 |
assert resp.json['data']['series'][0]['data'] == [] |
|
405 | ||
406 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=')) |
|
407 |
assert resp.json['data']['series'][0]['data'][0] == 20 |
|
408 | ||
409 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-item=xxx')) |
|
410 |
assert resp.json['data']['series'][0]['data'] == [] |
|
411 | ||
412 |
# filter on items field |
|
413 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=foo')) |
|
414 |
assert resp.json['data']['series'][0]['data'] == [] |
|
415 | ||
416 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=bar')) |
|
417 |
assert resp.json['data']['series'][0]['data'][0] == 13 |
|
418 | ||
419 |
resp = get_app(pub).get(sign_uri(url + '&filter-test-items=baz')) |
|
420 |
assert resp.json['data']['series'][0]['data'][0] == 16 |
|
421 | ||
422 |
# filter on block boolean field |
|
423 |
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_bool=true')) |
|
424 |
assert resp.json['data']['series'][0]['data'][0] == 13 |
|
425 | ||
426 |
resp = get_app(pub).get(sign_uri(url + '&filter-blockdata_bool=false')) |
|
427 |
assert resp.json['data']['series'][0]['data'][0] == 3 |
|
428 | ||
429 |
# filter on status |
|
430 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=_all')) |
|
431 |
assert resp.json['data']['series'][0]['data'][0] == 20 |
|
432 | ||
433 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=1')) |
|
434 |
assert resp.json['data']['series'][0]['data'][0] == 17 |
|
435 | ||
436 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=pending')) |
|
437 |
assert resp.json['data']['series'][0]['data'][0] == 17 |
|
438 | ||
439 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=2')) |
|
440 |
assert resp.json['data']['series'][0]['data'][0] == 3 |
|
441 | ||
442 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=done')) |
|
443 |
assert resp.json['data']['series'][0]['data'][0] == 3 |
|
444 | ||
445 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=')) |
|
446 |
assert resp.json['data']['series'][0]['data'][0] == 20 |
|
447 | ||
448 |
resp = get_app(pub).get(sign_uri(url + '&filter-status=xxx')) |
|
449 |
assert resp.json['data']['series'][0]['data'][0] == 20 |
|
450 | ||
451 |
# invalid filter |
|
452 |
resp = get_app(pub).get(sign_uri(url + '&filter-xxx=yyy')) |
|
453 |
assert resp.json['data']['series'][0]['data'] == [] |
wcs/backoffice/management.py | ||
---|---|---|
928 | 928 |
return ('start', 'end') |
929 | 929 |
return () |
930 | 930 | |
931 |
def get_item_filter_options(self, filter_field, selected_filter, criterias): |
|
931 |
def get_item_filter_options(self, filter_field, selected_filter, criterias=None):
|
|
932 | 932 |
criterias = (criterias or [])[:] |
933 | 933 |
# remove potential filter on self (Equal for item, Intersects for items) |
934 | 934 |
criterias = [ |
... | ... | |
1596 | 1596 |
field.has_relations = True |
1597 | 1597 |
yield RelatedField(carddef, card_field, field) |
1598 | 1598 | |
1599 |
yield FakeField('status', 'status', _('Status')) |
|
1599 |
yield FakeField('status', 'status', _('Status'), include_in_statistics=True)
|
|
1600 | 1600 |
yield FakeField('anonymised', 'anonymised', _('Anonymised')) |
1601 | 1601 | |
1602 | 1602 |
def get_default_columns(self): |
... | ... | |
3508 | 3508 | |
3509 | 3509 | |
3510 | 3510 |
class FakeField: |
3511 |
def __init__(self, id, type_, label, addable=True): |
|
3511 |
def __init__(self, id, type_, label, addable=True, include_in_statistics=False):
|
|
3512 | 3512 |
self.id = id |
3513 | 3513 |
self.contextual_id = self.id |
3514 | 3514 |
self.type = type_ |
... | ... | |
3518 | 3518 |
self.contextual_varname = self.varname |
3519 | 3519 |
self.store_display_value = None |
3520 | 3520 |
self.addable = addable |
3521 |
self.include_in_statistics = include_in_statistics |
|
3521 | 3522 | |
3522 | 3523 |
def get_view_value(self, value): |
3523 | 3524 |
# just here to quack like a duck |
wcs/fields.py | ||
---|---|---|
255 | 255 |
convert_value_to_str = None |
256 | 256 |
convert_value_from_anything = None |
257 | 257 |
allow_complex = False |
258 |
allow_statistics = False |
|
258 | 259 |
display_locations = [] |
259 | 260 |
prefill = None |
260 | 261 |
keep_raw_value = True |
... | ... | |
295 | 296 |
def include_in_summary_page(self): |
296 | 297 |
return 'summary' in (self.display_locations or []) |
297 | 298 | |
299 |
@property |
|
300 |
def include_in_statistics(self): |
|
301 |
return self.varname and 'statistics' in (self.display_locations or []) |
|
302 | ||
298 | 303 |
@property |
299 | 304 |
def unhtmled_label(self): |
300 | 305 |
return force_str(html.unescape(force_text(re.sub('<.*?>', ' ', self.label or ''))).strip()) |
... | ... | |
743 | 748 |
widget.extra_css_class = self.extra_css_class |
744 | 749 | |
745 | 750 |
def get_display_locations_options(self): |
746 |
return [
|
|
751 |
options = [
|
|
747 | 752 |
('validation', _('Validation Page')), |
748 | 753 |
('summary', _('Summary Page')), |
749 | 754 |
('listings', _('Management Listings')), |
750 | 755 |
] |
751 | 756 | |
757 |
if self.allow_statistics: |
|
758 |
options.append(('statistics', _('Statistics'))) |
|
759 | ||
760 |
return options |
|
761 | ||
752 | 762 |
def fill_admin_form(self, form): |
753 | 763 |
form.add(StringWidget, 'label', title=_('Label'), value=self.label, required=True, size=50) |
754 | 764 |
form.add(CheckboxWidget, 'required', title=_('Required'), value=self.required) |
... | ... | |
815 | 825 |
) |
816 | 826 | |
817 | 827 |
def check_admin_form(self, form): |
818 |
return |
|
828 |
display_locations = form.get_widget('display_locations').parse() |
|
829 |
varname = form.get_widget('varname').parse() |
|
830 |
if 'statistics' in display_locations and not varname: |
|
831 |
form.set_error( |
|
832 |
'display_locations', _('Field must have a varname in order to be displayed in statistics.') |
|
833 |
) |
|
819 | 834 | |
820 | 835 |
def get_admin_attributes(self): |
821 | 836 |
return Field.get_admin_attributes(self) + [ |
... | ... | |
1249 | 1264 |
key = 'bool' |
1250 | 1265 |
description = _('Check Box (single choice)') |
1251 | 1266 |
allow_complex = True |
1267 |
allow_statistics = True |
|
1252 | 1268 | |
1253 | 1269 |
widget_class = CheckboxWidget |
1254 | 1270 |
required = False |
... | ... | |
1888 | 1904 |
key = 'item' |
1889 | 1905 |
description = _('List') |
1890 | 1906 |
allow_complex = True |
1907 |
allow_statistics = True |
|
1891 | 1908 | |
1892 | 1909 |
items = [] |
1893 | 1910 |
show_as_radio = None |
... | ... | |
2215 | 2232 |
] |
2216 | 2233 | |
2217 | 2234 |
def check_admin_form(self, form): |
2235 |
super().check_admin_form(form) |
|
2218 | 2236 |
self.check_items_admin_form(form) |
2219 | 2237 |
self.check_zoom_admin_form(form) |
2220 | 2238 | |
... | ... | |
2273 | 2291 |
key = 'items' |
2274 | 2292 |
description = _('Multiple choice list') |
2275 | 2293 |
allow_complex = True |
2294 |
allow_statistics = True |
|
2276 | 2295 | |
2277 | 2296 |
items = [] |
2278 | 2297 |
min_choices = 0 |
... | ... | |
2358 | 2377 |
] |
2359 | 2378 | |
2360 | 2379 |
def check_admin_form(self, form): |
2380 |
super().check_admin_form(form) |
|
2361 | 2381 |
self.check_items_admin_form(form) |
2362 | 2382 | |
2363 | 2383 |
def get_prefill_value(self, user=None, force_string=True): |
wcs/statistics/views.py | ||
---|---|---|
20 | 20 |
from quixote import get_publisher |
21 | 21 | |
22 | 22 |
from wcs.api_utils import is_url_signed |
23 |
from wcs.backoffice.management import FormPage |
|
23 | 24 |
from wcs.categories import Category |
25 |
from wcs.formdef import FormDef |
|
24 | 26 |
from wcs.qommon import _, misc |
25 | 27 |
from wcs.qommon.misc import C_ |
26 |
from wcs.qommon.storage import Equal |
|
28 |
from wcs.qommon.storage import Equal, NotEqual, Or
|
|
27 | 29 | |
28 | 30 | |
29 | 31 |
class RestrictedView(View): |
... | ... | |
42 | 44 |
category_options = [{'id': '_all', 'label': C_('categories|All')}] + [ |
43 | 45 |
{'id': x.id, 'label': x.name} for x in categories |
44 | 46 |
] |
47 |
forms = FormDef.select() |
|
48 |
forms.sort(key=lambda x: misc.simplify(x.name)) |
|
49 |
form_options = [{'id': '_all', 'label': _('All')}] + [ |
|
50 |
{'id': x.url_name, 'label': x.name} for x in forms |
|
51 |
] |
|
45 | 52 |
return JsonResponse( |
46 | 53 |
{ |
47 | 54 |
'data': [ |
... | ... | |
81 | 88 |
'required': True, |
82 | 89 |
'default': '_all', |
83 | 90 |
}, |
91 |
{ |
|
92 |
'id': 'form', |
|
93 |
'label': _('Form'), |
|
94 |
'options': form_options, |
|
95 |
'required': True, |
|
96 |
'default': '_all', |
|
97 |
'has_subfilters': True, |
|
98 |
}, |
|
84 | 99 |
], |
85 | 100 |
} |
86 | 101 |
] |
... | ... | |
99 | 114 |
'criterias': [], |
100 | 115 |
} |
101 | 116 |
category_id = request.GET.get('category', '_all') |
102 |
if category_id != '_all': |
|
117 |
formdef_slug = request.GET.get('form', '_all') |
|
118 |
subfilters = [] |
|
119 |
if formdef_slug != '_all': |
|
120 |
try: |
|
121 |
formdef = FormDef.get_by_urlname(formdef_slug, ignore_migration=True) |
|
122 |
except KeyError: |
|
123 |
return HttpResponseBadRequest('invalid form') |
|
124 |
form_page = FormPage(formdef=formdef, update_breadcrumbs=False) |
|
125 | ||
126 |
totals_kwargs['criterias'].append(Equal('formdef_id', formdef.id)) |
|
127 |
totals_kwargs['criterias'].extend(self.get_filters_criterias(formdef, form_page)) |
|
128 | ||
129 |
subfilters = self.get_subfilters(form_page) |
|
130 |
elif category_id != '_all': |
|
103 | 131 |
totals_kwargs['criterias'].append(Equal('category_id', category_id)) |
132 | ||
104 | 133 |
time_interval_methods = { |
105 | 134 |
'month': sql.get_monthly_totals, |
106 | 135 |
'year': sql.get_yearly_totals, |
... | ... | |
122 | 151 |
'data': [x[1] for x in totals], |
123 | 152 |
} |
124 | 153 |
], |
154 |
'subfilters': subfilters, |
|
125 | 155 |
}, |
126 | 156 |
'err': 0, |
127 | 157 |
} |
128 | 158 |
) |
159 | ||
160 |
def get_filters_criterias(self, formdef, form_page): |
|
161 |
criterias = form_page.get_criterias_from_query() |
|
162 | ||
163 |
selected_status = self.request.GET.get('filter-status') |
|
164 |
applied_filters = None |
|
165 |
if selected_status and selected_status != '_all': |
|
166 |
if selected_status == 'pending': |
|
167 |
applied_filters = ['wf-%s' % x.id for x in formdef.workflow.get_not_endpoint_status()] |
|
168 |
elif selected_status == 'done': |
|
169 |
applied_filters = ['wf-%s' % x.id for x in formdef.workflow.get_endpoint_status()] |
|
170 |
else: |
|
171 |
try: |
|
172 |
formdef.workflow.get_status(selected_status) |
|
173 |
applied_filters = ['wf-%s' % selected_status] |
|
174 |
except KeyError: |
|
175 |
pass |
|
176 | ||
177 |
if applied_filters: |
|
178 |
criterias.append(Or([Equal('status', x) for x in applied_filters])) |
|
179 |
else: |
|
180 |
criterias = [NotEqual('status', 'draft')] + criterias |
|
181 | ||
182 |
return criterias |
|
183 | ||
184 |
@staticmethod |
|
185 |
def get_subfilters(form_page): |
|
186 |
subfilters = [] |
|
187 |
for field in form_page.get_formdef_fields(): |
|
188 |
if not getattr(field, 'include_in_statistics', False) or not field.contextual_varname: |
|
189 |
continue |
|
190 | ||
191 |
field_key = 'filter-%s' % field.contextual_varname |
|
192 |
field.required = False |
|
193 | ||
194 |
if field.type == 'status': |
|
195 |
waitpoint_status = form_page.formdef.workflow.get_waitpoint_status() |
|
196 |
if not waitpoint_status: |
|
197 |
continue |
|
198 | ||
199 |
field.required = True |
|
200 |
field.default_filter_value = '_all' |
|
201 |
options = [ |
|
202 |
('_all', _('All')), |
|
203 |
('pending', C_('statistics|Open')), |
|
204 |
('done', _('Done')), |
|
205 |
] |
|
206 |
for status in waitpoint_status: |
|
207 |
options.append((status.id, status.name)) |
|
208 |
elif field.type in ('item', 'items'): |
|
209 |
if not get_publisher().is_using_postgresql(): |
|
210 |
continue |
|
211 |
options = form_page.get_item_filter_options(field, selected_filter='all') |
|
212 |
if not options: |
|
213 |
continue |
|
214 |
elif field.type == 'bool': |
|
215 |
options = [('true', _('Yes')), ('false', _('No'))] |
|
216 |
else: |
|
217 |
continue |
|
218 | ||
219 |
filter_description = { |
|
220 |
'id': field_key, |
|
221 |
'label': field.label, |
|
222 |
'options': [{'id': x[0], 'label': x[1]} for x in options], |
|
223 |
'required': field.required, |
|
224 |
} |
|
225 |
if hasattr(field, 'default_filter_value'): |
|
226 |
filter_description['default'] = field.default_filter_value |
|
227 | ||
228 |
subfilters.append(filter_description) |
|
229 | ||
230 |
return subfilters |
|
129 |
- |