wcs/tests/form_pages/test_live.py

1925 lines
72 KiB
Python

import datetime
import io
import itertools
import json
from unittest import mock
import pytest
from webtest import Checkbox, Hidden
from wcs import fields
from wcs.blocks import BlockDef
from wcs.carddef import CardDef
from wcs.data_sources import NamedDataSource
from wcs.formdef import FormDef
from wcs.wf.form import WorkflowFormFieldsFormDef
from wcs.workflows import Workflow
from ..utilities import clean_temporary_pub, create_temporary_pub, get_app, login
from .test_all import create_user
def pytest_generate_tests(metafunc):
if 'pub' in metafunc.fixturenames:
metafunc.parametrize('pub', ['sql', 'sql-lazy'], indirect=True)
@pytest.fixture
def pub(request):
pub = create_temporary_pub(
sql_mode=bool('sql' in request.param),
lazy_mode=bool('lazy' in request.param),
)
pub.cfg['identification'] = {'methods': ['password']}
pub.cfg['language'] = {'language': 'en'}
pub.write_cfg()
return pub
def teardown_module(module):
clean_temporary_pub()
def test_field_live_condition(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(
type='string',
id='2',
label='Foo',
size='40',
required=True,
varname='foo',
condition={'type': 'django', 'value': 'form_var_bar == "bye"'},
),
]
formdef.store()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert not live_resp.json['result']['2']['visible']
resp.form['f1'] = 'bye'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
resp.form['f1'] = 'hello'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'name="f1"' in resp.text
assert 'name="f2"' not in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert '<span class="label">Bar</span>' in resp.text
assert '<span class="label">Foo</span>' not in resp.text
resp = get_app(pub).get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
resp.form['f1'] = 'bye'
resp = resp.form.submit('submit')
assert 'There were errors' in resp.text
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') is None
resp.form['f2'] = 'bye'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'name="f1"' in resp.text
assert 'name="f2"' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert '<span class="label">Bar</span>' in resp.text
assert '<span class="label">Foo</span>' in resp.text
def test_field_live_items_condition(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemsField(type='items', id='1', label='Bar', items=['a', 'b'], varname='bar'),
fields.StringField(
type='string',
id='2',
label='Foo',
size='40',
required=True,
varname='foo',
condition={'type': 'django', 'value': '"b" in form_var_bar'},
),
]
formdef.store()
create_user(pub)
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/foo/')
assert 'f1$element0' in resp.form.fields
assert 'f1$element1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
resp.form['f1$element0'].checked = True
app.post('/foo/autosave', params=resp.form.submit_fields())
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert not live_resp.json['result']['2']['visible']
resp.form['f1$element1'].checked = True
app.post('/foo/autosave', params=resp.form.submit_fields())
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
resp.form['f1$element0'].checked = False
resp.form['f1$element1'].checked = False
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert not live_resp.json['result']['2']['visible']
def test_live_field_condition_on_required_field(pub):
# from https://dev.entrouvert.org/issues/27247
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(
type='item',
id='1',
label='Bar',
items=['oui', 'non'],
display_mode='radio',
required=True,
varname='bar',
),
fields.ItemField(
type='item',
id='2',
label='Foo',
size='40',
required=True,
hint='---',
items=['plop'],
condition={'type': 'django', 'value': 'form_var_bar == "oui"'},
),
fields.PageField(
id='3', label='1st page', type='page', condition={'type': 'python', 'value': 'True'}
),
fields.CommentField(type='comment', id='4', label='HELLO!'),
]
formdef.store()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
resp.form['f1'] = 'non'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert not live_resp.json['result']['2']['visible']
resp = resp.form.submit('submit')
assert 'HELLO' in resp.text
resp = resp.form.submit('previous')
resp.form['f1'] = 'oui'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
resp = resp.form.submit('submit')
assert resp.pyquery('div.error').text() == 'required field'
assert 'HELLO' not in resp.text
def test_field_live_condition_multipages(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.PageField(id='0', label='2nd page', type='page'),
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(
type='string',
id='2',
label='Foo',
size='40',
required=True,
varname='foo',
condition={'type': 'django', 'value': 'form_var_bar == "bye"'},
),
fields.PageField(id='3', label='1st page', type='page'),
fields.StringField(type='string', id='4', label='Baz', size='40', required=True, varname='baz'),
]
formdef.store()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert not live_resp.json['result']['2']['visible']
resp.form['f1'] = 'bye'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
resp.form['f1'] = 'bye'
resp.form['f2'] = 'bye'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') is None
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
resp = resp.form.submit('submit')
resp.form['f4'] = 'plop'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'name="f1"' in resp.text
assert 'name="f2"' in resp.text
assert 'name="f4"' in resp.text
resp = resp.form.submit('submit')
def test_field_live_select_content(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemField(
type='item',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
assert live_resp.json['result']['3']['visible']
assert 'items' not in live_resp.json['result']['3']
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
assert live_resp.json['result']['3']['visible']
assert 'items' in live_resp.json['result']['3']
resp.form['f3'].options = []
for item in live_resp.json['result']['3']['items']:
# simulate javascript filling the <select>
resp.form['f3'].options.append((item['id'], False, item['text']))
resp.form['f3'] = 'a'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'name="f1"' in resp.text
assert 'name="f2"' in resp.text
assert 'name="f3"' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'hello'
assert formdata.data['2'] == 'plop'
assert formdata.data['3'] == 'a'
assert formdata.data['3_display'] == 'b'
# create and use geojson datasource
NamedDataSource.wipe()
data_source = NamedDataSource(name='geofoobar')
data_source.data_source = {
'type': 'geojson',
'value': 'http://remote.example.net/geojson?plop={{form_var_bar2}}',
}
data_source.id_property = 'id'
data_source.label_template_property = '{{ text }}'
data_source.cache_duration = '5'
data_source.store()
formdef.fields[2].data_source = {'type': 'geofoobar'}
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
assert live_resp.json['result']['3']['visible']
assert 'items' not in live_resp.json['result']['3']
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert live_resp.json['result']['1']['visible']
assert live_resp.json['result']['2']['visible']
assert live_resp.json['result']['3']['visible']
assert 'items' in live_resp.json['result']['3']
resp.form['f3'].options = []
for item in live_resp.json['result']['3']['items']:
# simulate javascript filling the <select>
resp.form['f3'].options.append((item['id'], False, item['text']))
resp.form['f3'] = '1'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'name="f1"' in resp.text
assert 'name="f2"' in resp.text
assert 'name="f3"' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'hello'
assert formdata.data['2'] == 'plop'
assert formdata.data['3'] == '1'
assert formdata.data['3_display'] == 'foo'
def test_field_live_select_content_on_workflow_form(pub, http_requests):
create_user(pub)
wf = Workflow(name='wf-title')
st1 = wf.add_status('Status1', 'st1')
# form displayed into the workflow
display_form = st1.add_action('form', id='_x')
display_form.by = ['_submitter']
display_form.varname = 'xxx'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemField(
type='item',
id='3',
label='Foo',
required=False,
varname='foo',
data_source={
'type': 'json',
'value': '{% if xxx_var_bar2 %}http://remote.example.net/json-list?plop={{xxx_var_bar2}}{% endif %}',
},
),
]
wf.store()
# initial empty form
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = []
formdef.confirmation = False
formdef.workflow_id = wf.id
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = login(app, username='foo', password='foo').get('/test/')
assert 'Forms - test' in resp.text
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded' in resp.text
assert 'data-live-url' in resp.html.find('form').attrs
assert 'fxxx_1' in resp.form.fields
assert 'fxxx_2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': 'xxx_2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': 'xxx_3'}).find('select')
resp = resp.form.submit('submit') # submit with error, to check <form> attributes
assert 'data-live-url' in resp.html.find('form').attrs
assert 'fxxx_1' in resp.form.fields
assert 'fxxx_2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': 'xxx_2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': 'xxx_3'}).find('select')
resp.form['fxxx_1'] = 'hello'
live_resp = app.post('/test/1/live', params=resp.form.submit_fields())
assert live_resp.json['result']['xxx_1']['visible']
assert live_resp.json['result']['xxx_2']['visible']
assert live_resp.json['result']['xxx_3']['visible']
assert 'items' not in live_resp.json['result']['xxx_3']
resp.form['fxxx_2'] = 'plop'
live_resp = app.post('/test/1/live?modified_field_id=xxx_2', params=resp.form.submit_fields())
assert live_resp.json['result']['xxx_1']['visible']
assert live_resp.json['result']['xxx_2']['visible']
assert live_resp.json['result']['xxx_3']['visible']
assert 'items' in live_resp.json['result']['xxx_3']
assert len(live_resp.json['result']['xxx_3']['items']) > 0
resp.form['fxxx_3'].options = []
for item in live_resp.json['result']['xxx_3']['items']:
# simulate javascript filling the <select>
resp.form['fxxx_3'].options.append((item['id'], False, item['text']))
resp.form['fxxx_3'] = 'a'
resp = resp.form.submit('submit')
assert 'invalid value selected' not in resp
resp = resp.follow()
formdata = formdef.data_class().select()[0]
assert formdata.workflow_data['xxx_var_bar'] == 'hello'
assert formdata.workflow_data['xxx_var_bar2'] == 'plop'
assert formdata.workflow_data['xxx_var_foo_raw'] == 'a'
assert formdata.workflow_data['xxx_var_foo'] == 'b'
def test_field_live_select_content_based_on_prefill(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(
type='string',
id='1',
label='Bar',
size='40',
required=True,
varname='bar',
prefill={'type': 'string', 'value': 'HELLO WORLD'},
),
fields.ItemField(
type='item',
id='2',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar %}http://remote.example.net/json-list?plop={{form_var_bar}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '2'}).find('select')
assert resp.html.find('option', {'value': 'a'})
assert http_requests.get_last('url') == 'http://remote.example.net/json-list?plop=HELLO WORLD'
# check with autocomplete and a remote source with id
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'json',
'value': 'http://remote.example.net/json?plop={{form_var_bar}}',
}
data_source.query_parameter = 'q'
data_source.id_parameter = 'id'
data_source.store()
formdef.fields[1].display_mode = 'autocomplete'
formdef.fields[1].data_source['type'] = 'foobar'
formdef.store()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
def test_field_live_select_content_on_other_default_select_option(pub, http_requests):
create_user(pub)
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
type='item',
id='2',
label='Foo',
varname='bar2',
data_source={'type': 'json', 'value': 'http://remote.example.net/json-list'},
),
fields.ItemField(
type='item',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f2' in resp.form.fields
assert 'f3' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
# javascript will make an initial call with ?modified_dield_id=init,
# simulate.
live_resp = app.post('/foo/live?modified_field_id=init', params=resp.form.submit_fields())
assert 'items' in live_resp.json['result']['3']
resp.form['f3'].options = []
for item in live_resp.json['result']['3']['items']:
# simulate javascript filling the <select>
resp.form['f3'].options.append((item['id'], False, item['text']))
resp.form['f3'] = 'a'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'name="f2"' in resp.text
assert 'name="f3"' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
formdata = formdef.data_class().select()[0]
assert formdata.data['2'] == 'a'
assert formdata.data['3'] == 'a'
def test_field_live_select(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemField(
type='item',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list-extra-with-disabled?plop={{form_var_bar2}}{% endif %}',
},
display_disabled_items=True,
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 2
assert live_resp.json['result']['3']['items'][1]['disabled'] is True
formdef.fields[1].display_disabled_items = False
formdef.store()
resp = app.get('/foo/')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 1
def test_field_live_items_checkboxes(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemsField(
type='items',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('ul')
assert not resp.html.find('div', {'data-field-id': '3'}).find('ul li')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 1
# simulate js, add relevant checkboxes
for option in live_resp.json['result']['3']['items']:
checkbox_name = '%s$element%s' % (
resp.html.find('div', {'data-field-id': '3'}).attrs['data-widget-name'],
option['id'],
)
resp.form.fields[checkbox_name] = Checkbox(
form=resp.form, name=checkbox_name, tag='input', value='yes', pos=10
)
resp.form.field_order.append((checkbox_name, resp.form.fields[checkbox_name]))
resp.form.fields[checkbox_name].checked = True
resp = resp.form.submit('submit') # -> validation
assert resp.pyquery('.CheckboxesWidget li label').text() == 'b'
resp = resp.form.submit('submit') # -> submitted
assert formdef.data_class().select()[0].data['3'] == ['a']
assert formdef.data_class().select()[0].data['3_display'] == 'b'
def test_field_live_items_select_multiple(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemsField(
type='items',
display_mode='autocomplete',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
assert not resp.html.find('div', {'data-field-id': '3'}).find('select option')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 1
# simulate js, add relevant options
resp.form['f3[]'].options = [(x['id'], False, x['text']) for x in live_resp.json['result']['3']['items']]
resp.form['f3[]'].select_multiple(['a'])
resp = resp.form.submit('submit') # -> validation
assert resp.pyquery('select option[selected]').text() == 'b'
resp = resp.form.submit('submit') # -> submitted
assert formdef.data_class().select()[0].data['3'] == ['a']
assert formdef.data_class().select()[0].data['3_display'] == 'b'
def test_field_live_template_content(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemField(
type='item',
id='3',
label='Foo',
extra_css_class='template-whatever',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list-extra-with-disabled?plop={{form_var_bar2}}{% endif %}',
},
display_disabled_items=True,
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert live_resp.json['result']['3']['items'][0]['foo'] == 'bar'
assert len(live_resp.json['result']['3']['items']) == 2
assert live_resp.json['result']['3']['items'][1]['disabled'] is True
formdef.fields[1].display_disabled_items = False
formdef.store()
resp = app.get('/foo/')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 1
def test_field_live_timetable_select(pub, http_requests):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'json',
'value': 'http://remote.example.net/api/datetimes{% if form_var_bar2 %}?{% endif %}',
}
data_source.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemField(
id='3',
type='item',
label='datetime',
display_mode='timetable',
data_source={'type': 'foobar'},
display_disabled_items=True,
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
with mock.patch('wcs.qommon.misc.urlopen') as urlopen:
data = {
"data": [
{"id": "1", "datetime": "2021-01-12 10:00:00", "text": "event 1", "api": {}},
{"id": "2", "datetime": "2021-01-13 10:20:00", "text": "event 2", "api": {}},
{
"id": "3",
"datetime": "2021-01-14 10:40:00",
"text": "event 3",
"api": {},
"disabled": True,
},
]
}
urlopen.side_effect = lambda *args: io.StringIO(json.dumps(data))
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert 'datetime' in live_resp.json['result']['3']['items'][0]
assert 'api' not in live_resp.json['result']['3']['items'][0]
assert len(live_resp.json['result']['3']['items']) == 3
assert live_resp.json['result']['3']['items'][2]['disabled'] is True
formdef.fields[1].display_disabled_items = False
formdef.store()
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert 'datetime' in live_resp.json['result']['3']['items'][0]
assert 'api' not in live_resp.json['result']['3']['items'][0]
assert len(live_resp.json['result']['3']['items']) == 2
def test_field_live_comment_content(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(type='string', id='2', label='Baz', size='40'),
fields.CommentField(id='5', label='bla {{form_var_bar}} bla', type='comment'),
fields.StringField(type='string', id='6', label='Bar2', size='40', required=True, varname='bar2'),
fields.CommentField(id='7', label='bla {{form_var_bar2}} bla', type='comment'),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['5']['content'] == '<p>bla hello bla</p>'
resp.form['f1'] = 'toto'
live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields())
assert live_resp.json['result']['5']['content'] == '<p>bla toto bla</p>'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert live_resp.json['result']['5']['content'] == '<p>bla toto bla</p>'
# check evaluation of later fields
# <https://dev.entrouvert.org/issues/31922>
resp = app.get('/foo/')
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['5']['content'] == '<p>bla hello bla</p>'
resp.form['f6'] = 'hello'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['7']['content'] == '<p>bla hello bla</p>'
def test_field_live_comment_content_from_structured_item_data(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
type='item',
id='1',
label='Foo',
varname='bar',
data_source={'type': 'json', 'value': 'http://remote.example.net/json-list-extra'},
),
fields.CommentField(id='7', label='bla {{form_var_bar_structured_foo}} bla', type='comment'),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
resp.form['f1'] = 'a'
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json['result']['7']['content'] == '<p>bla bar bla</p>'
def test_field_live_string_prefill(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(
type='string',
id='2',
label='Bar2',
size='40',
required=True,
varname='bar2',
prefill={'type': 'string', 'value': '{{form_var_bar|default:""}}'},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('#var_bar2.widget-prefilled') # second field is marked as prefilled
assert resp.form['f2'].value == ''
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live?modified_field_id=1&prefilled_2=on', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True, 'content': 'hello'}
resp.form['f2'] = 'other' # manually changed -> widget-prefilled class will be removed
resp.form['f1'] = 'xxx'
live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True}
# check it's not possible to declare user change from frontoffice
live_resp = app.post('/foo/live?modified_field_id=user', params=resp.form.submit_fields(), status=403)
def test_field_live_bool_prefill(pub, http_requests):
CardDef.wipe()
carddef = CardDef()
carddef.name = 'foo'
carddef.digest_templates = {'default': '{{ form_var_foo }}'}
carddef.fields = [
fields.StringField(id='1', type='string', label='string', varname='foo'),
fields.BoolField(
type='bool',
id='2',
varname='bool',
),
]
carddef.store()
carddef.data_class().wipe()
carddata1 = carddef.data_class()()
carddata1.data = {
'1': 'bar',
'2': True,
}
carddata1.just_created()
carddata1.store()
carddata2 = carddef.data_class()()
carddata2.data = {
'1': 'baz',
'2': False,
}
carddata2.just_created()
carddata2.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
type='item', id='1', label='foo', varname='foo', data_source={'type': 'carddef:foo'}
),
fields.BoolField(
type='bool',
id='2',
label='bool',
varname='bool',
prefill={'type': 'string', 'value': '{{ form_var_foo_live_var_bool }}'},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('#var_bool.widget-prefilled') # second field is marked as prefilled
assert resp.form['f2'].value is None
resp.form['f1'] = str(carddata1.id)
live_resp = app.post('/foo/live?modified_field_id=1&prefilled_2=on', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True, 'content': True}
resp.form['f2'] = False # manually changed -> widget-prefilled class will be removed
resp.form['f1'] = str(carddata2.id)
live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True}
def test_field_live_date_prefill(pub, http_requests):
pub.cfg['language'] = {'language': 'fr'}
pub.write_cfg()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'foo'
carddef.digest_templates = {'default': '{{ form_var_foo }}'}
carddef.fields = [
fields.StringField(id='1', type='string', label='string', varname='foo'),
fields.DateField(
type='date',
id='2',
varname='date',
),
]
carddef.store()
carddef.data_class().wipe()
carddata1 = carddef.data_class()()
carddata1.data = {
'1': 'bar',
'2': datetime.date(2021, 10, 1).timetuple(),
}
carddata1.just_created()
carddata1.store()
carddata2 = carddef.data_class()()
carddata2.data = {
'1': 'baz',
'2': datetime.date(2021, 10, 30).timetuple(),
}
carddata2.just_created()
carddata2.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
type='item', id='1', label='foo', varname='foo', data_source={'type': 'carddef:foo'}
),
fields.DateField(
type='date',
id='2',
label='date',
varname='date',
prefill={'type': 'string', 'value': '{{ form_var_foo_live_var_date }}'},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('#var_date.widget-prefilled') # second field is marked as prefilled
assert resp.form['f2'].value == ''
resp.form['f1'] = str(carddata1.id)
live_resp = app.post('/foo/live?modified_field_id=1&prefilled_2=on', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {
'visible': True,
'content': '2021-10-01',
'text_content': '01/10/2021',
}
resp.form['f2'] = '2021-10-30' # manually changed -> widget-prefilled class will be removed
resp.form['f1'] = str(carddata2.id)
live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True}
def test_field_live_item_datasource_carddef_prefill(pub, http_requests):
CardDef.wipe()
carddef_related = CardDef()
carddef_related.name = 'bar'
carddef_related.digest_templates = {'default': '{{ form_var_bar }}'}
carddef_related.fields = [
fields.StringField(id='1', type='string', label='string', varname='bar'),
]
carddef_related.store()
carddef_related.data_class().wipe()
for value in ['A', 'B', 'C']:
carddata = carddef_related.data_class()()
carddata.data = {
'1': value,
}
carddata.just_created()
carddata.store()
carddef = CardDef()
carddef.name = 'foo'
carddef.digest_templates = {'default': '{{ form_var_foo }}'}
carddef.fields = [
fields.StringField(id='1', type='string', label='string', varname='foo'),
fields.ItemField(
type='item',
id='2',
varname='item',
data_source={'type': 'carddef:bar'},
),
]
carddef.store()
carddef.data_class().wipe()
carddata1 = carddef.data_class()()
carddata1.data = {
'1': 'bar',
'2': '1',
'2_display': 'A',
}
carddata1.just_created()
carddata1.store()
carddata2 = carddef.data_class()()
carddata2.data = {
'1': 'baz',
'2': '2',
'2_display': 'B',
}
carddata2.just_created()
carddata2.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
type='item', id='1', label='foo', varname='foo', data_source={'type': 'carddef:foo'}
),
fields.ItemField(
type='item',
id='2',
label='item',
varname='item',
prefill={'type': 'string', 'value': '{{ form_var_foo_live_var_item }}'},
data_source={'type': 'carddef:bar'},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('#var_item.widget-prefilled') # second field is marked as prefilled
assert resp.form['f2'].value == '1'
resp.form['f1'] = str(carddata2.id)
live_resp = app.post('/foo/live?modified_field_id=1&prefilled_2=on', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True, 'content': '2'}
resp.form['f2'] = '3' # manually changed -> widget-prefilled class will be removed
resp.form['f1'] = str(carddata1.id)
live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields())
assert live_resp.json['result']['2'] == {'visible': True}
@mock.patch('wcs.qommon.misc.urlopen')
def test_field_live_item_datasource_prefill_with_request(urlopen, pub):
data = {'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux', 'x': 'bye'}]}
urlopen.side_effect = lambda *args: io.StringIO(json.dumps(data))
ds = {'type': 'json', 'value': 'http://remote.example.net/plop'}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
id='1',
label='item',
data_source=ds,
varname='foo',
prefill={'type': 'string', 'value': '{{ request.GET.plop }}'},
),
fields.ItemField(
type='item',
id='2',
label='item',
varname='item',
prefill={'type': 'string', 'value': '{{ form_var_foo }}'},
data_source=ds,
),
]
formdef.store()
app = get_app(pub)
resp = app.get('/foo/?plop=2')
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('#var_item.widget-prefilled') # second field is marked as prefilled
assert resp.form['f1'].value == '2'
live_resp = app.post(
'/foo/live?modified_field_id=init&prefilled_1=on&prefilled_2=on', params=resp.form.submit_fields()
)
assert live_resp.json['result'] == {'2': {'visible': True, 'content': '2'}}
@mock.patch('wcs.qommon.misc.urlopen')
def test_field_live_item_datasource_prefill_with_request_with_q(urlopen, pub):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'json',
'value': 'http://remote.example.net/json?plop={{form_var_bar}}',
}
data_source.query_parameter = 'q'
data_source.id_parameter = 'id'
data_source.store()
data = {'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux', 'x': 'bye'}]}
urlopen.side_effect = lambda *args: io.StringIO(json.dumps(data))
ds = {'type': 'foobar'}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(
id='1',
label='item',
data_source=ds,
varname='foo',
prefill={'type': 'string', 'value': '{{ request.GET.plop }}'},
),
fields.ItemField(
type='item',
id='2',
label='item',
varname='item',
prefill={'type': 'string', 'value': '{{ form_var_foo }}'},
data_source=ds,
),
]
formdef.store()
app = get_app(pub)
resp = app.get('/foo/?plop=2')
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('#var_item.widget-prefilled') # second field is marked as prefilled
assert resp.form['f1'].value == '2'
live_resp = app.post(
'/foo/live?modified_field_id=init&prefilled_1=on&prefilled_2=on', params=resp.form.submit_fields()
)
assert live_resp.json['result'] == {'2': {'visible': True, 'content': '2'}}
# check it has ?q=
assert urlopen.call_args[0][0] == 'http://remote.example.net/json?plop=&q=deux'
def test_field_live_block_string_prefill(pub, http_requests):
FormDef.wipe()
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(
id='123',
required=True,
label='Test',
type='string',
prefill={'type': 'string', 'value': '{{form_var_foo|default:""}}'},
),
]
block.store()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.StringField(id='1', label='test', type='string', varname='foo'),
fields.BlockField(id='2', label='test', type='block:foobar'),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get(formdef.get_url())
live_url = resp.html.find('form').attrs['data-live-url']
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.pyquery('[data-field-id="123"].widget-prefilled') # block/string
assert resp.form['f2$element0$f123'].value == ''
resp.form['f1'] = 'hello'
live_resp = app.post(live_url + '?modified_field_id=1&prefilled_123=on', params=resp.form.submit_fields())
assert live_resp.json['result'] == {
'1': {'visible': True},
'2': {'visible': True},
'2-123-0': {
'block_id': '2',
'block_row': 'element0',
'row': 0,
'content': 'hello',
'field_id': '123',
'visible': True,
},
}
def test_field_live_condition_unknown_page_id(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.PageField(id='0', label='2nd page', type='page'),
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(
type='string',
id='2',
label='Foo',
size='40',
required=True,
varname='foo',
condition={'type': 'django', 'value': 'form_var_bar == "bye"'},
),
fields.PageField(id='3', label='1st page', type='page'),
fields.StringField(type='string', id='4', label='Baz', size='40', required=True, varname='baz'),
]
formdef.store()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '2'}).attrs.get('style') == 'display: none'
resp.form['f1'] = 'hello'
params = resp.form.submit_fields()
params = [(key, value if key != 'page_id' else 'eiuiu') for key, value in params]
app.post('/foo/live', params=params)
def test_field_live_locked_prefilled_field(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='1', label='Bar', size='40', required=True, varname='bar'),
fields.StringField(
type='string',
id='2',
label='locked',
size='40',
required=True,
prefill={'type': 'string', 'value': 'bla {{form_var_bar}} bla', 'locked': True},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true'
resp.form['f1'] = 'hello'
live_resp = app.post('/foo/live?prefilled_2=on', params=resp.form.submit_fields())
assert live_resp.json['result']['2']['content'] == 'bla hello bla'
resp.form['f1'] = 'toto'
live_resp = app.post('/foo/live?modified_field_id=1&prefilled_2=on', params=resp.form.submit_fields())
assert live_resp.json['result']['2']['content'] == 'bla toto bla'
def test_field_live_locked_error_prefilled_field(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(
type='string',
id='2',
label='locked',
size='40',
required=True,
prefill={'type': 'string', 'value': 'bla {% if foo %}{{ foo }}{% end %}', 'locked': True},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert 'readonly' in resp.form['f2'].attrs
assert not resp.form['f2'].attrs.get('value')
@pytest.mark.parametrize('field_type', ['item', 'string', 'email'])
def test_dynamic_item_field_from_custom_view_on_cards(pub, field_type):
pub.role_class.wipe()
pub.custom_view_class.wipe()
user = create_user(pub)
role = pub.role_class(name='xxx')
role.store()
user.roles = [role.id]
user.is_admin = True
user.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'items'
carddef.digest_templates = {'default': '{{form_var_attr}}'}
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.fields = [
fields.StringField(id='1', type='string', label='string', varname='attr'),
]
if field_type == 'item':
carddef.fields.append(
fields.ItemField(id='0', type='item', label='item', varname='item', items=['foo', 'bar', 'baz'])
)
elif field_type == 'string':
carddef.fields.append(fields.StringField(id='0', type='string', label='string', varname='item'))
elif field_type == 'email':
carddef.fields.append(fields.EmailField(id='0', type='email', label='email', varname='item'))
carddef.store()
carddef.data_class().wipe()
baz_ids = set()
for i, value in enumerate(['foo', 'bar', 'baz'] * 10):
carddata = carddef.data_class()()
carddata.data = {
'0': value,
'1': 'attr%s' % i,
}
if field_type == 'item':
carddata.data['0_display'] = value
carddata.just_created()
carddata.store()
if value == 'baz':
baz_ids.add(str(carddata.id))
# create custom view
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/backoffice/data/items/')
assert resp.text.count('<tr') == 21 # thead + 20 items (max per page)
resp.forms['listing-settings']['filter-0'].checked = True
resp.forms['listing-settings']['filter-status'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter'].value = 'recorded'
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'as data source'
resp.forms['save-custom-view']['visibility'] = 'datasource'
resp = resp.forms['save-custom-view'].submit().follow()
if field_type == 'item':
# some javascript to get a text input for filter value
assert resp.forms['listing-settings']['filter-0-value'].attrs['data-allow-template']
assert 'custom value' in [x[2] for x in resp.forms['listing-settings']['filter-0-value'].options]
resp.forms['listing-settings']['filter-0-value'].force_value('{{ form_var_blah }}')
else:
resp.forms['listing-settings']['filter-0-value'] = '{{ form_var_blah }}'
resp = resp.forms['listing-settings'].submit()
assert resp.forms['listing-settings']['filter-0-value'].value == '{{ form_var_blah }}'
assert resp.text.count('<tr') == 1 # thead only
# save custom view with filter
resp = resp.forms['save-custom-view'].submit().follow()
custom_view = pub.custom_view_class.select()[0]
# use custom view as source
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
formdef.fields = [
fields.PageField(id='2', label='1st page', type='page'),
fields.ItemField(id='0', type='item', label='item', varname='blah', items=['foo', 'bar', 'baz']),
fields.ItemField(id='1', label='string', type='item', data_source=ds, display_disabled_items=True),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == [('', False, '---')]
resp.form['f0'] = 'baz'
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
assert len(live_resp.json['result']['1']['items']) == 10
assert {str(x['id']) for x in live_resp.json['result']['1']['items']} == baz_ids
resp.form['f1'].options = []
for item in live_resp.json['result']['1']['items']:
# simulate javascript filling the <select>
resp.form['f1'].options.append((str(item['id']), False, item['text']))
resp.form['f1'] = resp.form['f1'].options[0][0]
resp = resp.form.submit('submit') # -> validation page
assert 'Technical error' not in resp.text
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['1'] in baz_ids
assert formdef.data_class().select()[0].data['1_structured']['item'] == 'baz'
# same in autocomplete mode
formdef.fields[2].display_mode = 'autocomplete'
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f1_display'] = Hidden(form=resp.form, tag='input', name='f1_display', pos=10)
select2_url = resp.pyquery('select:last').attr['data-select2-url']
resp_json = app.get(select2_url + '?q=')
assert len(resp_json.json['data']) == 0
resp.form['f0'] = 'baz'
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
new_select2_url = live_resp.json['result']['1']['source_url']
resp_json = app.get(new_select2_url + '?q=')
assert len(resp_json.json['data']) == 10
assert {str(x['id']) for x in resp_json.json['data']} == baz_ids
resp.form['f1'].force_value(str(resp_json.json['data'][0]['id']))
resp.form.fields['f1_display'].force_value(resp_json.json['data'][0]['text'])
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['1'] in baz_ids
assert formdef.data_class().select()[0].data['1_structured']['item'] == 'baz'
# delete custom view
pub.loggederror_class.wipe()
custom_view.remove_self()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == []
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.formdef_id == formdef.id
assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"'
def test_dynamic_items_field_from_custom_view_on_cards(pub):
pub.role_class.wipe()
pub.custom_view_class.wipe()
user = create_user(pub)
role = pub.role_class(name='xxx')
role.store()
user.roles = [role.id]
user.is_admin = True
user.store()
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
items = ['foo', 'bar', 'baz', 'buz']
CardDef.wipe()
carddef = CardDef()
carddef.name = 'items'
carddef.digest_templates = {'default': '{{form_var_attr}} - {{form_var_items}}'}
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.fields = [
fields.ItemsField(id='0', type='items', label='items', varname='items', items=items),
fields.StringField(id='1', type='string', label='string', varname='attr'),
]
carddef.store()
carddef.data_class().wipe()
foo_bar_ids = set()
for i, (v1, v2) in enumerate(itertools.product(items, items)):
if v1 == v2:
continue
carddata = carddef.data_class()()
carddata.data = {
'0': [v1, v2],
'0_display': '%s,%s' % (v1, v2),
'1': 'attr%s' % i,
}
carddata.just_created()
carddata.store()
if 'foo' in {v1, v2}:
foo_bar_ids.add(str(carddata.id))
# create custom view
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/backoffice/data/items/?order_by=id')
assert resp.text.count('<tr') == 13 # thead + 12 items
resp.forms['listing-settings']['filter-0'].checked = True
resp.forms['listing-settings']['filter-status'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter'].value = 'recorded'
resp = resp.forms['listing-settings'].submit()
resp.forms['save-custom-view']['title'] = 'as data source'
resp.forms['save-custom-view']['visibility'] = 'datasource'
resp = resp.forms['save-custom-view'].submit().follow()
assert resp.forms['listing-settings']['filter-0-value'].attrs['data-allow-template']
assert 'custom value' in [x[2] for x in resp.forms['listing-settings']['filter-0-value'].options]
resp.forms['listing-settings']['filter-0-value'].force_value('{{ form_var_blah }}')
resp = resp.forms['listing-settings'].submit()
assert resp.forms['listing-settings']['filter-0-value'].value == '{{ form_var_blah }}'
assert resp.text.count('<tr') == 1 # thead only
# save custom view with filter
resp = resp.forms['save-custom-view'].submit().follow()
custom_view = pub.custom_view_class.select()[0]
# use custom view as source
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
formdef.fields = [
fields.PageField(id='2', label='1st page', type='page'),
fields.ItemsField(id='0', type='items', label='items', varname='blah', items=items),
fields.ItemField(id='1', label='string', type='item', data_source=ds, display_disabled_items=True),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == [('', False, '---')]
resp.form['f0$element0'] = 'foo'
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
assert len(live_resp.json['result']['1']['items']) == 6
assert {str(x['id']) for x in live_resp.json['result']['1']['items']} == foo_bar_ids
resp.form['f1'].options = []
for item in live_resp.json['result']['1']['items']:
# simulate javascript filling the <select>
resp.form['f1'].options.append((str(item['id']), False, item['text']))
resp.form['f1'] = resp.form['f1'].options[0][0]
resp = resp.form.submit('submit') # -> validation page
assert 'Technical error' not in resp.text
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['1'] in foo_bar_ids
assert formdef.data_class().select()[0].data['1_structured']['text'] == 'attr1 - foo,bar'
# same in autocomplete mode
formdef.fields[2].display_mode = 'autocomplete'
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f1_display'] = Hidden(form=resp.form, tag='input', name='f1_display', pos=10)
select2_url = resp.pyquery('select:last').attr['data-select2-url']
resp_json = app.get(select2_url + '?q=')
assert len(resp_json.json['data']) == 0
resp.form['f0$element0'] = 'foo'
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
new_select2_url = live_resp.json['result']['1']['source_url']
resp_json = app.get(new_select2_url + '?q=')
assert len(resp_json.json['data']) == 6
assert {str(x['id']) for x in resp_json.json['data']} == foo_bar_ids
resp.form['f1'].force_value(str(resp_json.json['data'][0]['id']))
resp.form.fields['f1_display'].force_value(resp_json.json['data'][0]['text'])
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['1'] in foo_bar_ids
assert formdef.data_class().select()[0].data['1_structured']['text'] == 'attr1 - foo,bar'
# delete custom view
pub.loggederror_class.wipe()
custom_view.remove_self()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == []
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.formdef_id == formdef.id
assert logged_error.summary == '[DATASOURCE] Unknown custom view "as-data-source" for CardDef "items"'
def test_dynamic_internal_id_from_custom_view_on_cards(pub):
pub.role_class.wipe()
pub.custom_view_class.wipe()
user = create_user(pub)
role = pub.role_class(name='xxx')
role.store()
user.roles = [role.id]
user.is_admin = True
user.store()
CardDef.wipe()
carddef = CardDef()
carddef.name = 'items'
carddef.digest_templates = {'default': '{{form_var_attr}}'}
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.fields = [
fields.StringField(id='1', type='string', label='string', varname='attr'),
fields.BoolField(
type='bool',
id='2',
varname='bool',
),
fields.StringField(id='3', type='string', label='string', varname='attr2'),
]
carddef.store()
carddef.data_class().wipe()
for i in range(10):
carddata = carddef.data_class()()
carddata.data = {
'1': 'attr%s' % i,
'2': bool(i % 2),
'3': str(i + 1),
}
carddata.just_created()
carddata.store()
custom_view = pub.custom_view_class()
custom_view.title = 'as datasource'
custom_view.formdef = carddef
custom_view.columns = {'list': [{'id': 'id'}]}
custom_view.filters = {
'filter-internal-id': 'on',
'filter-internal-id-value': '{{ form_var_num }}',
'filter-internal-id-operator': 'eq',
}
custom_view.visibility = 'datasource'
custom_view.store()
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.StringField(id='0', type='string', label='num', varname='num'),
fields.ItemField(id='1', label='item', type='item', data_source=ds, display_disabled_items=True),
]
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/backoffice/data/items/as-datasource/')
assert resp.text.count('<tr') == 1 # thead only
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == [('', False, '---')]
resp.form['f0'] = '3'
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
assert len(live_resp.json['result']['1']['items']) == 1
assert {x['id'] for x in live_resp.json['result']['1']['items']} == {3}
custom_view.filters['filter-internal-id-operator'] = 'ne'
custom_view.store()
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
assert len(live_resp.json['result']['1']['items']) == 9
assert {x['id'] for x in live_resp.json['result']['1']['items']} == {1, 2, 4, 5, 6, 7, 8, 9, 10}
custom_view.filters['filter-internal-id-operator'] = 'lte'
custom_view.store()
live_resp = app.post('/test/live?modified_field_id=0', params=resp.form.submit_fields())
assert len(live_resp.json['result']['1']['items']) == 3
assert {x['id'] for x in live_resp.json['result']['1']['items']} == {1, 2, 3}
formdef.fields[0] = fields.ComputedField(
id='0',
label='computed',
type='computed',
varname='num',
value_template='{{ cards|objects:"items"|filter_by:"bool"|filter_value:True|getlist:"form_internal_id"|list }}',
)
formdef.store()
custom_view.filters['filter-internal-id-operator'] = 'eq'
custom_view.store()
resp = get_app(pub).get('/test/')
assert {int(x[0]) for x in resp.form['f1'].options} == {2, 4, 6, 8, 10}
custom_view.filters['filter-internal-id-operator'] = 'ne'
custom_view.store()
resp = get_app(pub).get('/test/')
assert {int(x[0]) for x in resp.form['f1'].options} == {1, 3, 5, 7, 9}
for operator in ['lt', 'lte', 'gt', 'gte']:
pub.loggederror_class.wipe()
# list of values not allowed with theese operators
custom_view.filters['filter-internal-id-operator'] = operator
custom_view.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == [('', False, '---')]
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.formdef_id == formdef.id
assert (
logged_error.summary
== 'Invalid value "[2, 4, 6, 8, 10]" for custom view "as-datasource", CardDef "items", field "internal-id", operator "%s"'
% operator
)
# not integers
formdef.fields[
0
].value_template = '{{ cards|objects:"items"|filter_by:"bool"|filter_value:True|getlist:"attr"|list }}'
formdef.store()
custom_view.filters['filter-internal-id-operator'] = 'eq'
custom_view.store()
pub.loggederror_class.wipe()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == [('', False, '---')]
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.formdef_id == formdef.id
assert (
logged_error.summary == 'Invalid value "[\'attr1\', \'attr3\', \'attr5\', \'attr7\', \'attr9\']" '
'for custom view "as-datasource", CardDef "items", field "internal-id", operator "eq"'
)
# empty list
formdef.fields[
0
].value_template = (
'{{ cards|objects:"items"|filter_by:"attr"|filter_value:"unknown"|getlist:"form_internal_id"|list }}'
)
formdef.store()
custom_view.filters['filter-internal-id-operator'] = 'eq'
custom_view.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].options == [('', False, '---')]
custom_view.filters['filter-internal-id-operator'] = 'ne'
custom_view.store()
resp = get_app(pub).get('/test/')
assert {int(x[0]) for x in resp.form['f1'].options} == {1, 2, 3, 4, 5, 6, 7, 8, 9, 10}
# attr2 is filled with integers
formdef.fields[
0
].value_template = '{{ cards|objects:"items"|filter_by:"bool"|filter_value:True|getlist:"attr2"|list }}'
formdef.store()
resp = get_app(pub).get('/test/')
assert {int(x[0]) for x in resp.form['f1'].options} == {1, 3, 5, 7, 9}
def test_item_field_from_cards_check_lazy_live(pub):
create_user(pub)
CardDef.wipe()
carddef = CardDef()
carddef.name = 'items'
carddef.digest_templates = {'default': '{{form_var_name}}'}
carddef.fields = [
fields.StringField(id='0', label='string', varname='name'),
fields.StringField(id='1', label='string', varname='attr'),
]
carddef.store()
carddef.data_class().wipe()
for i, value in enumerate(['foo', 'bar', 'baz']):
carddata = carddef.data_class()()
carddata.data = {
'0': value,
'1': 'attr%s' % i,
}
carddata.just_created()
carddata.store()
ds = {'type': 'carddef:%s' % carddef.url_name}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(id='1', label='string', type='item', varname='item', data_source=ds),
fields.PageField(id='2', label='2nd page', type='page'),
fields.CommentField(id='3', label='live value: {{ form_var_item_live_var_attr }}', type='comment'),
fields.PageField(id='4', label='3rd page', type='page'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = '2'
resp = resp.form.submit('submit') # -> 2nd page
assert 'live value: attr1' in resp
# add a field with a condition on first page and third page
formdef.fields[1:1] = [
fields.StringField(
id='5',
label='field with condition',
type='string',
required=False,
condition={'type': 'django', 'value': '1'},
),
]
formdef.fields.append(
fields.StringField(
id='6',
label='second field with condition',
type='string',
required=False,
condition={'type': 'django', 'value': '1'},
)
)
formdef.store()
resp = get_app(pub).get('/test/')
resp.form['f1'] = '2'
resp = resp.form.submit('submit') # -> 2nd page
assert 'live value: attr1' in resp
resp = resp.form.submit('submit') # -> 3rd page
resp = resp.form.submit('previous') # -> 2nd page
assert 'live value: attr1' in resp
@mock.patch('wcs.qommon.misc.urlopen')
def test_field_live_condition_data_source_error(urlopen, pub):
ds = {'type': 'json', 'value': 'http://www.example.net/plop'}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.ItemField(id='1', label='string', data_source=ds, varname='foo'),
fields.StringField(
id='2',
label='bar',
required=True,
varname='bar',
condition={'type': 'django', 'value': 'form_var_foo_x == "bye"'},
),
]
formdef.store()
data = {'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux', 'x': 'bye'}]}
urlopen.side_effect = lambda *args: io.StringIO(json.dumps(data))
app = get_app(pub)
resp = app.get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' not in resp.form.fields
resp.form['f1'] = '2'
with mock.patch.object(NamedDataSource, 'get_structured_value', lambda *args: None):
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json == {
'reason': 'form deserialization failed: a datasource is unavailable',
'result': 'error',
}
live_resp = app.post('/foo/live', params=resp.form.submit_fields())
assert live_resp.json == {'result': {'1': {'visible': True}, '2': {'visible': True}}}
def test_comment_from_card_field(pub):
create_user(pub)
CardDef.wipe()
carddef = CardDef()
carddef.name = 'Template'
carddef.digest_templates = {'default': '{{ form_var_template }}'}
carddef.fields = [
fields.StringField(id='0', label='string', varname='template'),
]
carddef.store()
carddef.data_class().wipe()
for i, value in enumerate(['foo', 'bar']):
carddata = carddef.data_class()()
carddata.data = {
'0': "%s {{ form_var_foo }}" % value,
}
carddata.just_created()
carddata.store()
ds = {'type': 'carddef:%s' % carddef.url_name}
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.StringField(id='0', label='string', varname='foo'),
fields.ItemField(id='1', label='card', type='item', varname='card', data_source=ds),
fields.CommentField(
id='3',
label='X{{ form_var_card_live_var_template }}Y{{ form_var_card_live_var_template|as_template }}Z',
type='comment',
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'plop'
resp.form['f1'] = '1'
live_resp = app.post('/test/live', params=resp.form.submit_fields())
assert live_resp.json['result']['3']['content'] == '<p>Xfoo {{ form_var_foo }}Yfoo plopZ</p>'
resp.form['f1'] = '2'
live_resp = app.post('/test/live', params=resp.form.submit_fields())
assert live_resp.json['result']['3']['content'] == '<p>Xbar {{ form_var_foo }}Ybar plopZ</p>'