Projet

Général

Profil

0001-carddef-add-custom-view-dynamic-filter-on-items-fiel.patch

Benjamin Dauvergne, 04 janvier 2022 12:15

Télécharger (12,6 ko)

Voir les différences:

Subject: [PATCH] carddef: add custom view dynamic filter on items field
 (#48386)

 tests/form_pages/test_all.py  |  76 ++++++++++++++++++
 tests/form_pages/test_live.py | 146 ++++++++++++++++++++++++++++++++++
 wcs/carddef.py                |  30 +++++--
 3 files changed, 247 insertions(+), 5 deletions(-)
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
......
5627 5628
    assert formdef.data_class().select()[0].data['0_structured']['item'] == 'baz'
5628 5629

  
5629 5630

  
5631
@pytest.mark.parametrize('filter_value', ['{{ "foo" }}', 'foo'])
5632
def test_items_field_from_custom_view_on_cards(pub, filter_value):
5633
    pub.role_class.wipe()
5634
    pub.custom_view_class.wipe()
5635

  
5636
    user = create_user(pub)
5637
    role = pub.role_class(name='xxx')
5638
    role.store()
5639
    user.roles = [role.id]
5640
    user.is_admin = True
5641
    user.store()
5642

  
5643
    formdef = create_formdef()
5644
    formdef.data_class().wipe()
5645

  
5646
    items = ['foo', 'bar', 'baz', 'buz']
5647
    CardDef.wipe()
5648
    carddef = CardDef()
5649
    carddef.name = 'items'
5650
    carddef.digest_templates = {'default': '{{form_var_attr}} - {{form_var_item}}'}
5651
    carddef.workflow_roles = {'_editor': user.roles[0]}
5652
    carddef.fields = [
5653
        fields.ItemsField(id='0', type='items', label='item', varname='item', items=items),
5654
        fields.StringField(id='1', type='string', label='string', varname='attr'),
5655
    ]
5656
    carddef.store()
5657
    carddef.data_class().wipe()
5658
    foo_bar_ids = set()
5659
    for i, (v1, v2) in enumerate([(v1, v2) for (v1, v2) in itertools.product(items, items) if v1 != v2]):
5660
        carddata = carddef.data_class()()
5661
        carddata.data = {
5662
            '0': [v1, v2],
5663
            '0_display': '%s,%s' % (v1, v2),
5664
            '1': 'attr%s' % i,
5665
        }
5666
        carddata.just_created()
5667
        carddata.store()
5668
        if 'foo' in {v1, v2}:
5669
            foo_bar_ids.add(str(carddata.id))
5670

  
5671
    # create custom view
5672
    app = login(get_app(pub), username='foo', password='foo')
5673

  
5674
    # we must force the ordering to have a determinist test
5675
    resp = app.get('/backoffice/data/items/?order_by=id')
5676
    assert resp.text.count('<tr') == 13  # thead + 12 items (max per page)
5677
    resp.forms['listing-settings']['filter-0'].checked = True
5678
    resp = resp.forms['listing-settings'].submit()
5679

  
5680
    resp.forms['listing-settings']['filter-0-value'].force_value(filter_value)
5681
    resp = resp.forms['listing-settings'].submit()
5682

  
5683
    resp.forms['save-custom-view']['title'] = 'as data source'
5684
    resp.forms['save-custom-view']['visibility'] = 'datasource'
5685
    resp = resp.forms['save-custom-view'].submit()
5686

  
5687
    custom_view = pub.custom_view_class.select()[0]
5688

  
5689
    # use custom view as source
5690
    ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
5691
    formdef.fields = [
5692
        fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=True)
5693
    ]
5694
    formdef.store()
5695

  
5696
    resp = get_app(pub).get('/test/')
5697
    assert len(resp.form['f0'].options) == 6  # 12 - ['baz,buz', 'buz,baz']
5698
    assert {x[0] for x in resp.form['f0'].options} == foo_bar_ids
5699
    resp = resp.form.submit('submit')  # -> validation page
5700
    resp = resp.form.submit('submit')  # -> submit
5701
    formdata = formdef.data_class().select()[0]
5702
    assert formdata.data['0'] in foo_bar_ids
5703
    assert formdata.data['0_structured']['text'] == 'attr0 - foo,bar'
5704

  
5705

  
5630 5706
def test_item_field_with_disabled_items(http_requests, pub):
5631 5707
    create_user(pub)
5632 5708
    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 'foo' in {v1, v2}:
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
    live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
1416
    assert len(live_resp.json['result']['1']['items']) == 6
1417
    assert {str(x['id']) for x in live_resp.json['result']['1']['items']} == foo_bar_ids
1418

  
1419
    resp.form['f1'].options = []
1420
    for item in live_resp.json['result']['1']['items']:
1421
        # simulate javascript filling the <select>
1422
        resp.form['f1'].options.append((str(item['id']), False, item['text']))
1423

  
1424
    resp.form['f1'] = resp.form['f1'].options[0][0]
1425
    resp = resp.form.submit('submit')  # -> validation page
1426
    assert 'Technical error' not in resp.text
1427
    resp = resp.form.submit('submit')  # -> submit
1428
    assert formdef.data_class().select()[0].data['1'] in foo_bar_ids
1429
    assert formdef.data_class().select()[0].data['1_structured']['text'] == 'attr1 - foo,bar'
1430

  
1431
    # same in autocomplete mode
1432
    formdef.fields[2].display_mode = 'autocomplete'
1433
    formdef.store()
1434
    app = get_app(pub)
1435
    resp = app.get('/test/')
1436
    # simulate select2 mode, with qommon.forms.js adding an extra hidden widget
1437
    resp.form.fields['f1_display'] = Hidden(form=resp.form, tag='input', name='f1_display', pos=10)
1438
    select2_url = resp.pyquery('select:last').attr['data-select2-url']
1439
    resp_json = app.get(select2_url + '?q=')
1440
    assert len(resp_json.json['data']) == 0
1441
    resp.form['f0$element0'] = 'foo'
1442

  
1443
    live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
1444
    new_select2_url = live_resp.json['result']['1']['source_url']
1445
    resp_json = app.get(new_select2_url + '?q=')
1446
    assert len(resp_json.json['data']) == 6
1447
    assert {str(x['id']) for x in resp_json.json['data']} == foo_bar_ids
1448

  
1449
    resp.form['f1'].force_value(str(resp_json.json['data'][0]['id']))
1450
    resp.form.fields['f1_display'].force_value(resp_json.json['data'][0]['text'])
1451

  
1452
    resp = resp.form.submit('submit')  # -> validation page
1453
    resp = resp.form.submit('submit')  # -> submit
1454
    assert formdef.data_class().select()[0].data['1'] in foo_bar_ids
1455
    assert formdef.data_class().select()[0].data['1_structured']['text'] == 'attr1 - foo,bar'
1456

  
1457
    # delete custom view
1458
    if pub.is_using_postgresql():
1459
        pub.loggederror_class.wipe()
1460
    custom_view.remove_self()
1461
    resp = get_app(pub).get('/test/')
1462
    assert resp.form['f1'].options == []
1463
    if pub.is_using_postgresql():
1464
        assert pub.loggederror_class.count() == 1
1465
        logged_error = pub.loggederror_class.select()[0]
1466
        assert logged_error.formdef_id == formdef.id
1467
        assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"'
1468

  
1469

  
1324 1470
def test_item_field_from_cards_check_lazy_live(pub):
1325 1471
    create_user(pub)
1326 1472

  
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
                elif (
219
                    # case of template criteria on items
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_orig = criteria.value[0]
226
                    if Template.is_template_string(cvalue_orig):
227
                        cvalue_computed = WorkflowStatusItem.compute(cvalue_orig)
228
                        criteria.value = [cvalue_computed]
229

  
219 230
        if custom_view:
220 231
            view_digest_key = 'custom-view:%s' % custom_view.get_url_slug()
221 232
            if view_digest_key in (carddef.digest_templates or {}):
......
288 299
        from .fields import Field
289 300

  
290 301
        for criteria in custom_view.get_criterias(formdef=carddef):
291
            if not isinstance(criteria.value, str):
302
            if isinstance(criteria.value, str):
303
                cvalue = criteria.value
304
            elif (
305
                criteria.__class__.__name__ == 'Intersects'
306
                and isinstance(criteria.value, list)
307
                and len(criteria.value) == 1
308
                and isinstance(criteria.value[0], str)
309
            ):
310
                cvalue = criteria.value[0]
311
            else:
292 312
                continue
293
            varnames.extend(Field.get_referenced_varnames(formdef, criteria.value))
313
            varnames.extend(Field.get_referenced_varnames(formdef, cvalue))
294 314
        return varnames
295 315

  
296 316

  
297
-