Projet

Général

Profil

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

Benjamin Dauvergne, 27 décembre 2021 20:43

Télécharger (13,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 | 148 ++++++++++++++++++++++++++++++++++
 wcs/carddef.py                |  44 ++++++++--
 3 files changed, 263 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
......
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
-