0001-carddef-add-custom-view-dynamic-filter-on-items-fiel.patch
tests/form_pages/test_all.py | ||
---|---|---|
1 | 1 |
import hashlib |
2 | 2 |
import io |
3 |
import itertools |
|
3 | 4 |
import json |
4 | 5 |
import os |
5 | 6 |
import re |
... | ... | |
5512 | 5513 |
assert formdef.data_class().select()[0].data['0_structured']['item'] == 'baz' |
5513 | 5514 | |
5514 | 5515 | |
5516 |
@pytest.mark.parametrize('filter_value', ['{{ "foo,bar" }}', 'foo,bar', '{{ "foo,bar"|split:","|list }}']) |
|
5517 |
def test_items_field_from_custom_view_on_cards(pub, filter_value): |
|
5518 |
pub.role_class.wipe() |
|
5519 |
pub.custom_view_class.wipe() |
|
5520 | ||
5521 |
user = create_user(pub) |
|
5522 |
role = pub.role_class(name='xxx') |
|
5523 |
role.store() |
|
5524 |
user.roles = [role.id] |
|
5525 |
user.is_admin = True |
|
5526 |
user.store() |
|
5527 | ||
5528 |
formdef = create_formdef() |
|
5529 |
formdef.data_class().wipe() |
|
5530 | ||
5531 |
items = ['foo', 'bar', 'baz', 'buz'] |
|
5532 |
CardDef.wipe() |
|
5533 |
carddef = CardDef() |
|
5534 |
carddef.name = 'items' |
|
5535 |
carddef.digest_templates = {'default': '{{form_var_attr}} - {{form_var_item}}'} |
|
5536 |
carddef.workflow_roles = {'_editor': user.roles[0]} |
|
5537 |
carddef.fields = [ |
|
5538 |
fields.ItemsField(id='0', type='items', label='item', varname='item', items=items), |
|
5539 |
fields.StringField(id='1', type='string', label='string', varname='attr'), |
|
5540 |
] |
|
5541 |
carddef.store() |
|
5542 |
carddef.data_class().wipe() |
|
5543 |
foo_bar_ids = set() |
|
5544 |
for i, (v1, v2) in enumerate([(v1, v2) for (v1, v2) in itertools.product(items, items) if v1 != v2]): |
|
5545 |
carddata = carddef.data_class()() |
|
5546 |
carddata.data = { |
|
5547 |
'0': [v1, v2], |
|
5548 |
'0_display': '%s,%s' % (v1, v2), |
|
5549 |
'1': 'attr%s' % i, |
|
5550 |
} |
|
5551 |
carddata.just_created() |
|
5552 |
carddata.store() |
|
5553 |
if {v1, v2} & {'foo', 'bar'}: |
|
5554 |
foo_bar_ids.add(str(carddata.id)) |
|
5555 | ||
5556 |
# create custom view |
|
5557 |
app = login(get_app(pub), username='foo', password='foo') |
|
5558 | ||
5559 |
# we must force the ordering to have a determinist test |
|
5560 |
resp = app.get('/backoffice/data/items/?order_by=id') |
|
5561 |
assert resp.text.count('<tr') == 13 # thead + 12 items (max per page) |
|
5562 |
resp.forms['listing-settings']['filter-0'].checked = True |
|
5563 |
resp = resp.forms['listing-settings'].submit() |
|
5564 | ||
5565 |
resp.forms['listing-settings']['filter-0-value'].force_value(filter_value) |
|
5566 |
resp = resp.forms['listing-settings'].submit() |
|
5567 | ||
5568 |
resp.forms['save-custom-view']['title'] = 'as data source' |
|
5569 |
resp.forms['save-custom-view']['visibility'] = 'datasource' |
|
5570 |
resp = resp.forms['save-custom-view'].submit() |
|
5571 | ||
5572 |
custom_view = pub.custom_view_class.select()[0] |
|
5573 | ||
5574 |
# use custom view as source |
|
5575 |
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)} |
|
5576 |
formdef.fields = [ |
|
5577 |
fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=True) |
|
5578 |
] |
|
5579 |
formdef.store() |
|
5580 | ||
5581 |
resp = get_app(pub).get('/test/') |
|
5582 |
assert len(resp.form['f0'].options) == 10 # 12 - ['baz,buz', 'buz,baz'] |
|
5583 |
assert {x[0] for x in resp.form['f0'].options} == foo_bar_ids |
|
5584 |
resp = resp.form.submit('submit') # -> validation page |
|
5585 |
resp = resp.form.submit('submit') # -> submit |
|
5586 |
formdata = formdef.data_class().select()[0] |
|
5587 |
assert formdata.data['0'] in foo_bar_ids |
|
5588 |
assert formdata.data['0_structured']['text'] == 'attr0 - foo,bar' |
|
5589 | ||
5590 | ||
5515 | 5591 |
def test_item_field_with_disabled_items(http_requests, pub): |
5516 | 5592 |
create_user(pub) |
5517 | 5593 |
formdef = create_formdef() |
tests/form_pages/test_live.py | ||
---|---|---|
1 | 1 |
import datetime |
2 | 2 |
import io |
3 |
import itertools |
|
3 | 4 |
import json |
4 | 5 |
from unittest import mock |
5 | 6 | |
... | ... | |
1321 | 1322 |
assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"' |
1322 | 1323 | |
1323 | 1324 | |
1325 |
def test_dynamic_items_field_from_custom_view_on_cards(pub): |
|
1326 |
if not pub.is_using_postgresql(): |
|
1327 |
pytest.skip('this requires SQL') |
|
1328 |
return |
|
1329 | ||
1330 |
pub.role_class.wipe() |
|
1331 |
pub.custom_view_class.wipe() |
|
1332 | ||
1333 |
user = create_user(pub) |
|
1334 |
role = pub.role_class(name='xxx') |
|
1335 |
role.store() |
|
1336 |
user.roles = [role.id] |
|
1337 |
user.is_admin = True |
|
1338 |
user.store() |
|
1339 | ||
1340 |
FormDef.wipe() |
|
1341 |
formdef = FormDef() |
|
1342 |
formdef.name = 'test' |
|
1343 |
formdef.fields = [] |
|
1344 |
formdef.store() |
|
1345 |
formdef.data_class().wipe() |
|
1346 | ||
1347 |
items = ['foo', 'bar', 'baz', 'buz'] |
|
1348 |
CardDef.wipe() |
|
1349 |
carddef = CardDef() |
|
1350 |
carddef.name = 'items' |
|
1351 |
carddef.digest_templates = {'default': '{{form_var_attr}} - {{form_var_items}}'} |
|
1352 |
carddef.workflow_roles = {'_editor': user.roles[0]} |
|
1353 |
carddef.fields = [ |
|
1354 |
fields.ItemsField(id='0', type='items', label='items', varname='items', items=items), |
|
1355 |
fields.StringField(id='1', type='string', label='string', varname='attr'), |
|
1356 |
] |
|
1357 |
carddef.store() |
|
1358 |
carddef.data_class().wipe() |
|
1359 |
foo_bar_ids = set() |
|
1360 |
for i, (v1, v2) in enumerate(itertools.product(items, items)): |
|
1361 |
if v1 == v2: |
|
1362 |
continue |
|
1363 |
carddata = carddef.data_class()() |
|
1364 |
carddata.data = { |
|
1365 |
'0': [v1, v2], |
|
1366 |
'0_display': '%s,%s' % (v1, v2), |
|
1367 |
'1': 'attr%s' % i, |
|
1368 |
} |
|
1369 |
carddata.just_created() |
|
1370 |
carddata.store() |
|
1371 |
if {v1, v2} & {'foo', 'bar'}: |
|
1372 |
foo_bar_ids.add(str(carddata.id)) |
|
1373 | ||
1374 |
# create custom view |
|
1375 |
app = login(get_app(pub), username='foo', password='foo') |
|
1376 | ||
1377 |
resp = app.get('/backoffice/data/items/?order_by=id') |
|
1378 |
assert resp.text.count('<tr') == 13 # thead + 12 items |
|
1379 |
resp.forms['listing-settings']['filter-0'].checked = True |
|
1380 |
resp.forms['listing-settings']['filter-status'].checked = True |
|
1381 |
resp = resp.forms['listing-settings'].submit() |
|
1382 | ||
1383 |
resp.forms['listing-settings']['filter'].value = 'recorded' |
|
1384 |
resp = resp.forms['listing-settings'].submit() |
|
1385 | ||
1386 |
resp.forms['save-custom-view']['title'] = 'as data source' |
|
1387 |
resp.forms['save-custom-view']['visibility'] = 'datasource' |
|
1388 |
resp = resp.forms['save-custom-view'].submit().follow() |
|
1389 | ||
1390 |
assert resp.forms['listing-settings']['filter-0-value'].attrs['data-allow-template'] |
|
1391 |
assert 'custom value' in [x[2] for x in resp.forms['listing-settings']['filter-0-value'].options] |
|
1392 |
resp.forms['listing-settings']['filter-0-value'].force_value('{{ form_var_blah }}') |
|
1393 | ||
1394 |
resp = resp.forms['listing-settings'].submit() |
|
1395 |
assert resp.forms['listing-settings']['filter-0-value'].value == '{{ form_var_blah }}' |
|
1396 |
assert resp.text.count('<tr') == 1 # thead only |
|
1397 | ||
1398 |
# save custom view with filter |
|
1399 |
resp = resp.forms['save-custom-view'].submit().follow() |
|
1400 | ||
1401 |
custom_view = pub.custom_view_class.select()[0] |
|
1402 | ||
1403 |
# use custom view as source |
|
1404 |
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)} |
|
1405 |
formdef.fields = [ |
|
1406 |
fields.PageField(id='2', label='1st page', type='page'), |
|
1407 |
fields.ItemsField(id='0', type='items', label='items', varname='blah', items=items), |
|
1408 |
fields.ItemField(id='1', label='string', type='item', data_source=ds, display_disabled_items=True), |
|
1409 |
] |
|
1410 |
formdef.store() |
|
1411 | ||
1412 |
resp = get_app(pub).get('/test/') |
|
1413 |
assert resp.form['f1'].options == [('', False, '---')] |
|
1414 |
resp.form['f0$element0'] = 'foo' |
|
1415 |
resp.form['f0$element1'] = 'bar' |
|
1416 |
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields()) |
|
1417 |
assert len(live_resp.json['result']['1']['items']) == 10 |
|
1418 |
assert {str(x['id']) for x in live_resp.json['result']['1']['items']} == foo_bar_ids |
|
1419 | ||
1420 |
resp.form['f1'].options = [] |
|
1421 |
for item in live_resp.json['result']['1']['items']: |
|
1422 |
# simulate javascript filling the <select> |
|
1423 |
resp.form['f1'].options.append((str(item['id']), False, item['text'])) |
|
1424 | ||
1425 |
resp.form['f1'] = resp.form['f1'].options[0][0] |
|
1426 |
resp = resp.form.submit('submit') # -> validation page |
|
1427 |
assert 'Technical error' not in resp.text |
|
1428 |
resp = resp.form.submit('submit') # -> submit |
|
1429 |
assert formdef.data_class().select()[0].data['1'] in foo_bar_ids |
|
1430 |
assert formdef.data_class().select()[0].data['1_structured']['text'] == 'attr1 - foo,bar' |
|
1431 | ||
1432 |
# same in autocomplete mode |
|
1433 |
formdef.fields[2].display_mode = 'autocomplete' |
|
1434 |
formdef.store() |
|
1435 |
app = get_app(pub) |
|
1436 |
resp = app.get('/test/') |
|
1437 |
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget |
|
1438 |
resp.form.fields['f1_display'] = Hidden(form=resp.form, tag='input', name='f1_display', pos=10) |
|
1439 |
select2_url = resp.pyquery('select:last').attr['data-select2-url'] |
|
1440 |
resp_json = app.get(select2_url + '?q=') |
|
1441 |
assert len(resp_json.json['data']) == 0 |
|
1442 |
resp.form['f0$element0'] = 'foo' |
|
1443 |
resp.form['f0$element1'] = 'bar' |
|
1444 | ||
1445 |
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields()) |
|
1446 |
new_select2_url = live_resp.json['result']['1']['source_url'] |
|
1447 |
resp_json = app.get(new_select2_url + '?q=') |
|
1448 |
assert len(resp_json.json['data']) == 10 |
|
1449 |
assert {str(x['id']) for x in resp_json.json['data']} == foo_bar_ids |
|
1450 | ||
1451 |
resp.form['f1'].force_value(str(resp_json.json['data'][0]['id'])) |
|
1452 |
resp.form.fields['f1_display'].force_value(resp_json.json['data'][0]['text']) |
|
1453 | ||
1454 |
resp = resp.form.submit('submit') # -> validation page |
|
1455 |
resp = resp.form.submit('submit') # -> submit |
|
1456 |
assert formdef.data_class().select()[0].data['1'] in foo_bar_ids |
|
1457 |
assert formdef.data_class().select()[0].data['1_structured']['text'] == 'attr1 - foo,bar' |
|
1458 | ||
1459 |
# delete custom view |
|
1460 |
if pub.is_using_postgresql(): |
|
1461 |
pub.loggederror_class.wipe() |
|
1462 |
custom_view.remove_self() |
|
1463 |
resp = get_app(pub).get('/test/') |
|
1464 |
assert resp.form['f1'].options == [] |
|
1465 |
if pub.is_using_postgresql(): |
|
1466 |
assert pub.loggederror_class.count() == 1 |
|
1467 |
logged_error = pub.loggederror_class.select()[0] |
|
1468 |
assert logged_error.formdef_id == formdef.id |
|
1469 |
assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"' |
|
1470 | ||
1471 | ||
1324 | 1472 |
def test_item_field_from_cards_check_lazy_live(pub): |
1325 | 1473 |
create_user(pub) |
1326 | 1474 |
wcs/carddef.py | ||
---|---|---|
213 | 213 |
order_by = custom_view.order_by |
214 | 214 |
criterias.extend(custom_view.get_criterias(formdef=carddef)) |
215 | 215 |
for criteria in criterias: |
216 |
if not Template.is_template_string(criteria.value): |
|
217 |
continue |
|
218 |
criteria.value = WorkflowStatusItem.compute(criteria.value) |
|
216 |
if Template.is_template_string(criteria.value): |
|
217 |
criteria.value = WorkflowStatusItem.compute(criteria.value) |
|
218 |
# case of items |
|
219 |
elif ( |
|
220 |
criteria.__class__.__name__ == 'Intersects' |
|
221 |
and isinstance(criteria.value, list) |
|
222 |
and len(criteria.value) == 1 |
|
223 |
and isinstance(criteria.value[0], str) |
|
224 |
): |
|
225 |
cvalue = criteria.value[0] |
|
226 | ||
227 |
if Template.is_template_string(cvalue): |
|
228 |
publisher = get_publisher() |
|
229 |
with publisher.complex_data(): |
|
230 |
cvalue_evaluated = WorkflowStatusItem.compute(cvalue, allow_complex=True) |
|
231 |
cvalue_complex = publisher.get_cached_complex_data(cvalue_evaluated) |
|
232 |
if isinstance(cvalue_complex, list): |
|
233 |
cvalue = ','.join(str(item) for item in cvalue_complex) |
|
234 |
elif isinstance(cvalue_complex, str): |
|
235 |
# get_cached_complex_data remove Unicode |
|
236 |
# characters from the private use area (0xE000-0xF8FF) |
|
237 |
cvalue = cvalue_complex |
|
238 |
else: |
|
239 |
# cvalue_complex is something else (file, etc..) |
|
240 |
cvalue = cvalue_evaluated |
|
241 |
cvalue = list(cvalue.split(',')) |
|
242 |
criteria.value = [item.strip() for item in cvalue] |
|
243 | ||
219 | 244 |
if custom_view: |
220 | 245 |
view_digest_key = 'custom-view:%s' % custom_view.get_url_slug() |
221 | 246 |
if view_digest_key in (carddef.digest_templates or {}): |
... | ... | |
288 | 313 |
from .fields import Field |
289 | 314 | |
290 | 315 |
for criteria in custom_view.get_criterias(formdef=carddef): |
291 |
if not isinstance(criteria.value, str): |
|
316 |
if isinstance(criteria.value, str): |
|
317 |
cvalue = criteria.value |
|
318 |
elif ( |
|
319 |
criteria.__class__.__name__ == 'Intersects' |
|
320 |
and isinstance(criteria.value, list) |
|
321 |
and len(criteria.value) == 1 |
|
322 |
and isinstance(criteria.value[0], str) |
|
323 |
): |
|
324 |
cvalue = criteria.value[0] |
|
325 |
else: |
|
292 | 326 |
continue |
293 |
varnames.extend(Field.get_referenced_varnames(formdef, criteria.value))
|
|
327 |
varnames.extend(Field.get_referenced_varnames(formdef, cvalue)) |
|
294 | 328 |
return varnames |
295 | 329 | |
296 | 330 | |
297 |
- |