' in next_page.text
def test_form_submit_no_confirmation(pub):
formdef = create_formdef()
formdef.confirmation = False
formdef.store()
page = get_app(pub).get('/test/')
formdef.data_class().wipe()
next_page = page.forms[0].submit('submit')
assert next_page.status_int == 302
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
assert formdef.data_class().count() == 1
def test_form_string_field_submit(pub):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.store()
page = get_app(pub).get('/test/')
formdef.data_class().wipe()
next_page = page.forms[0].submit('submit') # but the field is required
assert next_page.pyquery('div.error').text() == 'required field'
next_page.forms[0]['f0'] = 'foobar'
next_page = next_page.forms[0].submit('submit')
assert 'Check values then click submit.' in next_page.text
next_page = next_page.forms[0].submit('submit')
assert next_page.status_int == 302
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {'0': 'foobar'}
def test_form_items_submit(pub):
formdef = create_formdef()
formdef.fields = [
fields.ItemsField(
id='0',
label='List of items',
type='items',
required=True,
varname='foo',
items=['Foo', 'Bar', 'Three', 'Four', 'Five', 'Six'],
)
]
formdef.store()
formdef.data_class().wipe()
page = get_app(pub).get('/test/')
assert 'List of items' in page.text
next_page = page.forms[0].submit('submit') # but the field is required
assert next_page.pyquery('div.error').text() == 'required field'
next_page.forms[0]['f0$element0'].checked = True
next_page.forms[0]['f0$element1'].checked = True
next_page = next_page.forms[0].submit('submit')
assert 'Check values then click submit.' in next_page.text
next_page = next_page.forms[0].submit('submit')
assert next_page.status_int == 302
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] == ['Foo', 'Bar']
assert data.data['0_display'] == 'Foo, Bar'
formdef.fields[0].min_choices = 2
formdef.fields[0].max_choices = 5
formdef.store()
page = get_app(pub).get('/test/')
page.forms[0]['f0$element0'].checked = True
page = page.forms[0].submit('submit')
assert page.pyquery('div.error').text() == 'You must select at least 2 answers.'
page.forms[0]['f0$element1'].checked = True
page.forms[0]['f0$element2'].checked = True
page.forms[0]['f0$element3'].checked = True
page.forms[0]['f0$element4'].checked = True
page.forms[0]['f0$element5'].checked = True
page = page.forms[0].submit('submit')
assert page.pyquery('div.error').text() == 'You must select at most 5 answers.'
page.forms[0]['f0$element5'].checked = False
page = next_page.forms[0].submit('submit').follow()
assert 'The form has been recorded' in page.text
def test_form_items_autocomplete(pub):
formdef = create_formdef()
formdef.fields = [
fields.ItemsField(
id='0',
label='List of items',
type='items',
required=True,
varname='foo',
display_mode='autocomplete',
items=['Foo', 'Bar', 'Three', 'Four', 'Five', 'Six'],
)
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert 'select2.min.js' in resp.text
resp = resp.forms[0].submit('submit') # but the field is required
assert resp.pyquery('div.error').text() == 'required field'
resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar'])
resp = resp.forms[0].submit('submit')
assert resp.pyquery('[name="f0[]"] option[selected]').text() == 'Foo Bar'
assert resp.pyquery('#form_f0[readonly]').val() == 'Foo, Bar'
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit').follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] == ['Foo', 'Bar']
assert data.data['0_display'] == 'Foo, Bar'
formdef.data_class().wipe()
formdef.fields[0].min_choices = 3
formdef.fields[0].max_choices = 4
formdef.store()
resp = get_app(pub).get('/test/')
resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar'])
resp = resp.forms[0].submit('submit')
assert resp.pyquery('div.error').text() == 'You must select at least 3 choices.'
assert resp.forms[0]['f0[]'].value == ['Foo', 'Bar']
resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar', 'Three', 'Four', 'Five'])
resp = resp.forms[0].submit('submit')
assert resp.pyquery('div.error').text() == 'You must select at most 4 choices.'
assert resp.forms[0]['f0[]'].value == ['Foo', 'Bar', 'Three', 'Four', 'Five']
resp.forms[0]['f0[]'].select_multiple(['Foo', 'Bar', 'Three'])
resp = resp.forms[0].submit('submit') # -> validation
resp = resp.forms[0].submit('submit').follow()
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] == ['Foo', 'Bar', 'Three']
# check empty options do not crash the widget
formdef.fields[0].items = []
formdef.store()
resp = get_app(pub).get('/test/')
def test_form_items_autocomplete_with_multiple_pages_no_confirmation(pub):
formdef = create_formdef()
formdef.confirmation = False
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemsField(
id='1',
label='List of items',
type='items',
required=True,
varname='foo',
display_mode='autocomplete',
items=['Foo', 'Bar', 'Three', 'Four', 'Five', 'Six'],
),
fields.PageField(id='2', label='2nd page', type='page'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1[]'].select_multiple(['Foo', 'Bar'])
resp = resp.forms[0].submit('submit') # -> 2nd page
resp = resp.forms[0].submit('submit') # -> submit
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['1'] == ['Foo', 'Bar']
assert data.data['1_display'] == 'Foo, Bar'
def test_form_string_with_invalid_xml_chars(pub):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f0'] = 'hello\x0b\x0cworld'
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
data = formdef.data_class().select()[0]
assert data.data == {'0': 'helloworld'}
def test_form_submit_handling_role_info(pub):
role = pub.role_class(name='xxx')
role.details = 'Managing service'
role.store()
formdef = create_formdef()
formdef.workflow_roles = {'_receiver': role.id}
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded' in resp
assert 'Your case is handled by' in resp
assert 'Managing service' in resp
formdata = formdef.data_class().select()[0]
formdata.jump_status('rejected')
formdata.store()
resp = resp.test_app.get(resp.request.url)
assert 'Your case has been handled by' in resp
def assert_current_page(resp, page_label):
for li_tag in resp.html.findAll('li'):
if 'current' in li_tag.attrs['class']:
assert li_tag.find_all('span')[-1].text == page_label
def test_form_multi_page(pub):
for initial_condition in (None, 'True'):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.fields[0].condition = {'type': 'python', 'value': initial_condition}
formdef.store()
page = get_app(pub).get('/test/')
formdef.data_class().wipe()
page.forms[0]['f1'] = 'foo'
assert page.forms[0].fields['submit'][0].value_if_submitted() == 'Next'
next_page = page.forms[0].submit('submit')
assert_current_page(next_page, '2nd page')
assert next_page.forms[0]['previous']
next_page.forms[0]['f3'] = 'bar'
next_page = next_page.forms[0].submit('submit')
assert_current_page(next_page, 'Validating')
assert 'Check values then click submit.' in next_page.text
next_page = next_page.forms[0].submit('submit')
assert next_page.status_int == 302
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {'1': 'foo', '3': 'bar'}
def test_form_multi_page_title_and_subtitle_as_template(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.TitleField(id='4', label='
', type='subtitle'
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.form['f1'] = '35 < 42'
resp = resp.form.submit('submit')
resp.form['f3'] = 'bar'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded' in resp.text
expected_label = '<i>title of second page 35 < 42</i>'
assert '
' % expected_label in resp.text
expected_label = '<i>subtitle of second page 35 < 42</i>'
assert '
' % expected_label in resp.text
def test_form_multi_page_condition(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(
id='2', label='2nd page', type='page', condition={'type': 'python', 'value': 'False'}
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1'] = 'foo'
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
assert resp.forms[0]['previous']
resp = resp.forms[0].submit('previous')
assert resp.forms[0]['f1']
def test_form_multi_page_condition_select(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(
id='1', label='select', type='item', required=True, varname='foo', items=['Foo', 'Bar']
),
fields.PageField(
id='2', label='2nd page', type='page', condition={'type': 'python', 'value': 'var_foo == "Foo"'}
),
fields.PageField(
id='3', label='3rd page', type='page', condition={'type': 'python', 'value': 'var_foo == "Bar"'}
),
fields.StringField(id='4', label='string 2'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert '2nd page' not in resp.text
assert '3rd page' not in resp.text
assert resp.forms[0]['f1'].value == 'Foo' # preset
resp = resp.forms[0].submit('submit')
assert '2nd page' in resp.text
assert '3rd page' not in resp.text
assert_current_page(resp, '2nd page')
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'Bar'
resp = resp.forms[0].submit('submit')
assert '2nd page' not in resp.text
assert '3rd page' in resp.text
assert_current_page(resp, '3rd page')
def test_form_multi_page_condition_select_new_varname(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(
id='1', label='select', type='item', required=True, varname='foo', items=['Foo', 'Bar']
),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'form_var_foo == "Foo"'},
),
fields.PageField(
id='3',
label='3rd page',
type='page',
condition={'type': 'python', 'value': 'form_var_foo == "Bar"'},
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert '2nd page' not in resp.text
assert '3rd page' not in resp.text
resp.forms[0]['f1'] = 'Foo'
resp = resp.forms[0].submit('submit')
assert '2nd page' in resp.text
assert '3rd page' not in resp.text
assert_current_page(resp, '2nd page')
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'Bar'
resp = resp.forms[0].submit('submit')
assert '2nd page' not in resp.text
assert '3rd page' in resp.text
assert_current_page(resp, '3rd page')
def test_form_multi_page_condition_checkbox(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.BoolField(id='1', label='checkbox', varname='checkbox'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'var_checkbox == "False"'},
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1'].checked = True
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
assert resp.forms[0]['previous']
resp = resp.forms[0].submit('previous')
assert resp.forms[0]['f1']
resp.forms[0]['f1'].checked = False
resp = resp.forms[0].submit('submit') # should go to second page
assert 'f3' in resp.forms[0].fields
def test_form_multi_page_condition_json_check(pub):
# make sure the json export has no value for fields from hidden pages
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.BoolField(id='1', label='checkbox', varname='checkbox'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'var_checkbox == "False"'},
),
fields.StringField(id='3', label='string 2', varname='st2'),
fields.PageField(
id='4',
label='3rd page',
type='page',
condition={'type': 'python', 'value': 'var_checkbox == "True"'},
),
fields.StringField(id='5', label='string 3', varname='st3'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.form['f1'].checked = True
resp = resp.form.submit('submit') # should go straight to 3rd page
assert 'f5' in resp.form.fields
resp.form['f5'] = 'VALUE F5'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['previous']
resp = resp.form.submit('previous')
resp = resp.form.submit('previous')
# back to first page
assert 'f1' in resp.form.fields
resp.form['f1'].checked = False
resp = resp.form.submit('submit') # should go to second page
assert 'f3' in resp.form.fields
resp.form['f3'] = 'VALUE F3'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit()
assert len(formdef.data_class().select()) == 1
json_dict = formdef.data_class().select()[0].get_json_export_dict()
assert json_dict['fields']['st2'] == 'VALUE F3'
assert json_dict['fields']['st3'] is None
def test_form_multi_page_condition_no_confirmation_json_check(pub):
# same as above but without the confirmation page.
formdef = create_formdef()
formdef.confirmation = False
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.BoolField(id='1', label='checkbox', varname='checkbox'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'var_checkbox == "False"'},
),
fields.StringField(id='3', label='string 2', varname='st2'),
fields.PageField(
id='4',
label='3rd page',
type='page',
condition={'type': 'python', 'value': 'var_checkbox == "True"'},
),
fields.StringField(id='5', label='string 3', varname='st3'),
fields.PageField(id='6', label='4th page', type='page'),
fields.CommentField(id='7', label='Check values then click submit.', type='comment'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.form['f1'].checked = True
resp = resp.form.submit('submit') # should go straight to 3rd page
assert 'f5' in resp.form.fields
resp.form['f5'] = 'VALUE F5'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['previous']
resp = resp.form.submit('previous')
resp = resp.form.submit('previous')
# back to first page
assert 'f1' in resp.form.fields
resp.form['f1'].checked = False
resp = resp.form.submit('submit') # should go to second page
assert 'f3' in resp.form.fields
resp.form['f3'] = 'VALUE F3'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
assert len(formdef.data_class().select()) == 1
json_dict = formdef.data_class().select()[0].get_json_export_dict()
assert json_dict['fields']['st2'] == 'VALUE F3'
assert json_dict['fields']['st3'] is None
def test_form_multi_page_condition_data_source(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.BoolField(id='1', label='checkbox', varname='checkbox'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'len(data_source.foobar) > 0'},
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
# add the named data source, empty
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'formula', 'value': repr([])}
data_source.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
assert resp.forms[0]['previous']
resp = resp.forms[0].submit('previous')
assert resp.forms[0]['f1']
# replace the named data source with one with items
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'formula', 'value': repr(['un', 'deux'])}
data_source.store()
resp = resp.forms[0].submit('submit') # should go to second page
assert 'f3' in resp.forms[0].fields
def test_form_multi_page_condition_data_source_with_form_variable(pub):
# this tries to recreate #8272 which is about a json datasource being
# used in a page condition and taking a value from the given page to
# filter its content. It is emulated here with a Python datasource
# being empty if a field was not set.
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='xxx', required=False),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'len(data_source.foobar) > 0'},
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
# add the named data source, related to a field on the first page
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'formula', 'value': 'form_var_xxx and [form_var_xxx] or []'}
data_source.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
assert resp.forms[0]['previous']
resp = resp.forms[0].submit('previous')
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'HELLO'
resp = resp.forms[0].submit('submit') # should go to second page
assert 'f3' in resp.forms[0].fields
def test_form_multi_page_condition_on_first_page(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(
id='0', label='1st page', type='page', condition={'type': 'python', 'value': 'False'}
),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
fields.PageField(id='4', label='3rd page', type='page'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
# should be on second page already
with pytest.raises(AssertionError):
resp.form.get('previous')
resp.form['f3'] = 'foo'
assert_current_page(resp, '2nd page')
resp = resp.form.submit('submit') # -> 3rd page
assert_current_page(resp, '3rd page')
resp = resp.form.submit('submit') # -> validation page
assert 'Check values then click submit.' in resp.text
assert resp.form['previous']
resp = resp.form.submit('previous') # -> 3rd page
assert_current_page(resp, '3rd page')
resp = resp.form.submit('previous') # -> 2nd page
assert_current_page(resp, '2nd page')
assert resp.form['f3']
with pytest.raises(AssertionError):
resp.form.get('previous')
def test_form_multi_page_condition_on_first_and_next(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(
id='0', label='1st page', type='page', condition={'type': 'python', 'value': 'True'}
),
fields.StringField(id='1', label='string', varname='val1'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'vars().get("form_var_val1") == "foo"'},
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'foo'
resp = resp.form.submit('submit')
assert resp.form['f3']
resp.form['f3'] = 'bar'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
assert len(formdef.data_class().select()) == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {'1': 'foo', '3': 'bar'}
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'xxx'
resp = resp.form.submit('submit')
with pytest.raises(AssertionError):
assert resp.form['f3']
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
assert len(formdef.data_class().select()) == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data.get('1') == 'xxx'
assert data.data.get('3') is None
def test_form_multi_page_condition_no_visible_page(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(
id='0', label='1st page', type='page', condition={'type': 'python', 'value': 'False'}
),
fields.StringField(id='1', label='string'),
fields.PageField(
id='2', label='2nd page', type='page', condition={'type': 'python', 'value': 'False'}
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert 'error-page' in resp
assert 'This form has no visible page.' in resp
def test_form_multi_page_many_conditions(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.PageField(
id='0', label='2nd page', type='page', condition={'type': 'python', 'value': 'True'}
),
]
formdef.store()
formdef.data_class().wipe()
with mock.patch('wcs.qommon.publisher.Substitutions.invalidate_cache') as invalidate_cache:
get_app(pub).get('/test/')
call_count = invalidate_cache.call_count
for i in range(30):
formdef.fields.append(
fields.PageField(
id=str(i + 2),
label='page %s' % (i + 2),
type='page',
condition={'type': 'python', 'value': 'True'},
)
)
formdef.store()
# check the cache doesn't get invalidated for every page
with mock.patch('wcs.qommon.publisher.Substitutions.invalidate_cache') as invalidate_cache:
get_app(pub).get('/test/')
assert invalidate_cache.call_count <= call_count
def test_form_multi_page_condition_stored_values(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'form_var_foo == "toto"'},
),
fields.StringField(id='3', label='string 2'),
fields.PageField(id='4', label='3rd page', type='page'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'toto'
resp = resp.form.submit('submit') # -> page 2
resp.form['f3'] = 'bar'
resp = resp.form.submit('submit') # -> page 3
resp = resp.form.submit('submit') # -> validation page
assert 'Check values then click submit.' in resp.text
assert 'bar' in resp.text
resp = resp.form.submit('previous') # -> page 3
resp = resp.form.submit('previous') # -> page 2
resp = resp.form.submit('previous') # -> page 1
resp.form['f1'] = 'blah'
resp = resp.form.submit('submit') # -> page 3
resp = resp.form.submit('submit') # -> validation page
assert 'Check values then click submit.' in resp.text
assert 'bar' not in resp.text
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'blah'
assert formdata.data.get('3') is None
# same without validation page
formdef.confirmation = False
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'toto'
resp = resp.form.submit('submit') # -> page 2
resp.form['f3'] = 'bar'
resp = resp.form.submit('submit') # -> page 3
resp = resp.form.submit('previous') # -> page 2
resp = resp.form.submit('previous') # -> page 1
resp.form['f1'] = 'blah'
resp = resp.form.submit('submit') # -> page 3
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'blah'
assert formdata.data.get('3') is None
def test_form_multi_page_post_conditions(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(
id='2', label='2nd page', type='page', condition={'type': 'python', 'value': 'False'}
),
fields.StringField(id='3', label='string 2'),
fields.PageField(id='4', label='3rd page', type='page'),
fields.StringField(id='5', label='string 3', varname='bar'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'foo'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].data['1'] == 'foo'
assert formdef.data_class().select()[0].data['5'] == 'bar'
formdef.fields[4].post_conditions = [
{'condition': {'type': 'python', 'value': 'False'}, 'error_message': 'You shall not pass.'},
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'foo'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'errornotice' in resp.text
assert 'global-errors' in resp.text
assert 'You shall not pass.' in resp.text
formdef.fields[4].post_conditions = [
{
'condition': {'type': 'python', 'value': 'form_var_foo == "foo"'},
'error_message': 'You shall not pass.',
},
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'bar'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'errornotice' in resp.text
assert 'You shall not pass.' in resp.text
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'foo'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
# check a post-condition raising an exception, they should always fail.
formdef.fields[4].post_conditions = [
{'condition': {'type': 'python', 'value': '1/0'}, 'error_message': 'You shall not pass.'},
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'bar'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'errornotice' in resp.text
assert 'You shall not pass.' in resp.text
# check a post-condition referring to a field on the same page
formdef.fields[4].post_conditions = [
{
'condition': {'type': 'python', 'value': 'form_var_bar == "bar"'},
'error_message': 'You shall not pass.',
},
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'bar'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'foo'
resp = resp.forms[0].submit('submit')
assert 'errornotice' in resp.text
assert 'You shall not pass.' in resp.text
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'bar'
resp = resp.forms[0].submit('submit')
resp.forms[0]['f5'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
def test_form_multi_page_conditions_and_post_conditions(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(
id='0',
label='1st page',
type='page',
post_conditions=[
{
'condition': {'type': 'python', 'value': 'form_var_bar == "bar"'},
'error_message': 'You shall not pass.',
}
],
),
fields.StringField(id='1', label='string', varname='bar'),
fields.PageField(id='3', label='2nd page', type='page'),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'bar'
resp = resp.form.submit('submit')
assert_current_page(resp, '2nd page')
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'foo'
resp = resp.form.submit('submit')
assert 'You shall not pass.' in resp.text
# add a conditional page, this will cause pages to be evaluated first
# (and would trigger #25197)
formdef.fields.append(
fields.PageField(id='4', label='3rd page', type='page', condition={'type': 'python', 'value': 'True'})
)
formdef.store()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'bar'
resp = resp.form.submit('submit')
assert_current_page(resp, '2nd page')
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'foo'
resp = resp.form.submit('submit')
assert 'You shall not pass.' in resp.text
def test_form_multi_page_page_name_as_title(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.TitleField(id='4', label='1st page', type='title'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
page = get_app(pub).get('/test/')
formdef.data_class().wipe()
page.forms[0]['f1'] = 'foo'
next_page = page.forms[0].submit('submit')
assert_current_page(next_page, '2nd page')
assert next_page.forms[0]['previous']
next_page.forms[0]['f3'] = 'bar'
next_page = next_page.forms[0].submit('submit')
assert_current_page(next_page, 'Validating')
assert 'Check values then click submit.' in next_page.text
assert next_page.text.count('1st page') == 2 # in steps and in main body
# add a comment that will not be displayed and should therefore not be
# considered.
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.CommentField(id='5', label='bla bla bla', type='comment'),
fields.TitleField(id='4', label='1st page', type='title'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
page = get_app(pub).get('/test/')
formdef.data_class().wipe()
page.forms[0]['f1'] = 'foo'
next_page = page.forms[0].submit('submit')
assert_current_page(next_page, '2nd page')
assert next_page.forms[0]['previous']
next_page.forms[0]['f3'] = 'bar'
next_page = next_page.forms[0].submit('submit')
assert_current_page(next_page, 'Validating')
assert 'Check values then click submit.' in next_page.text
assert next_page.text.count('1st page') == 2 # in steps and in main body
def test_form_submit_with_user(pub, emails):
create_user(pub)
formdef = create_formdef()
page = login(get_app(pub), username='foo', password='foo').get('/test/')
formdef.data_class().wipe()
next_page = page.forms[0].submit('submit')
assert 'Check values then click submit.' in next_page.text
next_page = next_page.forms[0].submit('submit')
assert next_page.status_int == 302
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
assert formdef.data_class().count() == 1
assert '
' in next_page.text
# check the user received a copy by email
assert emails.get('New form (test)')
assert emails.get('New form (test)')['email_rcpt'] == ['foo@localhost']
def test_form_submit_with_just_disabled_user(pub, emails):
user = create_user(pub)
formdef = create_formdef()
app = login(get_app(pub), username='foo', password='foo')
formdef.data_class().wipe()
resp = app.get('/test/')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp
user.is_active = False
user.store()
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'Sorry, your session have been lost.' in resp
def test_form_titles(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.TitleField(id='4', label='1st page', type='title'),
fields.SubtitleField(id='5', label='subtitle of 1st page', type='subtitle'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.TitleField(id='6', label='title of second page', type='title'),
fields.StringField(id='3', label='string 2', required=False),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert '
1st page/h3>' not in resp.text
assert 'subtitle of 1st page ' in resp.text
resp.form['f1'] = 'foo'
resp = resp.form.submit('submit')
assert 'title of second page ' in resp.text
resp.form['f3'] = 'foo'
resp = resp.form.submit('submit') # -> validation page
assert '1st page ' in resp.text
assert 'subtitle of 1st page ' in resp.text
assert 'title of second page ' in resp.text
resp = resp.form.submit('submit').follow() # -> submit
assert '1st page ' in resp.text
assert '
1st page ' not in resp.text
assert '
subtitle of 1st page ' in resp.text
assert '
title of second page ' in resp.text
def test_form_summary_empty_pages(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='toto'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'form_var_toto == "foo"'},
),
fields.TitleField(id='6', label='title in second page', type='title'),
fields.StringField(id='3', label='string'),
fields.PageField(id='4', label='3rd page', type='page'),
fields.StringField(id='5', label='string'),
fields.PageField(id='7', label='4th page', type='page'),
fields.CommentField(id='8', label='Bla bla bla', type='comment'),
]
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/') # -> 1st page
resp.form['f1'] = 'foo'
resp = resp.form.submit('submit') # -> 2nd page
resp.form['f3'] = 'bar'
resp = resp.form.submit('submit') # -> 3rd page
resp.form['f5'] = 'baz'
resp = resp.form.submit('submit') # -> 4th page
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit')
formdata_id = resp.location.split('/')[-2]
resp = resp.follow() # -> submit
assert '1st page ' in resp.text
assert '2nd page ' in resp.text
assert '3rd page ' in resp.text
assert '4th page ' not in resp.text
resp = app.get('/test/') # -> 1st page
resp.form['f1'] = 'foo'
resp = resp.form.submit('submit') # -> 2nd page
resp.form['f3'] = 'bar'
resp = resp.form.submit('previous') # -> 1st page
resp.form['f1'] = 'baz'
resp = resp.form.submit('submit') # -> 3rd page
resp.form['f5'] = 'baz'
resp = resp.form.submit('submit') # -> 4th page
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit').follow() # -> submit
assert '1st page ' in resp.text
assert '2nd page ' not in resp.text
assert '3rd page ' in resp.text
assert '4th page ' not in resp.text
# change condition to have second page never displayed
formdef.fields[2].condition['value'] = False
formdef.store()
formdata = formdef.data_class().get(formdata_id)
resp = app.get(formdata.get_url())
# it was filled by user, it should still appear (conditions should not be
# replayed)
assert '1st page ' in resp.text
assert '2nd page ' in resp.text
assert '3rd page ' in resp.text
assert '4th page ' not in resp.text
def test_form_display_locations(pub):
formdef = create_formdef()
formdef.fields = [
fields.StringField(id='1', label='string1', display_locations=[]),
fields.StringField(id='2', label='string2', display_locations=['validation']),
fields.StringField(id='3', label='string3', display_locations=['summary']),
fields.CommentField(
id='4', label='Bla bla bla', type='comment', display_locations=['validation', 'summary']
),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'plop1'
resp.form['f2'] = 'plop2'
resp.form['f3'] = 'plop3'
resp = resp.form.submit('submit') # -> validation
pq = resp.pyquery.remove_namespaces()
assert pq('div[style="display: none;"] [name=f1]')
assert not pq('div[style="display: none;"] [name=f2]')
assert pq('div[style="display: none;"] [name=f3]')
assert 'Bla bla bla' in resp.text
resp = resp.form.submit('submit').follow() # -> submit
assert formdef.data_class().select()[0].data['1'] == 'plop1'
assert formdef.data_class().select()[0].data['2'] == 'plop2'
assert formdef.data_class().select()[0].data['3'] == 'plop3'
assert 'plop1' not in resp.text
assert 'plop2' not in resp.text
assert 'plop3' in resp.text
assert 'Bla bla bla' in resp.text
def test_multipage_form_display_locations(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string1', display_locations=[]),
fields.PageField(id='2', label='2nd page', type='page'),
fields.CommentField(id='3', label='Bla bla bla', type='comment', display_locations=['validation']),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'plop1'
resp = resp.form.submit('submit') # -> page 2
resp = resp.form.submit('submit') # -> validation
pq = resp.pyquery.remove_namespaces()
assert '1st page ' not in resp.text # page 1 title not displayed
assert pq('div[style="display: none;"] [name=f1]') # but page 1 field included, hidden
assert '2nd page ' in resp.text # page 2 title
assert 'Bla bla bla' in resp.text # and page 2 comment field
def test_form_visit_existing(pub):
user = create_user(pub)
formdef = create_formdef()
login(get_app(pub), username='foo', password='foo').get('/test/')
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.store()
formdata_user = formdef.data_class()()
formdata_user.user_id = user.id
formdata_user.store()
resp = get_app(pub).get('/test/%s/' % formdata.id)
assert resp.location.startswith('http://example.net/login/?next=')
resp = get_app(pub).get('/test/%s/' % formdata_user.id)
assert resp.location.startswith('http://example.net/login/?next=')
resp = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % formdata_user.id)
assert 'The form has been recorded on' in resp
def test_form_no_tracking_code(pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = False
formdef.store()
resp = get_app(pub).get('/test/')
assert 'Tracking code ' not in resp.text
def test_form_no_tracking_code_variable(pub):
create_user(pub)
FormDef.wipe()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.CommentField(id='3', label='xxx{{form_tracking_code|default:""}}yyy
', type='comment'),
]
formdef.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
resp.form['f1'] = 'foo'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {
'result': 'error',
'reason': 'missing data',
}
resp = resp.form.submit('submit')
assert_current_page(resp, '2nd page')
assert 'xxxyyy' in resp.text
resp = resp.form.submit('submit')
assert_current_page(resp, 'Validating')
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {'1': 'foo'}
assert data.tracking_code is None
def get_displayed_tracking_code(resp):
tracking_code = None
for a_tag in resp.html.findAll('a'):
if 'code/' in a_tag['href']:
tracking_code = a_tag.text
break
return tracking_code
def test_form_tracking_code(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
resp.forms[0]['f0'] = 'foobar'
resp = resp.forms[0].submit('submit')
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].is_draft()
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert formdef.data_class().select()[0].data['0'] == 'foobar'
formdata_id = formdef.data_class().select()[0].id
# check we can load the formdata as a draft
resp = get_app(pub).get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
# check anonymous user can't get to it from the URL
pub.session_manager.session_class.wipe()
resp = get_app(pub).get('http://example.net/test/%s/' % formdata_id)
assert resp.location.startswith('http://example.net/login')
# or logged users that didn't enter the code:
create_user(pub)
login(get_app(pub), username='foo', password='foo').get(
'http://example.net/test/%s/' % formdata_id, status=403
)
# check we can also get to it as a logged user
pub.session_manager.session_class.wipe()
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp.forms[0]['code'] = tracking_code.lower()
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code.lower()
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
# go back as anonymous
pub.session_manager.session_class.wipe()
resp = get_app(pub).get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
resp = resp.forms[1].submit('previous')
assert resp.forms[1]['f0'].value == 'foobar'
# check submitted form keeps the tracking code
resp.forms[1]['f0'] = 'barfoo'
resp = resp.forms[1].submit('submit') # -> confirmation page
resp = resp.forms[1].submit('submit') # -> done
resp = resp.follow()
assert 'barfoo' in resp.text
assert formdef.data_class().count() == 1 # check the draft one has been removed
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert formdef.data_class().select()[0].status == 'wf-new'
assert formdef.data_class().select()[0].data['0'] == 'barfoo'
formdata_id = formdef.data_class().select()[0].id
# check we can still go back to it
app = get_app(pub)
resp = app.get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert 'form_comment' in resp.text # makes sure user is treated as submitter
resp.forms[0]['comment'] = 'hello world'
resp = resp.forms[0].submit()
assert formdef.data_class().get(formdata_id).evolution[-1].get_plain_text_comment() == 'hello world'
# check we can also use it with lowercase letters.
app = get_app(pub)
resp = app.get('/')
resp.forms[0]['code'] = tracking_code.lower()
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code.lower()
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
def test_form_tracking_code_verification(pub, nocache):
formdef = create_formdef()
formdef.fields = [
fields.StringField(id='0', label='string1', required=False),
fields.StringField(id='1', label='string2', required=False),
fields.DateField(id='2', label='date', required=False),
]
formdef.enable_tracking_codes = True
formdef.tracking_code_verify_fields = ['0', '1', '2']
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
resp.forms[0]['f0'] = 'foobar1'
resp.forms[0]['f1'] = 'foobar2'
resp.forms[0]['f2'] = '2022-01-01'
resp = resp.forms[0].submit('submit')
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].is_draft()
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert formdef.data_class().select()[0].data['0'] == 'foobar1'
assert formdef.data_class().select()[0].data['1'] == 'foobar2'
assert formdef.data_class().select()[0].data['2'].tm_year == 2022
formdata = formdef.data_class().select()[0]
formdata_id = formdata.id
resp = get_app(pub).get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert 'Access rights verification' in resp
resp.forms[0]['f0'] = 'foobar1'
resp.forms[0]['f1'] = 'foobar2'
resp.forms[0]['f2'] = '2022-01-01'
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
# check anonymous user can't get to it from the URL
pub.session_manager.session_class.wipe()
resp = get_app(pub).get('http://example.net/test/%s/' % formdata_id)
assert resp.location.startswith('http://example.net/login')
# or logged users that didn't enter the code:
create_user(pub)
login(get_app(pub), username='foo', password='foo').get(
'http://example.net/test/%s/' % formdata_id, status=403
)
# verification failure
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert 'Access rights verification' in resp
resp.forms[0]['f0'] = 'foobar1' # ok
resp.forms[0]['f1'] = 'barfoo2' # ko
resp.forms[0]['f2'] = '2022-01-01' # ok
resp = resp.forms[0].submit('submit')
assert 'Access denied: this content does not match the form' in resp
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert 'Access rights verification' in resp
resp.forms[0]['f0'] = 'foobar1' # ok
resp.forms[0]['f1'] = 'foobar2' # ok
resp.forms[0]['f2'] = '2022-01-02' # ko
resp = resp.forms[0].submit('submit')
assert 'Access denied: this content does not match the form' in resp
# draft with an empty field: do not verify it
formdata.data['0'] = None
formdata.store()
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert 'Access rights verification' in resp
assert 'f0' not in resp.forms[0].fields
assert 'f1' in resp.forms[0].fields
resp.forms[0]['f1'] = 'foobar2'
resp.forms[0]['f2'] = '2022-01-01'
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
# empty draft: no verification
formdata.data['1'] = None
formdata.data['2'] = None
formdata.store()
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
# not a draft: all validation fields are required
formdata.status = 'wf-new'
formdata.data['0'] = 'foobar1'
formdata.store()
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert 'Access rights verification' in resp
assert 'f0' in resp.forms[0].fields
assert 'f1' in resp.forms[0].fields
assert 'f2' in resp.forms[0].fields
resp.forms[0]['f0'] = 'foobar1'
resp.forms[0]['f1'] = ''
resp.forms[0]['f2'] = ''
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert 'foobar1' in resp.text
assert 'form_comment' in resp.text # user is treated as submitter
# verification failure
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert 'Access rights verification' in resp
resp.forms[0]['f0'] = 'foobar1' # ok
resp.forms[0]['f1'] = 'not empty' # ko
resp.forms[0]['f2'] = '' # ok
resp = resp.forms[0].submit('submit')
assert 'Access denied: this content does not match the form.' in resp
resp = get_app(pub).get('http://example.net/code/%s/load' % tracking_code)
assert 'Access rights verification' in resp
resp.forms[0]['f0'] = 'foobar1' # ok
resp.forms[0]['f1'] = '' # ok
resp.forms[0]['f2'] = '2022-02-02' # ko (not empty)
resp = resp.forms[0].submit('submit')
assert 'Access denied: this content does not match the form.' in resp
def test_form_tracking_code_rate_limit(pub, freezer):
pub.load_site_options()
if not pub.site_options.has_section('options'):
pub.site_options.add_section('options')
pub.site_options.set('options', 'rate-limit', '2/2s')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
# twice
freezer.move_to('2018-12-01T00:00:00')
get_app(pub).get('/code/ABC/load', status=404)
get_app(pub).get('/code/ABC/load', status=404)
# and out
get_app(pub).get('/code/ABC/load', status=403)
get_app(pub).get('/code/ABC/load', status=403)
# wait two second
freezer.move_to('2018-12-01T00:00:02')
# and ok again
get_app(pub).get('/code/ABC/load', status=404)
def test_form_tracking_code_as_user(pub, nocache):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
resp.forms[0]['f0'] = 'foobar'
resp = resp.forms[0].submit('submit')
tracking_code_2 = get_displayed_tracking_code(resp)
assert tracking_code == tracking_code_2
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].is_draft()
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert formdef.data_class().select()[0].data['0'] == 'foobar'
formdata_id = formdef.data_class().select()[0].id
# check we can load the formdata as a draft
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
resp = resp.forms[1].submit('previous')
assert resp.forms[1]['f0'].value == 'foobar'
# check submitted form keeps the tracking code
resp.forms[1]['f0'] = 'barfoo'
resp = resp.forms[1].submit('submit') # -> confirmation page
resp = resp.forms[1].submit('submit') # -> done
resp = resp.follow()
assert 'barfoo' in resp.text
assert formdef.data_class().count() == 1 # check the draft one has been removed
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert str(formdef.data_class().select()[0].user_id) == str(user.id)
assert formdef.data_class().select()[0].status == 'wf-new'
assert formdef.data_class().select()[0].data['0'] == 'barfoo'
formdata_id = formdef.data_class().select()[0].id
# check we can still go back to it
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert 'form_comment' in resp.text # makes sure user is treated as submitter
resp.forms[0]['comment'] = 'hello world'
resp = resp.forms[0].submit()
assert formdef.data_class().get(formdata_id).evolution[-1].get_plain_text_comment() == 'hello world'
# and check we can also get back to it as anonymous
app = get_app(pub)
resp = app.get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert 'form_comment' in resp.text # makes sure user is treated as submitter
# and check a bot is not allowed to get it
app = get_app(pub)
resp = app.get('/code/%s/load' % tracking_code, headers={'User-agent': 'Googlebot'}, status=403)
def test_form_tracking_code_prefill(pub, nocache):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string', prefill={'type': 'user', 'value': 'email'})]
formdef.enable_tracking_codes = True
formdef.store()
# first time
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert '
Tracking code ' in resp.text
assert 'You already started to fill this form.' not in resp.text
resp.forms[0]['f0'] = 'foobar'
resp = resp.forms[0].submit('submit')
# second time, invitation to load an existing draft
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert '
Tracking code ' in resp.text
assert 'You already started to fill this form.' in resp.text
def test_form_empty_tracking_code(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
# check we get a 404 if we use the tracking code before it gets any data
app = get_app(pub)
resp = app.get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow(status=404)
def test_form_tracking_code_email(pub, emails, nocache):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string'), fields.StringField(id='1', label='string2')]
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'barfoo'
# autosave will be made using javascript in real world
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
resp = get_app(pub).get('/test/code/%s/' % tracking_code)
assert '
Keep your tracking code ' in resp.text
resp.forms[0]['email'] = 'foo@localhost'
resp = resp.forms[0].submit()
assert emails.get('Tracking Code reminder')
assert tracking_code in emails.get('Tracking Code reminder')['payload']
assert resp.location == 'http://example.net/test/code/%s/load' % tracking_code
resp = resp.follow()
resp = resp.follow()
resp = resp.follow()
assert resp.forms[1]['f0'].value == 'barfoo'
def test_form_tracking_code_email_and_verification(pub, emails, nocache):
formdef = create_formdef()
formdef.fields = [
fields.StringField(id='0', label='string1', required=False),
fields.StringField(id='1', label='string2', required=False),
fields.DateField(id='2', label='date', required=False),
]
formdef.enable_tracking_codes = True
formdef.tracking_code_verify_fields = ['0', '1', '2']
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'barfoo'
# autosave will be made using javascript in real world
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
resp = app.get('/test/code/%s/' % tracking_code)
assert '
Keep your tracking code ' in resp.text
resp.forms[0]['email'] = 'foo@localhost'
resp = resp.forms[0].submit()
assert emails.get('Tracking Code reminder')
assert tracking_code in emails.get('Tracking Code reminder')['payload']
assert resp.location == 'http://example.net/test/code/%s/load' % tracking_code
# returns to the form, without verification: formdata is mine
resp = resp.follow()
resp = resp.follow()
resp = resp.follow()
assert resp.forms[1]['f0'].value == 'barfoo'
def test_form_tracking_code_email_antibot(pub, emails, nocache):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string'), fields.StringField(id='1', label='string2')]
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'barfoo'
# autosave will be made using javascript in real world
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
resp = get_app(pub).get('/test/code/%s/' % tracking_code)
assert '
Keep your tracking code ' in resp.text
resp.forms[0]['email'] = 'foo@localhost'
resp.forms[0]['validation'].checked = True # stupit bot will do that
resp = resp.forms[0].submit()
assert not emails.count()
def test_form_tracking_code_email_too_early(pub, emails, nocache):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string'), fields.StringField(id='1', label='string2')]
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
# do nothing more: formdata does not even exists
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
resp = get_app(pub).get('/test/code/%s/' % tracking_code)
assert 'Form is empty: tracking code is not yet available.' in resp.text
def test_form_tracking_code_remove_draft(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
resp.forms[0]['f0'] = 'foobar'
resp = resp.forms[0].submit('submit')
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].is_draft()
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert formdef.data_class().select()[0].data['0'] == 'foobar'
assert str(formdef.data_class().select()[0].page_no) == '1'
formdata_id = formdef.data_class().select()[0].id
app = get_app(pub)
# visit page, check there's no remove draft button
resp = app.get('/test/')
assert '
Tracking code ' in resp.text
assert 'removedraft' not in resp.text
# check we can load the formdata as a draft
resp = app.get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
assert '
Tracking code ' in resp.text
assert 'removedraft' in resp.text
resp = resp.forms[1].submit('previous')
assert resp.forms[1]['f0'].value == 'foobar'
resp = resp.forms[0].submit() # remove draft
assert resp.location == 'http://example.net/'
assert formdef.data_class().count() == 0
def test_form_tracking_code_remove_empty_draft(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
resp.forms[0]['f0'] = 'foobar'
resp = resp.forms[0].submit('submit')
resp = resp.forms[0].submit('previous')
resp_autosave = app.post('/test/autosave', params=resp.form.submit_fields())
assert resp_autosave.json == {'result': 'success'}
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].is_draft()
assert formdef.data_class().select()[0].tracking_code == tracking_code
assert formdef.data_class().select()[0].data['0'] == 'foobar'
assert str(formdef.data_class().select()[0].page_no) == '0'
# make draft empty
formdata = formdef.data_class().select()[0]
formdata.data = {}
formdata.store()
formdata_id = formdef.data_class().select()[0].id
app = get_app(pub)
# check we can load the formdata as a draft
resp = app.get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata_id
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
assert '
Tracking code ' in resp.text
assert 'removedraft' in resp.text
assert resp.forms[1]['f0'].value == ''
resp = resp.forms[0].submit() # remove draft
assert resp.location == 'http://example.net/'
assert formdef.data_class().count() == 0
def test_form_discard_draft(pub, nocache):
create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = False
formdef.store()
formdef.data_class().wipe()
# anonymous user, no tracking code (-> no draft)
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'foobar'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert [x.status for x in formdef.data_class().select()] == []
assert 'Cancel' in resp.text
assert 'Discard' not in resp.text
resp = resp.form.submit('cancel')
# anonymous user, tracking code (-> draft)
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'foobar'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert [x.status for x in formdef.data_class().select()] == ['draft']
assert 'Cancel' not in resp.text
assert 'Discard' in resp.text
resp = resp.form.submit('cancel')
assert [x.status for x in formdef.data_class().select()] == [] # discarded
# logged-in user, no tracking code
formdef.enable_tracking_codes = False
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f0'] = 'foobar'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert [x.status for x in formdef.data_class().select()] == ['draft']
assert 'Cancel' not in resp.text
assert 'Discard' in resp.text
resp = resp.form.submit('cancel')
assert [x.status for x in formdef.data_class().select()] == [] # discarded
# logged-in user, tracking code
formdef.enable_tracking_codes = True
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f0'] = 'foobar'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert [x.status for x in formdef.data_class().select()] == ['draft']
assert 'Cancel' not in resp.text
assert 'Discard' in resp.text
resp = resp.form.submit('cancel')
assert [x.status for x in formdef.data_class().select()] == [] # discarded
# anonymous user, tracking code, recalled
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f0'] = 'foobar'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert [x.status for x in formdef.data_class().select()] == ['draft']
assert 'Cancel' not in resp.text
assert 'Discard' in resp.text
tracking_code = get_displayed_tracking_code(resp)
resp = get_app(pub).get('/')
resp.form['code'] = tracking_code
resp = resp.form.submit().follow().follow().follow()
assert resp.forms[1]['f0'].value == 'foobar'
assert 'Cancel' in resp.text
assert 'Discard Draft' in resp.text
resp = resp.forms[1].submit('cancel')
assert [x.status for x in formdef.data_class().select()] == ['draft']
# logged-in user, no tracking code, recalled
formdef.data_class().wipe()
formdef.enable_tracking_codes = False
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f0'] = 'foobar'
resp = resp.form.submit('submit')
resp = resp.form.submit('previous')
assert [x.status for x in formdef.data_class().select()] == ['draft']
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp = resp.click('Continue with draft').follow()
assert 'Cancel' in resp.text
assert 'Discard Draft' in resp.text
resp = resp.forms[1].submit('cancel')
assert [x.status for x in formdef.data_class().select()] == ['draft']
def test_form_invalid_tracking_code(pub, nocache):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = True
formdef.store()
# create a secondary formdef, to always have the tracking code form
# displayed on homepage
formdef2 = FormDef()
formdef2.name = 'test2'
formdef2.fields = []
formdef2.enable_tracking_codes = True
formdef2.store()
resp = get_app(pub).get('/')
formdata = formdef.data_class()()
formdata.data = {'0': 'foobar'}
formdata.store()
# check we can go back to it
formdef.data_class().wipe()
code = pub.tracking_code_class()
code.formdata = formdata # this will save it again
code.store()
resp.forms[0]['code'] = code.id
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % code.id
resp = resp.follow()
assert resp.location == 'http://example.net/test/%s/' % formdata.id
resp = resp.follow()
# check we get a not found error message on non-existent code
fake_code = TrackingCode().get_new_id()
resp = get_app(pub).get('/')
resp.forms[0]['code'] = fake_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % fake_code
resp = resp.follow(status=404)
# check we also get an error if tracking code access is disabled after the
# fact
formdef.enable_tracking_codes = False
formdef.store()
resp = get_app(pub).get('/')
resp.forms[0]['code'] = code.id
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % code.id
resp = resp.follow(status=404)
def test_form_tracking_code_as_variable(pub, nocache):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.CommentField(type='comment', id='3', label='!{{ form_tracking_code }}!'),
]
formdef.enable_tracking_codes = True
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'foobar'
resp = resp.form.submit('submit')
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
assert '!%s!' % tracking_code in resp.text
def test_form_draft_with_file(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.FileField(id='0', label='file', type='file')]
formdef.enable_tracking_codes = True
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
formdef.data_class().wipe()
assert '
Tracking code ' in resp.text
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
resp.forms[0]['f0$file'] = Upload('test.txt', b'foobar', 'text/plain')
resp = resp.forms[0].submit('submit')
tracking_code_2 = get_displayed_tracking_code(resp)
assert tracking_code == tracking_code_2
# check we can load the formdata as a draft
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit()
assert resp.location == 'http://example.net/code/%s/load' % tracking_code
resp = resp.follow()
resp = resp.follow()
assert resp.location.startswith('http://example.net/test/?mt=')
resp = resp.follow()
resp = resp.forms[1].submit('previous')
assert resp.pyquery('.filename').text() == 'test.txt'
# check file is downloadable
r2 = resp.click('test.txt')
assert r2.content_type == 'text/plain'
assert r2.text == 'foobar'
# check submitted form keeps the file
resp = resp.forms[1].submit('submit') # -> confirmation page
resp = resp.forms[1].submit('submit') # -> done
resp = resp.follow()
assert resp.click('test.txt').follow().text == 'foobar'
def test_form_draft_with_file_direct_validation(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.FileField(id='0', label='file', type='file')]
formdef.enable_tracking_codes = True
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
formdef.data_class().wipe()
tracking_code = get_displayed_tracking_code(resp)
resp.forms[0]['f0$file'] = Upload('test2.txt', b'foobar2', 'text/plain')
resp = resp.forms[0].submit('submit')
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit().follow().follow().follow()
assert 'test2.txt' in resp.text
# check submitted form keeps the file
resp = resp.forms[1].submit('submit') # -> done
resp = resp.follow()
assert resp.click('test2.txt').follow().text == 'foobar2'
def test_form_draft_with_date(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.DateField(id='0', label='date', type='date')]
formdef.enable_tracking_codes = True
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
formdef.data_class().wipe()
tracking_code = get_displayed_tracking_code(resp)
resp.forms[0]['f0'] = '2012-02-12'
resp = resp.forms[0].submit('submit')
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit().follow().follow().follow()
assert '2012-02-12' in resp.text
# check submitted form keeps the date
resp = resp.forms[1].submit('submit') # -> done
resp = resp.follow()
assert '2012-02-12' in resp.text
@pytest.mark.parametrize('field_type', ['string', 'item'])
@pytest.mark.parametrize('logged_in', ['anonymous', 'logged-in'])
def test_form_draft_from_prefill(pub, field_type, logged_in):
create_user(pub)
formdef = create_formdef()
if field_type == 'string':
formdef.fields = [fields.StringField(id='0', label='string')]
else:
formdef.fields = [fields.ItemField(id='0', label='item', type='item', items=['foo', 'bar', 'hello'])]
formdef.enable_tracking_codes = True
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
if logged_in == 'logged-in':
login(app, username='foo', password='foo')
# no draft
app.get('/test/')
assert formdef.data_class().count() == 0
formdef.data_class().wipe()
# draft created if there's been some prefilled fields
formdef.fields[0].prefill = {'type': 'string', 'value': '{{request.GET.test|default:""}}'}
formdef.store()
app.get('/test/?test=hello')
assert formdef.data_class().count() == 1
formdef.data_class().wipe()
# unless the call was made from an application
app.get('/test/?test=hello', headers={'User-agent': 'python-requests/0'})
assert formdef.data_class().count() == 0
# or a bot
app.get('/test/?test=hello', headers={'User-agent': 'Googlebot'})
assert formdef.data_class().count() == 0
# check there's no leftover draft after submission
for with_tracking_code in (False, True):
formdef.enable_tracking_codes = with_tracking_code
formdef.store()
formdef.data_class().wipe()
resp = app.get('/test/?test=hello')
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().count() == 1
@pytest.mark.parametrize('tracking_code', [True, False])
def test_form_direct_draft_access(pub, tracking_code):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string')]
formdef.enable_tracking_codes = tracking_code
formdef.store()
formdata = formdef.data_class()()
formdata.data = {'0': 'foobar'}
formdata.status = 'draft'
formdata.store()
resp = get_app(pub).get('/test/%s/' % formdata.id, status=302)
assert resp.location.startswith('http://example.net/login')
formdata.user_id = user.id
formdata.store()
resp = get_app(pub).get('/test/%s/' % formdata.id, status=302)
assert resp.location.startswith('http://example.net/login')
resp = login(get_app(pub), 'foo', 'foo').get('/test/%s/' % formdata.id, status=302)
assert resp.location.startswith('http://example.net/test/?mt=')
formdata.user_id = 1000
formdata.store()
resp = login(get_app(pub), 'foo', 'foo').get('/test/%s/' % formdata.id, status=403)
def form_password_field_submit(app, password):
formdef = create_formdef()
formdef.enable_tracking_codes = True
formdef.fields = [fields.PasswordField(id='0', label='password', formats=['sha1', 'md5', 'cleartext'])]
formdef.store()
page = app.get('/test/')
formdef.data_class().wipe()
next_page = page.forms[0].submit('submit') # but the field is required
assert [x.text for x in next_page.pyquery('div.error p')] == ['required field'] * 2
next_page.forms[0]['f0$pwd1'] = password
next_page.forms[0]['f0$pwd2'] = password
next_page = next_page.forms[0].submit('submit')
assert 'Check values then click submit.' in next_page.text
next_page = next_page.forms[0].submit('submit')
assert next_page.status_int == 302
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {
'0': {
'sha1': force_str(hashlib.sha1(force_bytes(password)).hexdigest()),
'md5': force_str(hashlib.md5(force_bytes(password)).hexdigest()),
'cleartext': force_str(password),
}
}
def test_form_password_field_submit(pub):
create_user(pub)
form_password_field_submit(get_app(pub), 'foobar')
form_password_field_submit(get_app(pub), force_str('• 83003706'))
form_password_field_submit(login(get_app(pub), username='foo', password='foo'), 'foobar\u00eb')
def test_form_multi_page_formdef_count_condition(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(
id='2',
label='2nd page',
type='page',
condition={'type': 'python', 'value': 'form_objects.count > 0'},
),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1'] = 'foo'
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
# add a formdata this will make the second page appear.
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
resp = get_app(pub).get('/test/')
resp.forms[0]['f1'] = 'foo'
resp = resp.forms[0].submit('submit') # should NOT go straight to validation
assert 'Check values then click submit.' not in resp.text
def test_form_multi_page_post_edit(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
editable = st1.add_action('editable', id='_editable')
editable.by = ['_submitter', '_receiver']
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
page = login(get_app(pub), username='foo', password='foo').get('/test/')
page.forms[0]['f1'] = 'foo'
next_page = page.forms[0].submit('submit')
next_page.forms[0]['f3'] = 'barXYZ'
next_page = next_page.forms[0].submit('submit')
next_page = next_page.forms[0].submit('submit')
next_page = next_page.follow()
assert 'The form has been recorded' in next_page.text
data_id = formdef.data_class().select()[0].id
page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
assert 'button_editable-button' in page.text
assert 'barXYZ' in page.text
resp = page.forms[0].submit('button_editable')
assert resp.location.startswith('http://example.net/test/%s/wfedit-' % data_id)
resp = resp.follow()
# check there's no new "phantom" history entry
assert len(formdef.data_class().get(data_id).evolution) == 1
assert resp.forms[0]['f1'].value == 'foo'
resp.forms[0]['f1'] = 'foo2'
resp = resp.forms[0].submit('submit')
assert resp.forms[0]['f3'].value == 'barXYZ'
resp = resp.forms[0].submit('previous')
assert resp.forms[0]['f1'].value == 'foo2'
resp = resp.forms[0].submit('submit')
assert 'Save Changes' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/test/%s/' % data_id
resp = resp.follow()
assert 'foo2' in resp.text # modified value is there
assert 'barXYZ' in resp.text # unchanged value is still there
assert len(formdef.data_class().get(data_id).evolution) == 2 # new history entry
assert formdef.data_class().get(data_id).evolution[-1].who == '_submitter'
assert formdef.data_class().get(data_id).evolution[-1].status is None
# modify workflow to jump to another status after the edition
st2 = workflow.add_status('Status2', 'st2')
editable.status = st2.id
workflow.store()
assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id
page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
assert 'button_editable-button' in page.text
assert 'barXYZ' in page.text
resp = page.forms[0].submit('button_editable')
assert resp.location.startswith('http://example.net/test/%s/wfedit-' % data_id)
resp = resp.follow()
assert resp.forms[0]['f1'].value == 'foo2'
resp.forms[0]['f1'] = 'foo3'
resp = resp.forms[0].submit('submit')
assert (
formdef.data_class().get(data_id).data['1'] == 'foo2'
) # check foo3 has not been overwritten in database
assert resp.forms[0]['f3'].value == 'barXYZ'
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/test/%s/' % data_id
resp = resp.follow()
assert 'foo3' in resp.text # modified value is there
assert 'barXYZ' in resp.text # unchanged value is still there
assert formdef.data_class().get(data_id).status == 'wf-%s' % st2.id
assert len(formdef.data_class().get(data_id).evolution) == 3 # single new history entry
assert formdef.data_class().get(data_id).evolution[-1].who == '_submitter'
assert formdef.data_class().get(data_id).evolution[-1].status == 'wf-%s' % st2.id
# jump to a nonexistent status == do not jump, but add a LoggedError
pub.loggederror_class.wipe()
assert pub.loggederror_class.count() == 0
editable.status = 'deleted_status_id'
workflow.store()
# go back to st1
formdata = formdef.data_class().get(data_id)
formdata.status = 'wf-%s' % st1.id
formdata.store()
assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id
page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
resp = page.forms[0].submit('button_editable')
resp = resp.follow()
resp.forms[0]['f1'] = 'foo3'
resp = resp.forms[0].submit('submit')
resp = resp.forms[0].submit('submit')
resp = resp.follow()
assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id # stay on st1
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.formdata_id == str(formdata.id)
assert logged_error.formdef_id == formdef.id
assert logged_error.workflow_id == workflow.id
assert logged_error.status_id == st1.id
assert logged_error.status_item_id == editable.id
assert logged_error.occurences_count == 1
# do it again: increment logged_error.occurences_count
page = login(get_app(pub), username='foo', password='foo').get('/test/%s/' % data_id)
resp = page.forms[0].submit('button_editable')
resp = resp.follow()
resp.forms[0]['f1'] = 'foo3'
resp = resp.forms[0].submit('submit')
resp = resp.forms[0].submit('submit')
resp = resp.follow()
assert formdef.data_class().get(data_id).status == 'wf-%s' % st1.id # stay on st1
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.occurences_count == 2
def test_form_edit_autocomplete_list(pub):
create_user(pub)
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'json', 'value': 'http://remote.example.net/json'}
data_source.query_parameter = 'q'
data_source.id_parameter = 'id'
data_source.store()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='0',
label='string',
type='item',
data_source={'type': 'foobar'},
display_mode='autocomplete',
),
]
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
editable = st1.add_action('editable', id='_editable')
editable.by = ['_submitter', '_receiver']
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
app = get_app(pub)
login(app, username='foo', password='foo')
with responses.RequestsMock() as rsps:
data = {
'data': [
{'id': '1', 'text': 'hello', 'extra': 'foo'},
{'id': '2', 'text': 'world', 'extra': 'bar'},
]
}
rsps.get('http://remote.example.net/json', json=data)
resp = app.get('/test/')
assert 'data-select2-url=' in resp.text
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f0_display'] = Hidden(form=resp.form, tag='input', name='f0_display', pos=10)
resp.form['f0'].force_value('1')
resp.form.fields['f0_display'].force_value('hello')
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '1'
assert formdef.data_class().select()[0].data['0_display'] == 'hello'
assert formdef.data_class().select()[0].data['0_structured'] == data['data'][0]
resp = resp.follow()
url = resp.request.url
resp = resp.form.submit('button_editable')
assert 'wfedit' in resp.location
resp = resp.follow()
assert 'data-value="1"' in resp
assert 'data-initial-display-value="hello"' in resp
# relogin
app = get_app(pub)
login(app, username='foo', password='foo')
resp = app.get(url)
resp = resp.form.submit('button_editable')
resp = resp.follow()
assert 'data-value="1"' in resp
assert 'data-initial-display-value="hello"' in resp
def test_form_edit_with_internal_id_condition(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='1', label='1st field', type='string'),
fields.StringField(
id='2',
label='2nd field',
type='string',
condition={'type': 'django', 'value': 'form_internal_id'},
),
]
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
editable = st1.add_action('editable', id='_editable')
editable.by = ['_submitter', '_receiver']
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
app = get_app(pub)
login(app, username='foo', password='foo')
resp = app.get(formdef.get_url())
resp.form['f1'] = 'test'
assert 'f2' not in resp.form.fields
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['1'] == 'test'
resp = resp.follow()
resp = resp.form.submit('button_editable')
assert 'wfedit' in resp.location
resp = resp.follow()
assert 'f2' in resp.form.fields
def test_form_count_dispatching(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.condition = {'type': 'python', 'value': 'form_objects.count_status_st2 < 1'}
jump.status = 'st2'
workflow.add_status('Status2', 'st2')
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
page = login(get_app(pub), username='foo', password='foo').get('/test/')
page = page.forms[0].submit('submit') # form page
page = page.forms[0].submit('submit') # confirmation page
page = page.follow()
assert 'The form has been recorded' in page.text # success
assert len(formdef.data_class().select(clause=lambda x: x.status == 'wf-st1')) == 0
assert len(formdef.data_class().select(clause=lambda x: x.status == 'wf-st2')) == 1
page = login(get_app(pub), username='foo', password='foo').get('/test/')
page = page.forms[0].submit('submit') # form page
page = page.forms[0].submit('submit') # confirmation page
page = page.follow()
assert 'The form has been recorded' in page.text # success
assert len(formdef.data_class().select(clause=lambda x: x.status == 'wf-st2')) == 1
assert len(formdef.data_class().select(clause=lambda x: x.status == 'wf-st1')) == 1
def test_preview_form(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = []
formdef.disabled = True
formdef.store()
# check the preview page is not accessible to regular users
get_app(pub).get('/preview/test/', status=403)
# check it's accessible to admins
user.is_admin = True
user.store()
page = login(get_app(pub), username='foo', password='foo').get('/preview/test/')
# check the form is marked as a preview (this disables autosave calls)
assert page.pyquery('form[data-autosave=false]').length
# check no formdata gets stored
next_page = page.forms[0].submit('submit')
assert 'Check values then click submit.' in next_page.text
next_page = next_page.forms[0].submit('submit')
assert next_page.status_int == 200
assert 'This was only a preview: form was not actually submitted.' in next_page.text
assert len([x for x in formdef.data_class().select() if not x.is_draft()]) == 0
# check no drafts are proposed for recall
formdef.data_class().wipe()
draft = formdef.data_class()()
draft.user_id = user.id
draft.status = 'draft'
draft.data = {}
draft.store()
resp = login(get_app(pub), username='foo', password='foo').get('/preview/test/')
assert 'You already started to fill this form.' not in resp.text
def test_form_item_data_source_field_submit(pub):
def submit_item_data_source_field(ds):
formdef = create_formdef()
formdef.fields = [fields.ItemField(id='0', label='string', data_source=ds)]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f0'] = '1'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
return formdef.data_class().get(data_id).data
ds = {
'type': 'formula',
'value': repr([('1', 'un'), ('2', 'deux')]),
}
assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
ds['value'] = repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}])
assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
ds['value'] = repr([{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}])
assert submit_item_data_source_field(ds) == {
'0': '1',
'0_display': 'un',
'0_structured': {'id': '1', 'text': 'un', 'more': 'foo'},
}
# numeric identifiers
ds['value'] = repr([{'id': 1, 'text': 'un'}, {'id': 2, 'text': 'deux'}])
assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
# json source
ds = {
'type': 'json',
'value': 'http://www.example.net/plop',
}
with responses.RequestsMock() as rsps:
rsps.get(
'http://www.example.net/plop',
json={'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]},
)
assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
# numeric identifiers
with responses.RequestsMock() as rsps:
rsps.get(
'http://www.example.net/plop', json={'data': [{'id': 1, 'text': 'un'}, {'id': 2, 'text': 'deux'}]}
)
assert submit_item_data_source_field(ds) == {'0': '1', '0_display': 'un'}
@pytest.mark.parametrize('fail_after_count_page', range(2, 8))
@pytest.mark.parametrize('fail_after_count_validation', range(0, 2))
@responses.activate
def test_form_item_data_source_error(pub, monkeypatch, fail_after_count_page, fail_after_count_validation):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'json', 'value': 'http://www.example.net/plop'}
data_source.id_parameter = 'id'
data_source.store()
normal_get_structured_value = NamedDataSource.get_structured_value
class failing_get_structured_value:
def __init__(self, fail_after_count):
self.fail_after_count = fail_after_count
self.count = 0
def __call__(self, *args):
import inspect
for frame in inspect.stack():
if frame.function in ['store_display_value', 'store_structured_value']:
count = self.count
self.count += 1
if count >= self.fail_after_count:
return None
return normal_get_structured_value(*args)
@property
def method(self):
def f(*args):
return self(*args)
return f
responses.get(
'http://www.example.net/plop', json={'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]}
)
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(id='1', label='string', data_source={'type': 'foobar'}),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1'] = '1'
# fail in get_structured_value
monkeypatch.setattr(
NamedDataSource, 'get_structured_value', failing_get_structured_value(fail_after_count_page).method
)
resp = resp.forms[0].submit('submit')
assert 'Technical error, please try again' in resp.text
# fix transient failure
monkeypatch.setattr(NamedDataSource, 'get_structured_value', normal_get_structured_value)
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
# fail in get_structured_value
monkeypatch.setattr(
NamedDataSource,
'get_structured_value',
failing_get_structured_value(fail_after_count_validation).method,
)
resp = resp.forms[0].submit('submit')
assert 'Technical error, please try again' in resp.text
# fix transient failure
monkeypatch.setattr(NamedDataSource, 'get_structured_value', normal_get_structured_value)
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
return formdef.data_class().get(data_id).data
@pytest.mark.parametrize('fail_after_count_page', range(2, 8))
@responses.activate
def test_form_item_data_source_error_no_confirmation(pub, monkeypatch, fail_after_count_page):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'json', 'value': 'http://www.example.net/plop'}
data_source.id_parameter = 'id'
data_source.store()
normal_get_structured_value = NamedDataSource.get_structured_value
class failing_get_structured_value:
def __init__(self, fail_after_count):
self.fail_after_count = fail_after_count
self.count = 0
def __call__(self, *args):
import inspect
for frame in inspect.stack():
if frame.function in ['store_display_value', 'store_structured_value']:
count = self.count
self.count += 1
if count >= self.fail_after_count:
return None
return normal_get_structured_value(*args)
@property
def method(self):
def f(*args):
return self(*args)
return f
responses.get(
'http://www.example.net/plop', json={'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]}
)
formdef = create_formdef()
formdef.confirmation = False
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(id='1', label='string', data_source={'type': 'foobar'}),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1'] = '1'
# fail in get_structured_value
monkeypatch.setattr(
NamedDataSource, 'get_structured_value', failing_get_structured_value(fail_after_count_page).method
)
resp = resp.forms[0].submit('submit')
assert 'Technical error, please try again' in resp.text
# fix transient failure
monkeypatch.setattr(NamedDataSource, 'get_structured_value', normal_get_structured_value)
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
return formdef.data_class().get(data_id).data
def test_form_items_data_source_field_submit(pub):
def submit_items_data_source_field(ds):
formdef = create_formdef()
formdef.fields = [fields.ItemsField(id='0', label='string', data_source=ds)]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f0$element1'].checked = True
resp.forms[0]['f0$element3'].checked = True
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
return formdef.data_class().get(data_id).data
ds = {
'type': 'formula',
'value': repr([('1', 'un'), ('2', 'deux'), ('3', 'trois')]),
}
assert submit_items_data_source_field(ds) == {
'0': ['1', '3'],
'0_display': 'un, trois',
'0_structured': [{'id': '1', 'text': 'un'}, {'id': '3', 'text': 'trois'}],
}
ds['value'] = repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}, {'id': '3', 'text': 'trois'}])
assert submit_items_data_source_field(ds) == {
'0': ['1', '3'],
'0_display': 'un, trois',
'0_structured': [{'id': '1', 'text': 'un'}, {'id': '3', 'text': 'trois'}],
}
ds['value'] = repr(
[
{'id': '1', 'text': 'un', 'more': 'foo'},
{'id': '2', 'text': 'deux', 'more': 'bar'},
{'id': '3', 'text': 'trois', 'more': 'baz'},
]
)
assert submit_items_data_source_field(ds) == {
'0': ['1', '3'],
'0_display': 'un, trois',
'0_structured': [
{'id': '1', 'more': 'foo', 'text': 'un'},
{'id': '3', 'more': 'baz', 'text': 'trois'},
],
}
def test_form_page_string_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='0', label='string', prefill={'type': 'string', 'value': 'HELLO WORLD'})
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == 'HELLO WORLD'
assert 'widget-prefilled' in resp.text
def test_form_page_profile_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string', prefill={'type': 'user', 'value': 'email'})]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == ''
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.forms[0]['f0'].value == 'foo@localhost'
def test_form_page_profile_first_name_prefill(pub):
user = create_user(pub)
from wcs.admin.settings import UserFieldsFormDef
user_formdef = UserFieldsFormDef(pub)
user_formdef.fields = [
fields.StringField(
id='_first_name', label='first name', type='string', extra_css_class='autocomplete-given-name'
),
fields.StringField(
id='_city', label='city', type='string', extra_css_class='autocomplete-address-level2'
),
fields.StringField(id='_plop', label='plop', type='string', extra_css_class='xxx'),
]
user_formdef.store()
user.form_data = {'_first_name': 'plop', '_city': 'mytown'}
user.set_attributes_from_formdata(user.form_data)
user.store()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='0', label='string', prefill={'type': 'user', 'value': '_first_name'}),
fields.StringField(id='1', label='string', prefill={'type': 'user', 'value': '_city'}),
fields.StringField(id='2', label='string', prefill={'type': 'user', 'value': '_plop'}),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == ''
assert resp.forms[0]['f0'].attrs['autocomplete'] == 'given-name' # html5
assert resp.forms[0]['f1'].value == ''
assert resp.forms[0]['f1'].attrs['autocomplete'] == 'address-level2' # html5
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.forms[0]['f0'].value == 'plop'
assert resp.forms[0]['f1'].value == 'mytown'
def test_form_page_formula_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='0', label='string', prefill={'type': 'formula', 'value': repr('HELLO WORLD')})
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == 'HELLO WORLD'
assert 'widget-prefilled' in resp.text
def test_form_page_template_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(
id='0', label='string', prefill={'type': 'string', 'value': '{{session_user_display_name}}'}
)
]
formdef.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
assert resp.form['f0'].value == 'User Name'
assert 'widget-prefilled' in resp.text
# erroneous prefill
formdef.fields = [
fields.StringField(
id='0',
label='string',
prefill={'type': 'string', 'value': '{{session_user_display_name|unknown}}'},
)
]
formdef.store()
resp = app.get('/test/')
assert resp.form['f0'].value == ''
# still marked with a css class, in case of live changes.
assert 'widget-prefilled' in resp.text
def test_form_page_session_var_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='0', label='string', prefill={'type': 'formula', 'value': 'session_var_foo'})
]
formdef.store()
# check it's empty if it doesn't exist
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == ''
# check it's not set if it's not whitelisted
resp = get_app(pub).get('/?session_var_foo=hello')
assert urllib.parse.urlparse(resp.location).path == '/'
resp = resp.follow()
resp = resp.click('test')
assert resp.forms[0]['f0'].value == ''
# check it works
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
fd.write(
'''[options]
query_string_allowed_vars = foo,bar
'''
)
resp = get_app(pub).get('/?session_var_foo=hello')
assert urllib.parse.urlparse(resp.location).path == '/'
resp = resp.follow()
resp = resp.click('test')
assert resp.forms[0]['f0'].value == 'hello'
# check it survives a login
resp = get_app(pub).get('/?session_var_foo=hello2')
assert urllib.parse.urlparse(resp.location).path == '/'
resp = resp.follow()
resp = resp.click('Login')
resp = resp.follow()
resp.forms[0]['username'] = 'foo'
resp.forms[0]['password'] = 'foo'
resp = resp.forms[0].submit()
resp = resp.follow()
resp = resp.click('test')
assert resp.forms[0]['f0'].value == 'hello2'
# check repeated options are ignored
resp = get_app(pub).get('/?session_var_foo=hello&session_var_foo=hello2')
assert urllib.parse.urlparse(resp.location).path == '/'
resp = resp.follow()
resp = resp.click('test')
assert resp.forms[0]['f0'].value == ''
# check extra query string parameters are not lost
resp = get_app(pub).get('/?session_var_foo=hello&foo=bar')
assert urllib.parse.urlparse(resp.location).path == '/'
assert urllib.parse.urlparse(resp.location).query == 'foo=bar'
os.unlink(os.path.join(pub.app_dir, 'site-options.cfg'))
def test_form_page_template_list_prefill(pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='1',
type='item',
label='item',
varname='item',
required=True,
items=['Foo', 'Bar'],
prefill={'type': 'string', 'value': 'Foo'},
)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].value == 'Foo'
formdef.fields[0].prefill['value'] = 'Bar'
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].value == 'Bar'
formdef.fields[0].prefill['value'] = 'Baz'
formdef.store()
resp = get_app(pub).get('/test/')
assert 'invalid value selected' in resp.text
formdef.fields[0].prefill['value'] = '{{plop|default:""}}'
formdef.store()
resp = get_app(pub).get('/test/')
assert 'invalid value selected' not in resp.text
def test_form_page_query_string_list_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='1',
label='item',
varname='item',
required=False,
data_source={'type': 'foobar'},
prefill={'type': 'string', 'value': '{{request.GET.preselect}}'},
)
]
formdef.store()
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'formula',
'value': repr(
[
{'id': '1', 'text': 'un'},
{'id': '2', 'text': 'deux'},
{'id': '3', 'text': 'trois'},
{'id': '4', 'text': 'quatre'},
]
),
}
data_source.store()
resp = get_app(pub).get('/test/')
assert resp.form['f1'].value == '1'
resp = get_app(pub).get('/test/?preselect=2')
assert resp.form['f1'].value == '2'
resp = resp.form.submit('submit')
resp = resp.form.submit('submit').follow()
assert 'deux' in resp.text
def test_form_page_profile_prefill_list(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='0',
label='item',
type='item',
items=['', 'bar@localhost', 'foo@localhost'],
required=False,
prefill={'type': 'user', 'value': 'email'},
)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == ''
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.forms[0]['f0'].value == 'foo@localhost'
# invalid value
formdef.fields = [
fields.ItemField(
id='0',
label='item',
type='item',
items=['', 'bar@localhost'],
required=False,
prefill={'type': 'user', 'value': 'email'},
)
]
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert 'invalid value selected' in resp.text
assert resp.forms[0]['f0'].value == ''
def test_form_page_formula_prefill_items_field(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemsField(
id='0',
label='items',
items=['foo', 'bar', 'baz'],
prefill={'type': 'formula', 'value': '["foo", "baz"]'},
)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0$element0'].checked
assert not resp.form['f0$element1'].checked
assert resp.form['f0$element2'].checked
assert 'widget-prefilled' in resp.text
resp.form['f0$element0'].checked = False
resp = resp.form.submit('submit')
resp = resp.form.submit('submit').follow()
assert '>foo<' not in resp.text
assert '>bar<' not in resp.text
assert '>baz<' in resp.text
# check with remote json
ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
formdef.fields = [
fields.ItemsField(
id='0',
label='items',
data_source=ds,
display_disabled_items=True,
prefill={'type': 'formula', 'value': '["2"]'},
)
]
resp = get_app(pub).get('/test/')
assert not resp.form['f0$element1'].checked
assert resp.form['f0$element2'].checked
def test_form_page_checkbox_prefill(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.BoolField(id='0', label='check', type='bool', prefill={'type': 'formula', 'value': 'True'})
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].checked
resp = resp.forms[0].submit('submit') # -> validation
assert resp.forms[0]['f0'].value == 'True'
assert resp.forms[0]['f0disabled'].checked
assert resp.forms[0]['f0disabled'].attrs['disabled']
resp = get_app(pub).get('/test/')
resp.forms[0]['f0'].checked = False
resp = resp.forms[0].submit('submit') # -> validation
assert resp.forms[0]['f0'].value == 'False'
assert not resp.forms[0]['f0disabled'].checked
assert resp.forms[0]['f0disabled'].attrs['disabled']
# check with locked value
formdef.fields[0].prefill['locked'] = True
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].attrs['onclick']
assert resp.forms[0]['f0'].checked
resp.forms[0]['f0'].checked = False # alter value while it's not allowed
resp = resp.forms[0].submit('submit') # -> validation
assert resp.forms[0]['f0'].value == 'True'
assert resp.forms[0]['f0disabled'].checked
assert resp.forms[0]['f0disabled'].attrs['disabled']
def test_form_page_template_prefill_items_field(pub):
BlockDef.wipe()
create_user(pub)
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='123', required=True, label='Test', type='string', varname='test'),
]
block.store()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemsField(
id='0',
label='items',
items=['foo', 'bar', 'baz'],
prefill={'type': 'string', 'value': 'foo,baz'},
),
fields.FileField(id='1', label='file', type='file', varname='file'),
fields.BlockField(id='2', label='test', type='block:foobar', varname='foobar'),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0$element0'].checked
assert not resp.form['f0$element1'].checked
assert resp.form['f0$element2'].checked
# this selection will be reused in the complex data test
resp.form['f0$element1'].checked = True
resp.form['f0$element2'].checked = False
assert 'widget-prefilled' in resp.text
resp.form['f1$file'] = Upload('test.txt', b'foobar', 'text/plain')
resp.form['f2$element0$f123'] = 'plop'
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
# check with remote json
ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
formdef.fields[0] = fields.ItemsField(
id='0',
label='items',
data_source=ds,
display_disabled_items=True,
prefill={'type': 'string', 'value': 'foo,baz'},
)
formdef.store()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={
'data': [
{'id': 'foo', 'text': 'hello'},
{'id': 'bar', 'text': 'world'},
{'id': 'baz', 'text': '!'},
]
},
)
resp = get_app(pub).get('/test/')
assert resp.form['f0$elementfoo'].checked
assert not resp.form['f0$elementbar'].checked
assert resp.form['f0$elementbaz'].checked
# check with template returning a complex data
formdef.fields[0] = fields.ItemsField(
id='0',
varname='items',
label='items',
data_source=ds,
display_disabled_items=True,
prefill={'type': 'string', 'value': '{{form_objects|first|get:"form_var_items_raw"}}'},
)
formdef.store()
# it will use foo,bar as selected in the first part of this test
resp = get_app(pub).get('/test/')
assert resp.form['f0$elementfoo'].checked
assert resp.form['f0$elementbar'].checked
assert not resp.form['f0$elementbaz'].checked
# check with complex data of wrong type
for invalid_prefill_value in [
{'type': 'string', 'value': '{{form_objects|first|get:"form_var_file_raw"}}'},
{'type': 'string', 'value': '{{form_objects|first|get:"form_var_foobar"}}'},
{'type': 'formula', 'value': '[{"a": "foo", "b": "baz"}]'},
]:
formdef.fields[0] = fields.ItemsField(
id='0',
varname='items',
label='items',
data_source=ds,
display_disabled_items=True,
prefill=invalid_prefill_value,
)
formdef.store()
resp = get_app(pub).get('/test/')
assert not resp.form['f0$elementfoo'].checked
assert not resp.form['f0$elementbar'].checked
assert not resp.form['f0$elementbaz'].checked
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.summary == 'Invalid value for items prefill on field "items"'
pub.loggederror_class.wipe()
# check with a "none" explicit prefill, or a None value
for none_prefill_value in [
{},
{'type': 'none'},
{'type': 'string', 'value': '{{ None }}'},
{'type': 'formula', 'value': 'None'},
]:
formdef.fields[0] = fields.ItemsField(
id='0',
varname='items',
label='items',
data_source=ds,
display_disabled_items=True,
prefill=none_prefill_value,
)
formdef.store()
# all checkboxes will be left unchecked
resp = get_app(pub).get('/test/')
assert not resp.form['f0$elementfoo'].checked
assert not resp.form['f0$elementbar'].checked
assert not resp.form['f0$elementbaz'].checked
assert pub.loggederror_class.count() == 0
def test_form_page_changing_prefill(pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(
id='3', label='string 2', prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}
),
fields.PageField(id='4', label='3rd page', type='page'),
]
formdef.store()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'Hello'
resp = resp.form.submit('submit') # -> 2nd page
assert resp.form['f3'].value == 'Hello World'
resp = resp.form.submit('submit') # -> 3rd page
resp = resp.form.submit('previous') # back to 2nd page
assert resp.form['f3'].value == 'Hello World'
resp = resp.form.submit('previous') # back to 1st page
assert resp.form['f1'].value == 'Hello'
resp.form['f1'] = 'Goodbye Cruel'
resp = resp.form.submit('submit') # -> 2nd page
assert resp.form['f3'].value == 'Goodbye Cruel World'
resp = resp.form.submit('submit') # -> 3rd page
resp = resp.form.submit('previous') # back to 2nd page
resp.form['f3'].value = 'Changed value'
resp = resp.form.submit('previous') # back to 1st page
resp = resp.form.submit('submit') # -> 2nd page
assert resp.form['f3'].value == 'Changed value'
resp = resp.form.submit('submit') # -> 3rd page
resp = resp.form.submit('submit') # -> 2nd page
assert resp.form['f3'].value == 'Changed value'
def test_form_page_changing_prefill_draft(pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(
id='3', label='string 2', prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}
),
fields.PageField(id='4', label='3rd page', type='page'),
]
formdef.enable_tracking_codes = True
formdef.store()
resp = get_app(pub).get('/test/')
resp.form['f1'] = 'Hello'
resp = resp.form.submit('submit') # -> 2nd page
assert resp.form['f3'].value == 'Hello World'
resp = resp.form.submit('submit') # -> 3rd page
resp = resp.form.submit('previous') # back to 2nd page
assert resp.form['f3'].value == 'Hello World'
resp = resp.form.submit('submit') # -> 3rd page
tracking_code = get_displayed_tracking_code(resp)
# start with a new session and restore draft using the tracking code
resp = get_app(pub).get('/')
resp.form['code'] = tracking_code
resp = resp.form.submit().follow().follow().follow()
assert_current_page(resp, '3rd page')
resp = resp.forms[1].submit('previous') # back to 2nd page
assert resp.forms[1]['f3'].value == 'Hello World'
resp = resp.forms[1].submit('previous') # back to 1st page
assert resp.forms[1]['f1'].value == 'Hello'
resp.forms[1]['f1'] = 'Goodbye Cruel'
resp = resp.forms[1].submit('submit') # -> 2nd page
assert resp.forms[1]['f3'].value == 'Goodbye Cruel World'
def test_prefill_query_parameter(pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(
id='1',
label='str',
varname='foo',
required=False,
prefill={'type': 'string', 'value': '{{request.GET.prefill}}'},
),
fields.PageField(id='2', label='2nd page', type='page'),
]
formdef.store()
resp = get_app(pub).get('/test/?prefill=Hello')
assert resp.form['f1'].value == 'Hello'
resp = resp.form.submit('submit') # -> 2nd page
resp = resp.form.submit('previous') # back to 1st page
# check it has not be reset to the empty string (as there's no request.GET
# anymore)
assert resp.form['f1'].value == 'Hello'
def test_form_captcha(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='Some field')]
formdef.has_captcha = True
formdef.enable_tracking_codes = True
formdef.store()
# test authenticated users are not presented with a captcha
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp = resp.click('test')
resp.form['f0'] = 'test'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'form_captcha' not in resp.text
# check anonymous user gets the captcha
app = get_app(pub)
resp = app.get('/')
resp = resp.click('test')
resp.form['f0'] = 'test'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'form_captcha' in resp.text
session_id = list(app.cookies.values())[0].strip('"')
session = pub.session_class.get(session_id)
resp.form['captcha$q'] = session.get_captcha_token(resp.forms[0]['captcha$token'].value)['answer']
resp = resp.form.submit('submit')
assert resp.status_code == 302 # redirect when formdata is created
# and check it gets it only once
resp = app.get('/')
resp = resp.click('test')
resp.form['f0'] = 'test'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'form_captcha' not in resp.text
def test_form_captcha_and_no_validation_page(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='Some field')]
formdef.has_captcha = True
formdef.enable_tracking_codes = True
formdef.confirmation = False
formdef.store()
# test authenticated users are not stopped on a confirmation page
resp = login(get_app(pub), username='foo', password='foo').get('/')
resp = resp.click('test')
resp.form['f0'] = 'test'
resp = resp.form.submit('submit')
assert resp.status_code == 302 # redirect when formdata is created
# check anonymous user gets the captcha
app = get_app(pub)
resp = app.get('/')
resp = resp.click('test')
resp.form['f0'] = 'test'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'form_captcha' in resp.text
def test_form_table_field_submit(pub, emails):
formdef = create_formdef()
formdef.fields = [
fields.TableField(
id='0',
label='table',
type='table',
rows=[force_str('Ã '), 'b'],
columns=['c', 'd', force_str('e')],
required=False,
)
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert formdef.data_class().select()[0].data == {'0': [['', '', ''], ['', '', '']]}
formdef.data_class().wipe()
formdef.fields = [
fields.TableField(
id='0', label='table', type='table', rows=['a', 'b'], columns=['c', 'd', 'e'], required=True
)
]
formdef.store()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' not in resp.text
resp = get_app(pub).get('/test/')
resp.form['f0$c-0-0'] = 'a'
resp.form['f0$c-1-0'] = 'b'
resp.form['f0$c-0-1'] = 'c'
resp.form['f0$c-1-1'] = 'd'
resp.form['f0$c-0-2'] = 'e'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().select()[0].data == {'0': [['a', 'c', 'e'], ['b', 'd', '']]}
# check table is present in received email (via form_details).
create_user(pub)
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f0$c-0-0'] = 'Ã Ã ' # would trigger column length bug (#23072)
resp.form['f0$c-1-0'] = 'bb'
resp.form['f0$c-0-1'] = 'cc'
resp.form['f0$c-1-1'] = 'dd'
resp.form['f0$c-0-2'] = 'ee'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp.text
# check rst2html didn't fail
assert b'ee' in emails.get('New form (test)')['msg'].get_payload()[1].get_payload(decode=True)
def test_form_table_rows_field_submit(pub, emails):
formdef = create_formdef()
formdef.fields = [
fields.TableRowsField(id='0', label='table', type='tablerows', columns=['a', 'b'], required=False)
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert formdef.data_class().select()[0].data == {'0': []}
formdef.data_class().wipe()
formdef.fields = [
fields.TableRowsField(id='0', label='table', type='tablerows', columns=['a', 'b'], required=True)
]
formdef.store()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' not in resp.text
resp = get_app(pub).get('/test/')
resp.form['f0$element0$col0'] = 'a'
resp.form['f0$element0$col1'] = 'b'
resp.form['f0$element1$col0'] = 'c'
resp.form['f0$element1$col1'] = 'd'
resp.form['f0$element2$col0'] = 'e'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().select()[0].data == {'0': [['a', 'b'], ['c', 'd'], ['e', '']]}
formdef.data_class().wipe()
formdef.fields = [
fields.TableRowsField(
id='0', label='table', type='tablerows', columns=['a', 'b'], required=True, total_row=True
)
]
formdef.store()
resp = get_app(pub).get('/test/')
resp.form['f0$element0$col0'] = 'a'
resp.form['f0$element0$col1'] = '14'
resp.form['f0$element1$col0'] = 'c'
resp.form['f0$element1$col1'] = '23'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert '37.00' in resp.text
# check table is present in received email (via form_details).
create_user(pub)
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f0$element0$col0'] = 'Ã Ã '
resp.form['f0$element0$col1'] = '14'
resp.form['f0$element1$col0'] = 'ee'
resp.form['f0$element1$col1'] = '23'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert b'ee' in emails.get('New form (test)')['msg'].get_payload()[1].get_payload(decode=True)
def test_form_new_table_rows_field_draft_recall(pub):
formdef = create_formdef()
formdef.enable_tracking_codes = True
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
]
formdef.store()
formdef.data_class().wipe()
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'test'
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
tracking_code = get_displayed_tracking_code(resp)
assert tracking_code is not None
# add new table rows field to formdef
formdef.fields.append(
fields.TableRowsField(id='3', label='table', type='tablerows', columns=['a', 'b'], required=False)
)
formdef.store()
# restore form on validation page
resp = get_app(pub).get('/')
resp.form['code'] = tracking_code
resp = resp.form.submit().follow().follow().follow()
# validate form
resp = resp.forms[1].submit()
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].data['1'] == 'test'
assert formdef.data_class().select()[0].data['3'] is None
def test_form_table_rows_field_and_prefill(pub, emails):
formdef = create_formdef()
formdef.fields = [
fields.TableRowsField(id='0', label='table', type='tablerows', columns=['a', 'b'], required=True),
fields.StringField(id='1', label='string', prefill={'type': 'string', 'value': 'HELLO WORLD'}),
]
formdef.store()
formdef.data_class().wipe()
get_app(pub).get('/test/')
def test_form_table_rows_add_row(pub):
formdef = create_formdef()
formdef.fields = [
fields.StringField(id='1', label='string', require=True),
fields.TableRowsField(id='0', label='table', type='tablerows', columns=['a', 'b'], required=True),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert len(resp.pyquery.find('input[name^="f0$element"]')) == 10
resp = resp.form.submit('f0$add_element')
assert 'There were errors processing the form' not in resp
assert len(resp.pyquery.find('input[name^="f0$element"]')) == 12
resp = resp.form.submit('f0$add_element')
assert len(resp.pyquery.find('input[name^="f0$element"]')) == 14
resp = resp.form.submit('submit')
assert 'There were errors processing the form' in resp
def test_form_map_field_back_and_submit(pub):
formdef = create_formdef()
formdef.fields = [
fields.MapField(id='0', label='map'),
fields.StringField(
id='1', label='street', required=True, prefill={'type': 'geolocation', 'value': 'road'}
),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
assert 'qommon.map.js' in resp.text
assert 'qommon.geolocation.js' in resp.text
# with a real user interaction this would get set by javascript
resp.forms[0]['f0$latlng'].value = '1.234;-1.234'
assert 'data-geolocation="road"' in resp.text
# check required field
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' not in resp.text
assert 'data-geolocation="road"' in resp.text
resp.forms[0]['f1'].value = 'bla'
# check summary page
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'data-init-lng="-1.234"' in resp.text
assert 'data-init-lat="1.234"' in resp.text
# get back to the map field
resp = resp.forms[0].submit('previous')
# check the field is still marked as holding the road
assert 'data-geolocation="road"' in resp.text
assert resp.forms[0]['f0$latlng'].value == '1.234;-1.234'
# back to summary page
resp = resp.forms[0].submit('submit')
# and submitting the form
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {'1': 'bla', '0': '1.234;-1.234'}
def test_form_map_initial_zoom_level(pub):
formdef = create_formdef()
formdef.fields = [
fields.MapField(id='0', label='map'),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert 'data-initial_zoom="13"' in resp.text
pub.cfg['misc']['default-zoom-level'] = '16'
pub.write_cfg()
resp = get_app(pub).get('/test/')
assert 'data-initial_zoom="16"' in resp.text
formdef.fields[0].initial_zoom = '11'
formdef.store()
resp = get_app(pub).get('/test/')
assert 'data-initial_zoom="11"' in resp.text
def test_form_map_geolocation_text_field(pub):
formdef = create_formdef()
formdef.fields = [
fields.MapField(id='0', label='map'),
fields.TextField(
id='1', label='street', required=True, prefill={'type': 'geolocation', 'value': 'road'}
),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
assert 'qommon.map.js' in resp.text
assert 'qommon.geolocation.js' in resp.text
assert 'WCS_DEFAULT_GEOCODING_COUNTRY' not in resp.text
# check page has default geocoding country in a javascript variable if set
if not pub.site_options.has_section('options'):
pub.site_options.add_section('options')
pub.site_options.set('options', 'default-geocoding-country', 'France')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
resp = get_app(pub).get('/test/')
assert 'WCS_DEFAULT_GEOCODING_COUNTRY' in resp.text
def test_form_map_field_prefill_address(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='address', required=True, varname='address'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.MapField(id='3', label='map', prefill={'type': 'string', 'value': '{{ form_var_address }}'}),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.form['f1'] = '169 rue du chateau, paris'
with responses.RequestsMock() as rsps:
rsps.get('https://nominatim.entrouvert.org/search', json=[{'lat': '48.8337085', 'lon': '2.3233693'}])
resp = resp.form.submit('submit')
assert resp.form['f3$latlng'].value == '48.8337085;2.3233693'
assert 'chateau' in rsps.calls[0].request.url
def test_form_map_field_prefill_coords(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.MapField(id='1', label='map', type='map', varname='map1'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.MapField(id='3', label='map', prefill={'type': 'string', 'value': '{{ form_var_map1 }}'}),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.form['f1$latlng'] = '1.234;-1.234'
resp = resp.form.submit('submit')
assert resp.form['f3$latlng'].value == '1.234;-1.234'
def test_form_map_multi_page(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.MapField(id='1', label='map'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1$latlng'] = '1.234;-1.234'
assert resp.forms[0].fields['submit'][0].value_if_submitted() == 'Next'
resp = resp.forms[0].submit('submit')
assert resp.forms[0]['previous']
resp.forms[0]['f3'] = 'bar'
resp = resp.forms[0].submit('submit')
assert resp.forms[0]['f1$latlng'].value == '1.234;-1.234'
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data == {'1': '1.234;-1.234', '3': 'bar'}
def test_form_middle_session_change(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.forms[0]['f1'] = 'foo'
assert resp.forms[0].fields['submit'][0].value_if_submitted() == 'Next'
resp = resp.forms[0].submit('submit')
assert resp.forms[0]['previous']
app.cookiejar.clear()
resp.forms[0]['f3'] = 'bar'
resp = resp.forms[0].submit('submit')
assert resp.location == 'http://example.net/test/'
resp = resp.follow()
assert 'Sorry, your session have been lost.' in resp.text
app = get_app(pub)
resp = app.get('/test/')
resp.forms[0]['f1'] = 'foo'
assert resp.forms[0].fields['submit'][0].value_if_submitted() == 'Next'
resp = resp.forms[0].submit('submit')
assert resp.forms[0]['previous']
resp.forms[0]['f3'] = 'bar'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
app.cookiejar.clear()
resp = resp.forms[0].submit('submit')
resp = resp.follow()
assert 'Sorry, your session have been lost.' in resp.text
def test_form_autocomplete_variadic_url(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.PageField(
id='3', label='2nd page', type='page', condition={'type': 'python', 'value': 'True'}
),
fields.ItemField(id='1', label='string', type='item', varname='foo', items=['Foo', 'Bar']),
fields.StringField(
id='2', label='string2', required=True, data_source={'type': 'jsonp', 'value': '[var_foo]'}
),
fields.PageField(
id='4', label='3rd page', type='page', condition={'type': 'python', 'value': 'True'}
),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit') # next
# test javascript will be used to compute the full URL
assert 'options.wcs_base_url' in resp.text
assert 'jquery-ui.min.js' in resp.text
# test going forward (will error out), check it's still a variadic URL (#9786)
resp.form['f1'] = 'Foo'
resp = resp.form.submit('submit')
assert 'options.wcs_base_url' in resp.text
def test_form_page_formula_prefill_user_name(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='0', label='string', prefill={'type': 'formula', 'value': 'form_user_email'})
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == ''
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.forms[0]['f0'].value == 'foo@localhost'
def test_form_page_formula_prefill_session_user(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.StringField(id='0', label='string', prefill={'type': 'formula', 'value': 'session_user_email'})
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.forms[0]['f0'].value == ''
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.forms[0]['f0'].value == 'foo@localhost'
app = login(get_app(pub), username='foo', password='foo')
for session in pub.session_manager.values():
session.extra_user_variables = {'foo': 'bar'}
session.store()
formdef.fields = [
fields.StringField(
id='0', label='string', prefill={'type': 'formula', 'value': 'session_var_user_foo'}
)
]
formdef.store()
resp = app.get('/test/')
assert resp.forms[0]['f0'].value == 'bar'
def test_form_date_field_submit(pub):
formdef = create_formdef()
formdef.fields = [fields.DateField(id='0', label='string', type='date', required=False)]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f0'] = '2015-01-01'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert time.strftime('%Y-%m-%d', data.data['0']) == '2015-01-01'
# without filling the field
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] is None
def test_form_jsonp_item_field(http_requests, pub):
formdef = create_formdef()
formdef.fields = [
fields.ItemField(
id='1',
label='string',
type='item',
data_source={'type': 'jsonp', 'value': 'http://remote.example.net/jsonp'},
),
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert 'data-select2-url="http://remote.example.net/jsonp"' in resp.text
assert 'select2.min.js' in resp.text
def test_form_string_regex_field_submit(pub):
formdef = create_formdef()
formdef.fields = [
fields.StringField(
id='0',
label='string',
type='string',
validation={'type': 'regex', 'value': r'\d{5}$'},
required=False,
)
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f0'] = '12345'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] == '12345'
# without filling the field
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] is None
# with an invalid input
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f0'] = 'foobar'
resp = resp.forms[0].submit('submit')
assert 'invalid value' in resp.text
def test_form_text_field_submit(pub):
formdef = create_formdef()
formdef.fields = [fields.TextField(id='0', label='string', type='text', required=False)]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f0'] = '12345'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] == '12345'
# without filling the field
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.forms[0].submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] is None
# check max length
formdef.fields = [fields.TextField(id='0', label='string', type='text', maxlength=10)]
formdef.store()
resp = get_app(pub).get('/test/')
resp.forms[0]['f0'] = 'x' * 11
resp = resp.forms[0].submit('submit')
assert 'too many characters (limit is 10)' in resp.text
# check it counts characters, not bytes
resp.forms[0]['f0'] = 'â˜' * 10
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
def test_unknown_datasource(pub):
formdef = create_formdef()
formdef.fields = [
fields.StringField(
id='1', label='string', varname='string', required=False, data_source={'type': 'foobar'}
),
fields.ItemField(
id='2', label='item', varname='item', required=False, data_source={'type': 'foobar'}
),
fields.ItemsField(
id='3', label='items', varname='items', required=False, data_source={'type': 'foobar'}
),
]
formdef.store()
data_class = formdef.data_class()
data_class.wipe()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
def test_form_items_datasource(pub):
formdef = create_formdef()
formdef.fields = [
fields.ItemsField(
id='1', label='items', varname='items', required=False, data_source={'type': 'foobar'}
)
]
formdef.store()
data_class = formdef.data_class()
data_class.wipe()
# add the named data source
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'formula', 'value': repr([])}
data_source.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp = resp.forms[0].submit('submit') # should go straight to validation
assert 'Check values then click submit.' in resp.text
assert resp.forms[0]['previous']
resp = resp.forms[0].submit('previous')
# replace the named data source with one with items
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'formula', 'value': repr(['un', 'deux'])}
data_source.store()
resp = get_app(pub).get('/test/')
assert 'f1$elementun' in resp.form.fields
assert 'f1$elementdeux' in resp.form.fields
resp.form['f1$elementun'].checked = True
resp.form['f1$elementdeux'].checked = True
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert data_class.select()[0].data == {
'1': ['un', 'deux'],
'1_display': 'un, deux',
'1_structured': [{'id': 'un', 'text': 'un'}, {'id': 'deux', 'text': 'deux'}],
}
data_source.data_source = {
'type': 'formula',
'value': repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
}
data_source.store()
data_class.wipe()
resp = get_app(pub).get('/test/')
assert 'f1$element1' in resp.form.fields
assert 'f1$element2' in resp.form.fields
resp.form['f1$element1'].checked = True
resp.form['f1$element2'].checked = True
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert data_class.select()[0].data == {
'1': ['1', '2'],
'1_display': 'un, deux',
'1_structured': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}],
}
data_source.data_source = {
'type': 'formula',
'value': repr([{'id': '1', 'text': 'un', 'foo': 'bar1'}, {'id': '2', 'text': 'deux', 'foo': 'bar2'}]),
}
data_source.store()
data_class.wipe()
resp = get_app(pub).get('/test/')
assert 'f1$element1' in resp.form.fields
assert 'f1$element2' in resp.form.fields
resp.form['f1$element1'].checked = True
resp.form['f1$element2'].checked = True
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
resp = resp.follow()
assert data_class.select()[0].data == {
'1': ['1', '2'],
'1_structured': [
{'text': 'un', 'foo': 'bar1', 'id': '1'},
{'text': 'deux', 'foo': 'bar2', 'id': '2'},
],
'1_display': 'un, deux',
}
# along the way, check substitution variables
substvars = data_class.select()[0].get_substitution_variables()
assert substvars['form_var_items'] == 'un, deux'
assert substvars['form_var_items_raw'] == ['1', '2']
assert substvars['form_var_items_0_foo'] == 'bar1'
assert substvars['form_var_items_1_foo'] == 'bar2'
def test_form_ranked_items_field_submit(pub):
formdef = create_formdef()
formdef.fields = [
fields.RankedItemsField(
id='0', label='ranked items', type='ranked-items', required=False, items=['foo', 'bar', 'baz']
)
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.form['f0$element0'] = '1'
resp.form['f0$element1'] = '2'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
data = formdef.data_class().get(data_id)
assert data.data['0'] == {'bar': 2, 'foo': 1}
def test_form_ranked_items_randomize_order(pub):
formdef = create_formdef()
formdef.fields = [
fields.RankedItemsField(
id='0',
label='ranked items',
type='ranked-items',
required=False,
randomize_items=True,
items=['foo', 'bar', 'baz'],
)
]
formdef.store()
orders = {}
for _ in range(10):
resp = get_app(pub).get('/test/')
orders['%s-%s-%s' % (resp.text.index('foo'), resp.text.index('bar'), resp.text.index('baz'))] = True
assert len(orders.keys()) > 1
def test_form_autosave(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.enable_tracking_codes = True
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'foobar'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.status == 'draft'
assert formdef.data_class().select()[0].data['1'] == 'foobar'
resp.form['f1'] = 'foobar2'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().select()[0].data['1'] == 'foobar2'
resp.form['f1'] = 'foobar3'
resp = resp.forms[0].submit('submit')
assert formdef.data_class().select()[0].data['1'] == 'foobar3'
resp.form['f3'] = 'xxx'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().select()[0].data['1'] == 'foobar3'
assert formdef.data_class().select()[0].data['3'] == 'xxx'
resp.form['f3'] = 'xxx2'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().select()[0].data['1'] == 'foobar3'
assert formdef.data_class().select()[0].data['3'] == 'xxx2'
resp.form['f3'] = 'xxx3'
resp = resp.forms[0].submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'foobar3' in resp.text
assert 'xxx3' in resp.text
resp = resp.forms[0].submit('submit')
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].data['1'] == 'foobar3'
assert formdef.data_class().select()[0].data['3'] == 'xxx3'
# make sure autosave() doesn't destroy data that would have been submitted
# in the meantime
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'foobar'
autosave_fields = resp.form.submit_fields()
resp.form['f1'] = 'foobar3'
resp = resp.forms[0].submit('submit')
assert formdef.data_class().select()[0].data['1'] == 'foobar3'
# post content with 'foobar' as value, it should not be saved
ajax_resp = app.post('/test/autosave', params=autosave_fields)
assert json.loads(ajax_resp.text)['result'] == 'error'
assert formdef.data_class().select()[0].data['1'] == 'foobar3'
def test_form_autosave_timeout(pub, monkeypatch):
from wcs.forms.root import FormPage
monkeypatch.setattr(FormPage, 'AUTOSAVE_TIMEOUT', 0.0001)
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string 2'),
]
formdef.enable_tracking_codes = True
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'foobar'
resp = app.post('/test/autosave', params=resp.form.submit_fields())
assert resp.json == {'reason': 'too long', 'result': 'error'}
def test_form_autosave_with_items_field(pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.ItemsField(
id='3',
label='items',
type='items',
items=[force_str(x) for x in ('pomme', 'poire', 'pêche', 'abricot')],
),
]
formdef.enable_tracking_codes = True
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'bar'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.status == 'draft'
assert formdef.data_class().select()[0].data['1'] == 'bar'
assert formdef.data_class().select()[0].data.get('3') is None
resp = resp.forms[0].submit('submit')
resp.form['f3$element1'].checked = True
resp.form['f3$element3'].checked = True
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].data['1'] == 'bar'
assert formdef.data_class().select()[0].data['3'] == ['poire', 'abricot']
def test_form_autosave_with_invalid_data(pub):
formdef = create_formdef()
formdef.fields = [
fields.EmailField(id='1', label='email', type='email'),
]
formdef.enable_tracking_codes = True
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'foobar' # not a valid email
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.status == 'draft'
assert formdef.data_class().select()[0].data['1'] == 'foobar'
# restore draft
tracking_code = get_displayed_tracking_code(resp)
resp = get_app(pub).get('/')
resp.forms[0]['code'] = tracking_code
resp = resp.forms[0].submit().follow().follow().follow()
assert resp.forms[1]['f1'].value == 'foobar' # not a valid email
@responses.activate
def test_form_autosave_item_field_data_source_error(pub):
ds = {'type': 'json', 'value': 'http://www.example.net/plop'}
formdef = create_formdef()
formdef.fields = [
fields.ItemField(id='1', label='string', data_source=ds),
]
formdef.enable_tracking_codes = True
formdef.store()
responses.get(
'http://www.example.net/plop', json={'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]}
)
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = '1' # not a valid email
# make the ds fails
with mock.patch.object(NamedDataSource, 'get_structured_value', lambda *args: None):
autosave_resp = app.post('/test/autosave', params=resp.form.submit_fields())
assert autosave_resp.json == {
'reason': 'form deserialization failed: a datasource is unavailable',
'result': 'error',
}
autosave_resp = app.post('/test/autosave', params=resp.form.submit_fields())
assert autosave_resp.json['result'] == 'success'
def test_form_autosave_with_parameterized_datasource(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.ItemField(
id='3',
label='item',
type='item',
data_source={'type': 'formula', 'value': '''[('1', form_var_foo*2)]'''},
),
]
formdef.enable_tracking_codes = True
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'bar'
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.status == 'draft'
assert formdef.data_class().select()[0].data['1'] == 'bar'
assert formdef.data_class().select()[0].data.get('3') is None
resp = resp.forms[0].submit('submit')
assert app.post('/test/autosave', params=resp.form.submit_fields()).json == {'result': 'success'}
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].data['1'] == 'bar'
assert formdef.data_class().select()[0].data['3'] == '1'
assert formdef.data_class().select()[0].data['3_display'] == 'barbar'
def test_form_autosave_never_overwrite(mocker, pub, settings):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string1'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='3', label='string2'),
]
formdef.store()
app = get_app(pub)
login(app, username='foo', password='foo')
resp = app.get('/test/')
resp.form['f1'] = '1'
# go to the second page
resp = resp.form.submit('submit')
resp.form['f3'] = 'tmp'
# autosave this temporary data
autosave_data = dict(resp.form.submit_fields())
resp_autosave = app.post('/test/autosave', params=autosave_data)
assert resp_autosave.json == {'result': 'success'}
# check the draft has been modified
formdata = formdef.data_class().select()[0]
formdata.refresh_from_storage()
assert formdata.data['3'] == 'tmp'
# now finish submitting with new value
resp.form['f3'] = '1'
resp = resp.form.submit('submit') # -> validation page
formdata.refresh_from_storage()
assert formdata.data['3'] == '1'
# autosave wrong data
# _ajax_form_token is just a form_token, so take the current one to
# simulate a rogue autosave from the previous page
autosave_data['_ajax_form_token'] = resp.form['_form_id'].value
resp_autosave = app.post('/test/autosave', params=autosave_data)
formdata.refresh_from_storage()
assert resp_autosave.json != {'result': 'success'}
assert formdata.data['3'] == '1'
# validate
resp = resp.form.submit('submit') # -> submit
# everything is still fine in the end, even for pickle storage
# (as the overwritten # data are recreated from validation page)
assert formdef.data_class().select()[0].data == {'1': '1', '3': '1'}
def test_form_string_field_autocomplete(pub):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string', type='string', required=False)]
formdef.fields[0].data_source = {'type': 'jsonp'}
formdef.store()
# not filled completed, no call to .autocomplete
resp = get_app(pub).get('/test/')
assert ').autocomplete({' not in resp.text
# straight URL
formdef.fields[0].data_source = {'type': 'jsonp', 'value': 'http://example.org'}
formdef.store()
resp = get_app(pub).get('/test/')
assert ').autocomplete({' in resp.text
assert 'http://example.org' in resp.text
# URL from variable
formdef.fields[0].data_source = {'type': 'jsonp', 'value': '[site_url]'}
formdef.store()
resp = get_app(pub).get('/test/')
assert ').autocomplete({' in resp.text
assert 'http://example.net' in resp.text
def test_form_string_field_autocomplete_named_datasource(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.StringField(
id='0', label='string', type='string', required=False, data_source={'type': 'foobar'}
)
]
formdef.store()
# jsonp datasource
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'jsonp', 'value': 'http://remote.example.net/json'}
data_source.store()
resp = get_app(pub).get('/test/')
assert ').autocomplete({' in resp.text
assert "options.url = 'http://remote.example.net/json'" in resp.text
assert "options.url = '/api/autocomplete/" not in resp.text
assert 'dataType: "jsonp",' in resp.text
# json datasource
data_source.data_source['type'] = 'json'
data_source.query_parameter = 'q'
data_source.store()
resp = get_app(pub).get('/test/')
assert ').autocomplete({' in resp.text
assert "options.url = 'http://remote.example.net/json'" not in resp.text
assert "options.url = '/api/autocomplete/" in resp.text
assert 'dataType: "json",' in resp.text
# card datasource
CardDef.wipe()
carddef = CardDef()
carddef.name = 'Foo'
carddef.fields = []
carddef.store()
data_source.data_source['type'] = 'carddef:foo'
data_source.store()
resp = get_app(pub).get('/test/')
assert ').autocomplete({' in resp.text
assert "options.url = 'http://remote.example.net/json'" not in resp.text
assert "options.url = '/api/autocomplete/" in resp.text
assert 'dataType: "json",' in resp.text
def test_form_autocomplete_named_datasource_expired_token(pub):
CardDef.wipe()
FormDef.wipe()
pub.token_class.wipe()
formdef = FormDef()
formdef.name = 'test'
formdef.fields = [
fields.StringField(
id='0', label='string', type='string', required=False, data_source={'type': 'foobar'}
)
]
formdef.store()
carddef = CardDef()
carddef.name = 'Foo'
carddef.fields = []
carddef.store()
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'carddef:foo'}
data_source.store()
resp = get_app(pub).get('/test/')
assert pub.token_class.count() == 1
token = pub.token_class.select()[0]
assert '/api/autocomplete/%s' % token.id in resp.text
token.set_expiration_delay(-1) # expired
token.store()
# check a new token is generated
resp = get_app(pub).get('/test/')
assert '/api/autocomplete/%s' % token.id not in resp.text
def test_form_workflow_trigger(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.status = 'st2'
jump2 = st1.add_action('jump')
jump2.trigger = 'YYY'
jump2.status = 'st3'
jump2.set_marker_on_status = True
workflow.add_status('Status2', 'st2')
workflow.add_status('Status3', 'st3')
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
app = get_app(pub)
login(app, username='foo', password='foo').get('/')
app.post(formdata.get_url() + 'jump/trigger/XXX', status=403)
pub.role_class.wipe()
role = pub.role_class(name='xxx')
role.store()
jump.by = [role.id]
workflow.store()
app.post(formdata.get_url() + 'jump/trigger/XXX', status=403)
user.roles = [role.id]
user.store()
app.post(formdata.get_url() + 'jump/trigger/XXX', status=302)
formdata = formdef.data_class().get(formdata.id)
assert formdata.status == 'wf-st2'
formdata.status = 'wf-st1'
formdata.store()
app.post(formdata.get_url() + 'jump/trigger/YYY', status=403)
jump2.by = [role.id]
workflow.store()
app.post(formdata.get_url() + 'jump/trigger/YYY', status=302)
formdata = formdef.data_class().get(formdata.id)
assert formdata.status == 'wf-st3'
assert formdata.workflow_data.get('_markers_stack') == [{'status_id': 'st1'}]
formdata.status = 'wf-st1'
formdata.store()
app.post(
formdata.get_url() + 'jump/trigger/YYY',
params=json.dumps({'data': {'foo': 'bar'}}),
content_type='application/json',
)
formdata = formdef.data_class().get(formdata.id)
assert formdata.workflow_data.get('data') == {'foo': 'bar'}
def test_form_worklow_multiple_identical_status(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
st1.extra_css_class = 'CSS-STATUS1'
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.status = 'st1'
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
app = get_app(pub)
pub.role_class.wipe()
role = pub.role_class(name='xxx')
role.allows_backoffice_access = False
role.store()
jump.by = [role.id]
workflow.store()
user.roles = [role.id]
user.store()
assert len(formdef.data_class().get(formdata.id).evolution) == 1
assert formdef.data_class().get(formdata.id).evolution[0].last_jump_datetime is None
login(app, username='foo', password='foo')
resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302)
formdata = formdef.data_class().get(formdata.id)
# status is not changed: no new evolution, only a new last_jump_datetime
assert len(formdata.evolution) == 1
assert formdata.status == 'wf-st1'
assert formdata.evolution[0].last_jump_datetime is not None
assert (
formdef.data_class().get(formdata.id).get_static_substitution_variables()['form_status_changed']
is False
)
assert formdef.data_class().get(formdata.id).get_substitution_variables()['form_status_changed'] is False
# add a comment to last evolution, forcing create a new one
formdata.evolution[-1].comment = 'new-evolution-1'
formdata.store()
resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302)
formdata = formdef.data_class().get(formdata.id)
assert len(formdata.evolution) == 2
assert formdata.status == 'wf-st1'
assert (
formdef.data_class().get(formdata.id).get_static_substitution_variables()['form_status_changed']
is False
)
assert formdef.data_class().get(formdata.id).get_substitution_variables()['form_status_changed'] is False
# again
formdata.evolution[-1].comment = 'new-evolution-2'
formdata.store()
resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302)
# last evolution is empty, this last trigger does not create a new one
resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302)
# finally, 3 evolutions: new-evolution-1, new-evolution-2, empty
formdata = formdef.data_class().get(formdata.id)
assert len(formdata.evolution) == 3
assert formdata.status == 'wf-st1'
assert formdata.evolution[0].comment == 'new-evolution-1'
assert formdata.evolution[1].comment == 'new-evolution-2'
assert formdata.evolution[2].comment is None
# mark user as owner so it can check the UI
formdata.user_id = user.id
formdata.store()
resp = app.get(formdata.get_url())
assert resp.text.count('Status1') == 3 # once in summary and two in journal
assert resp.text.count('CSS-STATUS1') == 2
assert resp.text.count('new-evolution-1') == 1
assert resp.text.count('new-evolution-2') == 1
def test_form_worklow_comments_on_same_status(pub):
pub.session_manager.session_class.wipe()
user = create_user(pub)
role = pub.role_class(name='xxx')
role.store()
user.roles = [role.id]
user.store()
formdef = create_formdef()
formdef.fields = []
formdef.workflow_roles = {'_receiver': role.id}
formdef.store()
workflow = Workflow.get_default_workflow()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.perform_workflow()
formdata.store()
assert formdef.data_class().get(formdata.id).status == 'wf-new'
app = get_app(pub)
assert (
formdef.data_class().get(formdata.id).get_static_substitution_variables()['form_status_changed']
is True
)
assert formdef.data_class().get(formdata.id).get_substitution_variables()['form_status_changed'] is True
login(app, username='foo', password='foo')
resp = app.get(formdata.get_url()).follow()
resp.form['comment'] = 'TEST COMMENT'
resp = resp.form.submit('button_commentable')
assert (
formdef.data_class().get(formdata.id).get_static_substitution_variables()['form_status_changed']
is False
)
assert formdef.data_class().get(formdata.id).get_substitution_variables()['form_status_changed'] is False
resp = app.get(formdata.get_url()).follow()
resp = resp.form.submit('button_accept')
assert (
formdef.data_class().get(formdata.id).get_static_substitution_variables()['form_status_changed']
is True
)
assert formdef.data_class().get(formdata.id).get_substitution_variables()['form_status_changed'] is True
def test_form_worklow_double_comments(pub):
Workflow.wipe()
create_user(pub)
wf = Workflow(name='status')
st1 = wf.add_status('Status1', 'st1')
commentable = st1.add_action('commentable', id='1')
commentable.by = [logged_users_role().id]
commentable = st1.add_action('commentable', id='2')
commentable.by = [logged_users_role().id]
wf.store()
formdef = create_formdef()
formdef.workflow = wf
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.perform_workflow()
formdata.store()
app = get_app(pub)
login(app, username='foo', password='foo')
resp = app.get(formdata.get_url())
resp.form['comment'] = 'TEST COMMENT'
resp = resp.form.submit('button_commentable').follow()
assert resp.text.count('TEST COMMENT') == 1
def test_display_message(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st0 = workflow.add_status('Status0', 'st0')
jump = st0.add_action('jump')
jump.status = 'st1'
st1 = workflow.add_status('Status1', 'st1')
display1 = st1.add_action('displaymsg')
display1.message = 'message-to-all'
display1.to = []
display2 = st1.add_action('displaymsg')
display2.message = 'message-to-submitter'
display2.to = ['_submitter']
display3 = st1.add_action('displaymsg')
display3.message = 'message-to-nobody'
display3.to = ['xxx']
display4 = st1.add_action('displaymsg')
display4.message = 'message-to-xxx-and-submitter'
display4.to = ['_submitter', 'xxx']
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
page = app.get('/test/')
page = page.forms[0].submit('submit') # form page
page = page.forms[0].submit('submit') # confirmation page
page = page.follow()
assert 'message-to-all' in page.text
assert 'message-to-submitter' in page.text
assert 'message-to-nobody' not in page.text
assert 'message-to-xxx-and-submitter' in page.text
assert page.text.index('message-to-submitter') < page.text.index('message-to-xxx-and-submitter')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
# actions alert vs top alert
display2.position = 'actions'
workflow.store()
page = app.get(formdata.get_url())
assert 'message-to-all' in page.text
assert 'message-to-submitter' not in page.text
assert 'message-to-xxx-and-submitter' in page.text
# add an action, so display2 will appear again
jump1 = st1.add_action('choice', id='_jump1')
jump1.label = 'Jump 1'
jump1.by = ['_submitter']
jump1.status = st1.id
workflow.store()
page = app.get(formdata.get_url())
assert 'message-to-all' in page.text
assert 'message-to-submitter' in page.text
assert 'message-to-xxx-and-submitter' in page.text
assert page.text.index('message-to-submitter') > page.text.index('message-to-xxx-and-submitter')
jump1.by = ['xxx']
workflow.store()
page = app.get(formdata.get_url())
assert 'message-to-all' in page.text
assert 'message-to-submitter' not in page.text
assert 'message-to-xxx-and-submitter' in page.text
# change to always display at the bottom
display2.position = 'bottom'
workflow.store()
page = app.get(formdata.get_url())
assert 'message-to-all' in page.text
assert 'message-to-submitter' in page.text
assert 'message-to-xxx-and-submitter' in page.text
assert page.text.index('message-to-submitter') > page.text.index('message-to-xxx-and-submitter')
# set a level
display2.level = 'warning'
workflow.store()
page = app.get(formdata.get_url())
assert 'warningnotice' in page.text
# check message is not displayed if status is not visible to user
st1.visibility = ['_receiver']
workflow.store()
page = app.get(formdata.get_url())
assert 'warningnotice' not in page.text
def test_workflow_condition_on_message(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
display1 = st1.add_action('displaymsg')
display1.message = 'message-to-all'
display1.to = []
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
page = app.get('/test/')
page = page.forms[0].submit('submit') # form page
page = page.forms[0].submit('submit') # confirmation page
page = page.follow()
assert 'message-to-all' in page.text
formdata = formdef.data_class().select()[0]
page = app.get(formdata.get_url())
assert 'message-to-all' in page.text
display1.condition = {'type': 'django', 'value': 'xxx'}
workflow.store()
page = app.get(formdata.get_url())
assert 'message-to-all' not in page.text
def test_workflow_message_with_template_error(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
display1 = st1.add_action('displaymsg')
display1.message = '
{% for x in 0 %}crash{% endfor %}'
display1.to = []
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
resp = resp.forms[0].submit('submit') # form page
resp = resp.forms[0].submit('submit') # confirmation page
resp = resp.follow()
assert 'Error rendering message.' in resp.text
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.summary == "Error in template of workflow message ('int' object is not iterable)"
def test_session_cookie_flags(pub):
create_formdef()
app = get_app(pub)
resp = app.get('/test/', status=200)
assert resp.headers['Set-Cookie'].startswith('sessionid-')
assert 'HttpOnly' in resp.headers['Set-Cookie']
assert 'Secure' not in resp.headers['Set-Cookie']
app = get_app(pub, https=True)
resp = app.get('/test/', status=200)
assert resp.headers['Set-Cookie'].startswith('sessionid-')
assert 'HttpOnly' in resp.headers['Set-Cookie']
assert 'Secure' in resp.headers['Set-Cookie']
def test_form_page_profile_verified_prefill(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [fields.StringField(id='0', label='string', prefill={'type': 'user', 'value': 'email'})]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0'].value == ''
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.form['f0'].value == 'foo@localhost'
assert 'readonly' not in resp.form['f0'].attrs
resp.form['f0'].value = 'Hello'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['f0'].value == 'Hello'
user.verified_fields = ['email']
user.store()
for prefill_settings in (
{'type': 'user', 'value': 'email'}, # verified profile
{'type': 'string', 'value': 'foo@localhost', 'locked': True}, # locked value
):
formdef.confirmation = True
formdef.fields[0].prefill = prefill_settings
formdef.store()
formdef.data_class().wipe()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.form['f0'].value == 'foo@localhost'
assert 'readonly' in resp.form['f0'].attrs
resp.form['f0'].value = 'Hello' # try changing the value
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['f0'].value == 'foo@localhost' # it is reverted
resp.form['f0'].value = 'Hello' # try again changing the value
resp = resp.form.submit('submit')
formdatas = [x for x in formdef.data_class().select() if not x.is_draft()]
assert len(formdatas) == 1
assert formdatas[0].data['0'] == 'foo@localhost'
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.form['f0'].value == 'foo@localhost'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp.form['f0'].value = 'Hello' # try changing
resp = resp.form.submit('previous')
assert 'readonly' in resp.form['f0'].attrs
assert 'Check values then click submit.' not in resp.text
assert resp.form['f0'].value == 'foo@localhost'
# try it without validation page
formdef.confirmation = False
formdef.store()
formdef.data_class().wipe()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.form['f0'].value == 'foo@localhost'
assert 'readonly' in resp.form['f0'].attrs
resp.form['f0'].value = 'Hello' # try changing the value
resp = resp.form.submit('submit')
formdatas = [x for x in formdef.data_class().select() if not x.is_draft()]
assert len(formdatas) == 1
assert formdatas[0].data['0'] == 'foo@localhost'
def test_form_worklow_multiple_identical_status_with_wserror(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = []
formdef.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.trigger = 'XXX'
jump.status = 'st1'
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
assert formdef.data_class().get(formdata.id).status == 'wf-st1'
app = get_app(pub)
login(app, username='foo', password='foo')
pub.role_class.wipe()
role = pub.role_class(name='xxx')
role.allows_backoffice_access = False
role.store()
jump.by = [role.id]
workflow.store()
user.roles = [role.id]
user.store()
for _i in range(3):
resp = app.post(formdata.get_url() + 'jump/trigger/XXX', status=302)
formdata = formdef.data_class().get(formdata.id)
formdata.evolution[-1].add_part(JournalWsCallErrorPart('bla', 'bla', {}))
formdata.store()
# mark user as owner so it can check the UI
formdata.user_id = user.id
formdata.store()
resp = app.get(formdata.get_url())
assert resp.text.count('
with the real value
# then an unnamed with the formatted value.
assert resp.form['f0'].attrs['type'] == 'hidden'
assert resp.pyquery('input#form_f0[type=text][readonly=readonly]')
resp.form['f0'].value = '2018-09-24' # try changing the value
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['f0'].value == '2018-09-27' # it is reverted
resp.form['f0'].value = '2018-09-24' # try again changing the value
resp = resp.form.submit('submit')
formdatas = [x for x in formdef.data_class().select() if not x.is_draft()]
assert len(formdatas) == 1
assert time.strftime('%Y-%m-%d', formdatas[0].data['0']) == '2018-09-27'
def test_form_page_profile_date_as_locked_string_prefill(pub):
user = create_user(pub)
from wcs.admin.settings import UserFieldsFormDef
user_formdef = UserFieldsFormDef(pub)
user_formdef.fields.append(fields.DateField(id='_date', label='date', type='date'))
user_formdef.store()
user.form_data = {'_date': time.strptime('2018-09-27', '%Y-%m-%d')}
user.set_attributes_from_formdata(user.form_data)
user.store()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='1', label='1st page', type='page'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(id='0', label='date', prefill={'type': 'user', 'value': '_date', 'locked': True}),
]
formdef.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp = resp.form.submit('submit') # -> second page
assert resp.form['f0'].value == '2018-09-27'
assert 'readonly' in resp.form['f0'].attrs
resp.form['f0'].value = '2015-09-27'
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['f0'].value == '2018-09-27'
def test_form_page_profile_verified_radio_item_prefill(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='0',
label='item',
type='item',
items=['bar@localhost', 'foo@localhost', 'baz@localhost'],
display_mode='radio',
prefill={'type': 'user', 'value': 'email'},
)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0'].value is None
user.verified_fields = ['email']
user.store()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
assert resp.form['f0'].value == 'foo@localhost'
assert 'disabled' in resp.form['f0'].attrs
for radio in resp.html.findAll('input'):
if radio['name'] == 'f0':
if radio['value'] == 'foo@localhost':
assert radio.attrs.get('checked')
assert not radio.attrs.get('disabled')
else:
assert not radio.attrs.get('checked')
assert radio.attrs.get('disabled')
resp.form['f0'].value = 'baz@localhost' # try changing the value
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert resp.form['f0'].value == 'foo@localhost' # it is reverted
def test_item_field_from_cards(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
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.fields = [
fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=True)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0'].options == [
('2', False, 'bar'),
('3', False, 'baz'),
('1', False, 'foo'),
]
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'bar'
assert formdef.data_class().select()[0].data['0_structured']['name'] == 'bar'
def test_item_field_from_cards_id_identifier(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
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='id'),
]
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.fields = [
fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=True)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0'].options == [
('2', False, 'bar'),
('3', False, 'baz'),
('1', False, 'foo'),
]
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'bar'
assert formdef.data_class().select()[0].data['0_structured']['name'] == 'bar'
def test_item_field_from_cards_then_comment_related_card(pub):
# https://dev.entrouvert.org/issues/58292
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
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}
carddef2 = CardDef()
carddef2.name = 'others'
carddef2.fields = [fields.ItemField(id='0', varname='card1', type='item', data_source=ds)]
carddef2.store()
carddata2 = carddef2.data_class()()
carddata2.data = {'0': str(carddata.id), '0_display': 'baz'}
carddata2.just_created()
carddata2.store()
formdef.fields = [
fields.PageField(id='1', label='1st page', type='page'),
fields.ItemField(
id='0', label='string', type='item', data_source=ds, display_disabled_items=True, varname='card'
),
fields.PageField(id='2', label='2nd page', type='page'),
fields.CommentField(
id='3',
label='card value: {{ cards|objects:"others"|filter_by:"card1"|filter_value:form_var_card|first|get:"form_number" }}
',
type='comment',
),
]
formdef.store()
resp = get_app(pub).get('/test/')
resp.form['f0'] = '3'
resp = resp.form.submit('submit') # -> second page
assert 'card value: %s' % carddata2.get_display_id() in resp.text
def test_item_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 = create_formdef()
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.ItemField(id='0', type='item', label='item', varname='item', items=['foo', 'bar', 'baz']),
fields.StringField(id='1', type='string', label='string', varname='attr'),
]
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,
'0_display': value,
'1': 'attr%s' % (i + 1),
}
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(' validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == baz_id
assert formdef.data_class().select()[0].data['0_display'] == 'attr%s' % baz_id
assert formdef.data_class().select()[0].data['0_structured']['item'] == 'baz'
# give custom view it a custom digest
formdef.data_class().wipe()
carddef.digest_templates['custom-view:%s' % custom_view.slug] = 'X{{form_var_attr}}Y'
carddef.store()
# compute digests
for carddata in carddef.data_class().select():
carddata.store()
resp = get_app(pub).get('/test/')
assert len(resp.form['f0'].options) == 10
assert {x[0] for x in resp.form['f0'].options} == baz_ids
resp.form['f0'].value = baz_id
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == baz_id
assert formdef.data_class().select()[0].data['0_display'] == 'Xattr%sY' % baz_id
def test_item_field_from_custom_view_on_cards_filter_status(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 = create_formdef()
formdef.data_class().wipe()
card_workflow = CardDef.get_default_workflow()
st1 = card_workflow.add_status('Status1', 'st1')
card_workflow.id = None
card_workflow.store()
CardDef.wipe()
carddef = CardDef()
carddef.workflow_id = card_workflow.id
carddef.name = 'items'
carddef.digest_templates = {'default': '{{form_var_attr}}'}
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.fields = [
fields.ItemField(id='0', type='item', label='item', varname='item', items=['foo', 'bar', 'baz']),
fields.StringField(id='1', type='string', 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,
'0_display': value,
'1': 'attr%s' % (i + 1),
}
carddata.just_created()
carddata.store()
carddata.jump_status(st1.id)
carddata.store()
# create custom view
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/backoffice/data/items/')
resp.forms['listing-settings']['filter-status'].checked = True
resp = resp.forms['listing-settings'].submit()
resp.forms['listing-settings']['filter-operator'].value = 'ne'
resp.forms['listing-settings']['filter'].value = 'st1'
resp = resp.forms['listing-settings'].submit()
assert resp.pyquery('tbody tr').length == 2
resp.forms['save-custom-view']['title'] = 'as data source'
resp.forms['save-custom-view']['visibility'] = 'datasource'
resp = resp.forms['save-custom-view'].submit()
custom_view = pub.custom_view_class.select()[0]
assert custom_view.filters == {'filter-operator': 'ne', 'filter': 'st1', 'filter-status': 'on'}
# use custom view as source
ds = {'type': 'carddef:%s:%s' % (carddef.url_name, custom_view.slug)}
formdef.fields = [
fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=True)
]
formdef.store()
resp = get_app(pub).get('/test/')
assert len(resp.form['f0'].options) == 2
assert {x[2] for x in resp.form['f0'].options} == {'attr1', 'attr2'}
custom_view.filters['filter-operator'] = 'eq'
custom_view.store()
resp = get_app(pub).get('/test/')
assert [x[2] for x in resp.form['f0'].options] == ['attr3']
custom_view.filters['filter'] = 'all'
custom_view.store()
resp = get_app(pub).get('/test/')
assert len(resp.form['f0'].options) == 3
custom_view.filters['filter'] = 'all'
custom_view.filters['filter-operator'] = 'ne'
custom_view.store()
resp = get_app(pub).get('/test/')
assert len(resp.form['f0'].options) == 1
assert [x[2] for x in resp.form['f0'].options] == ['---']
@pytest.mark.parametrize('filter_value', ['{{ "foo" }}', 'foo'])
def test_items_field_from_custom_view_on_cards(pub, filter_value):
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 = create_formdef()
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_item}}'}
carddef.workflow_roles = {'_editor': user.roles[0]}
carddef.fields = [
fields.ItemsField(id='0', type='items', label='item', varname='item', 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([(v1, v2) for (v1, v2) in itertools.product(items, items) if v1 != v2]):
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')
# we must force the ordering to have a determinist test
resp = app.get('/backoffice/data/items/?order_by=id')
assert resp.text.count(' validation page
resp = resp.form.submit('submit') # -> submit
formdata = formdef.data_class().select()[0]
assert formdata.data['0'] in foo_bar_ids
assert formdata.data['0_structured']['text'] == 'attr0 - foo,bar'
def test_item_field_with_disabled_items(http_requests, pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
formdef.fields = [
fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=True)
]
formdef.store()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
resp.form['f0'] = '1'
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'world'
formdef.data_class().wipe()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
pq = resp.pyquery.remove_namespaces()
assert pq('option[disabled=disabled][value="1"]').text() == 'hello'
resp.form['f0'] = '1'
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'world'
resp = get_app(pub).get('/test/')
pq = resp.pyquery.remove_namespaces()
assert pq('option[disabled=disabled][value="1"]').text() == 'hello'
resp.form['f0'] = '1'
resp = resp.form.submit('submit') # -> validation page
assert 'There were errors processing the form' in resp.text
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(id='0', label='string', type='item', data_source=ds, display_disabled_items=False)
]
formdef.store()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
pq = resp.pyquery.remove_namespaces()
assert len(pq('option[disabled=disabled][value="1"]')) == 0
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'world'
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='0',
label='string',
type='item',
data_source=ds,
display_mode='radio',
display_disabled_items=True,
)
]
formdef.store()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
resp.form['f0'] = '1'
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'world'
formdef.data_class().wipe()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
pq = resp.pyquery.remove_namespaces()
assert len(pq('input[name="f0"][disabled=disabled][value="1"]')) == 1
resp.form['f0'] = '1'
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'world'
resp = get_app(pub).get('/test/')
pq = resp.pyquery.remove_namespaces()
assert len(pq('input[name="f0"][disabled=disabled][value="1"]')) == 1
resp.form['f0'] = '1'
resp = resp.form.submit('submit') # -> validation page
assert 'There were errors processing the form' in resp.text
def test_items_field_with_disabled_items(http_requests, pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
formdef.fields = [
fields.ItemsField(id='0', label='string', type='items', data_source=ds, display_disabled_items=True)
]
formdef.store()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
resp.form['f0$element1'].checked = True
resp.form['f0$element2'].checked = True
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == ['1', '2']
assert formdef.data_class().select()[0].data['0_display'] == 'hello, world'
formdef.data_class().wipe()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
assert 'disabled' in resp.form['f0$element1'].attrs
resp.form['f0$element1'].checked = True
resp.form['f0$element2'].checked = True
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == ['2']
assert formdef.data_class().select()[0].data['0_display'] == 'world'
formdef.data_class().wipe()
formdef.fields = [
fields.ItemsField(id='0', label='string', type='items', data_source=ds, display_disabled_items=False)
]
formdef.store()
with responses.RequestsMock() as rsps:
rsps.get(
'http://remote.example.net/json',
json={'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]},
)
resp = get_app(pub).get('/test/')
assert 'f0$element1' not in resp.form.fields
resp.form['f0$element2'].checked = True
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == ['2']
assert formdef.data_class().select()[0].data['0_display'] == 'world'
def test_item_field_autocomplete_json_source(http_requests, pub, error_email, emails):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'json', 'value': 'http://remote.example.net/json'}
data_source.store()
formdef.fields = [
fields.ItemField(
id='0',
label='string',
type='item',
data_source={'type': 'foobar'},
display_mode='autocomplete',
),
]
formdef.store()
with responses.RequestsMock() as rsps:
data = {
'data': [
{'id': '1', 'text': 'hello', 'extra': 'foo'},
{'id': '2', 'text': 'world', 'extra': 'bar'},
]
}
rsps.get('http://remote.example.net/json', json=data)
resp = get_app(pub).get('/test/')
assert 'data-autocomplete="true"' in resp.text
assert resp.form['f0'].value == '1'
resp.form['f0'] = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'world'
assert formdef.data_class().select()[0].data['0_structured'] == data['data'][1]
# check hint is displayed within
formdef.fields[0].hint = 'help text'
formdef.store()
with responses.RequestsMock() as rsps:
data = {
'data': [
{'id': '1', 'text': 'hello', 'extra': 'foo'},
{'id': '2', 'text': 'world', 'extra': 'bar'},
]
}
rsps.get('http://remote.example.net/json', json=data)
resp = get_app(pub).get('/test/')
assert 'data-autocomplete="true"' in resp.text
assert 'data-hint="help text"' in resp.text
assert resp.form['f0'].value == ''
formdef.fields[0].hint = ''
formdef.store()
# check with possibility of remote query
data_source.query_parameter = 'q'
data_source.id_parameter = 'id'
data_source.store()
formdef.data_class().wipe()
app = get_app(pub)
with responses.RequestsMock() as rsps:
data = {
'data': [
{'id': '1', 'text': 'hello', 'extra': 'foo'},
{'id': '2', 'text': 'world', 'extra': 'bar'},
]
}
resp = app.get('/test/')
assert len(rsps.calls) == 0
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
with responses.RequestsMock() as rsps:
data = {'data': [{'id': '1', 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json', json=data)
resp2 = app.get(select2_url + '?q=hell')
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json?q=hell'
assert resp2.json == data
# check unauthorized access
resp2 = get_app(pub).get(select2_url + '?q=hell', status=403)
# check error handling in autocomplete endpoint
formdef.data_class().wipe()
app = get_app(pub)
with responses.RequestsMock() as rsps:
rsps.get('http://remote.example.net/json', body=ConnectionError('...'))
resp = app.get('/test/')
assert len(rsps.calls) == 0
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
assert emails.count() == 0
resp2 = app.get(select2_url + '?q=hell')
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json?q=hell'
assert resp2.json == {'data': [], 'err': '1'}
assert emails.count() == 0
data_source.notify_on_errors = True
data_source.record_on_errors = True
data_source.store()
resp2 = app.get(select2_url + '?q=hell')
assert emails.count() == 1
assert emails.get_latest('subject') == '[ERROR] [DATASOURCE] Error loading JSON data source (...)'
assert pub.loggederror_class.count() == 1
logged_error = pub.loggederror_class.select()[0]
assert logged_error.workflow_id is None
assert logged_error.summary == '[DATASOURCE] Error loading JSON data source (...)'
data_source.notify_on_errors = False
data_source.store()
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f0_display'] = Hidden(form=resp.form, tag='input', name='f0_display', pos=10)
resp.form['f0'].force_value('1')
resp.form.fields['f0_display'].force_value('hello')
with responses.RequestsMock() as rsps:
data = {'data': [{'id': '1', 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json', json=data)
resp = resp.form.submit('submit') # -> validation page
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json?id=1'
assert resp.form['f0'].value == '1'
assert resp.form['f0_label'].value == 'hello'
with responses.RequestsMock() as rsps:
data = {'data': [{'id': '1', 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json', json=data)
resp = resp.form.submit('submit') # -> submit
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json?id=1'
assert formdef.data_class().select()[0].data['0'] == '1'
assert formdef.data_class().select()[0].data['0_display'] == 'hello'
assert formdef.data_class().select()[0].data['0_structured'] == data['data'][0]
# same thing with numeric identifiers
formdef.data_class().wipe()
data_source.data_source = {'type': 'json', 'value': 'http://remote.example.net/json-numeric-id'}
data_source.store()
app = get_app(pub)
with responses.RequestsMock() as rsps:
resp = app.get('/test/')
assert len(rsps.calls) == 0
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
with responses.RequestsMock() as rsps:
data = {'data': [{'id': 1, 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json-numeric-id', json=data)
resp2 = app.get(select2_url + '?q=hell')
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json-numeric-id?q=hell'
assert resp2.json == data
# check unauthorized access
resp2 = get_app(pub).get(select2_url + '?q=hell', status=403)
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f0_display'] = Hidden(form=resp.form, tag='input', name='f0_display', pos=10)
resp.form['f0'].force_value('1')
resp.form.fields['f0_display'].force_value('hello')
with responses.RequestsMock() as rsps:
data = {'data': [{'id': 1, 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json-numeric-id', json=data)
resp = resp.form.submit('submit') # -> validation page
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json-numeric-id?id=1'
assert resp.form['f0'].value == '1'
assert resp.form['f0_label'].value == 'hello'
with responses.RequestsMock() as rsps:
data = {'data': [{'id': 1, 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json-numeric-id', json=data)
resp = resp.form.submit('submit') # -> submit
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url == 'http://remote.example.net/json-numeric-id?id=1'
assert formdef.data_class().select()[0].data['0'] == '1'
assert formdef.data_class().select()[0].data['0_display'] == 'hello'
assert formdef.data_class().select()[0].data['0_structured'] == data['data'][0]
# same thing with signed URLs
data_source.data_source = {'type': 'json', 'value': 'http://remote.example.net/json'}
data_source.store()
formdef.data_class().wipe()
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
fd.write(
'''\
[wscall-secrets]
remote.example.net = 1234
'''
)
app = get_app(pub)
with responses.RequestsMock() as rsps:
resp = app.get('/test/')
assert len(rsps.calls) == 0
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
with responses.RequestsMock() as rsps:
data = {'data': [{'id': '1', 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json', json=data)
resp2 = app.get(select2_url + '?q=hell')
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url.startswith(
'http://remote.example.net/json?q=hell&orig=example.net&'
)
assert resp2.json == data
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f0_display'] = Hidden(form=resp.form, tag='input', name='f0_display', pos=10)
resp.form['f0'].force_value('1')
resp.form.fields['f0_display'].force_value('hello')
with responses.RequestsMock() as rsps:
data = {'data': [{'id': '1', 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json', json=data)
resp = resp.form.submit('submit') # -> validation page
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url.startswith('http://remote.example.net/json?id=1&orig=example.net&')
assert resp.form['f0'].value == '1'
assert resp.form['f0_label'].value == 'hello'
with responses.RequestsMock() as rsps:
data = {'data': [{'id': '1', 'text': 'hello', 'extra': 'foo'}]}
rsps.get('http://remote.example.net/json', json=data)
resp = resp.form.submit('submit') # -> submit
assert len(rsps.calls) == 1
assert rsps.calls[-1].request.url.startswith('http://remote.example.net/json?id=1&orig=example.net&')
assert formdef.data_class().select()[0].data['0'] == '1'
assert formdef.data_class().select()[0].data['0_display'] == 'hello'
assert formdef.data_class().select()[0].data['0_structured'] == data['data'][0]
# check with optional field
formdef.data_class().wipe()
formdef.fields[0].required = False
formdef.store()
app = get_app(pub)
with responses.RequestsMock() as rsps:
data = {
'data': [
{'id': '1', 'text': 'hello', 'extra': 'foo'},
{'id': '2', 'text': 'world', 'extra': 'bar'},
]
}
rsps.get('http://remote.example.net/json', json=data)
resp = app.get('/test/')
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
rsps.reset()
resp = resp.form.submit('submit') # -> validation page
assert resp.form['f0'].value == ''
assert resp.form['f0_label'].value == ''
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] is None
# check there's no crash if url is empty
data_source.data_source = {'type': 'json', 'value': '{% if 0 %}http://remote.example.net/json{% endif %}'}
data_source.store()
app = get_app(pub)
with responses.RequestsMock() as rsps:
resp = app.get('/test/')
assert len(rsps.calls) == 0
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
with responses.RequestsMock() as rsps:
resp2 = app.get(select2_url + '?q=hell', status=403)
assert len(rsps.calls) == 0
def test_item_field_autocomplete_jsonp_source(http_requests, pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'jsonp', 'value': 'http://remote.example.net/jsonp'}
data_source.store()
formdef.fields = [
fields.ItemField(
id='0',
label='string',
type='item',
data_source={'type': 'foobar'},
display_mode='autocomplete',
),
]
formdef.store()
app = get_app(pub)
with responses.RequestsMock() as rsps:
resp = app.get('/test/')
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
assert select2_url == 'http://remote.example.net/jsonp'
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f0_display'] = [Hidden(form=resp.form, tag='input', name='f0_display', pos=10)]
resp.form.field_order.append(('f0_display', resp.form.fields['f0_display'][0]))
resp.form['f0'].force_value('1')
resp.form['f0_display'].force_value('hello')
with responses.RequestsMock() as rsps:
resp = resp.form.submit('submit') # -> validation page
assert len(rsps.calls) == 0
assert resp.form['f0'].value == '1'
assert resp.form['f0_label'].value == 'hello'
with responses.RequestsMock() as rsps:
resp = resp.form.submit('submit') # -> submit
assert len(rsps.calls) == 0
assert formdef.data_class().select()[0].data['0'] == '1'
assert formdef.data_class().select()[0].data['0_display'] == 'hello'
# no _structured data for pure jsonp sources
assert '0_structured' not in formdef.data_class().select()[0].data
def test_item_field_autocomplete_cards_source(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().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()
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.fields = [
fields.ItemField(
id='0',
label='string',
type='item',
data_source=ds,
display_mode='autocomplete',
),
]
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
select2_url = resp.pyquery('select').attr['data-select2-url']
resp2 = app.get(select2_url + '?q=ba')
assert [x['text'] for x in resp2.json['data']] == ['bar', 'baz']
resp.form['f0'].force_value(str(resp2.json['data'][0]['id']))
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert formdef.data_class().select()[0].data['0'] == '2'
assert formdef.data_class().select()[0].data['0_display'] == 'bar'
def test_item_field_autocomplete_ezt_variable_jsonp(http_requests, pub):
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemField(
id='1',
label='foo',
type='item',
varname='foo',
data_source={'type': 'jsonp', 'value': '[site_url]/foo-jsonp'},
),
fields.PageField(id='2', label='2nd page', type='page'),
fields.ItemField(
id='3',
label='bar',
type='item',
data_source={'type': 'jsonp', 'value': '[site_url]/foo-jsonp?a=[form_var_foo_raw]'},
),
]
formdef.store()
app = get_app(pub)
with responses.RequestsMock() as rsps:
resp = app.get('/test/')
assert len(rsps.calls) == 0
pq = resp.pyquery.remove_namespaces()
select2_url = pq('select').attr['data-select2-url']
assert select2_url == 'http://example.net/foo-jsonp'
# 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)]
resp.form.field_order.append(('f1_display', resp.form.fields['f1_display'][0]))
resp.form['f1'].force_value('1')
resp.form['f1_display'].force_value('hello')
with responses.RequestsMock() as rsps:
resp = resp.form.submit('submit') # -> 2nd page
assert len(rsps.calls) == 0
assert resp.pyquery('select').attr['data-select2-url'] == 'http://example.net/foo-jsonp?a=1'
# simulate select2 mode, with qommon.forms.js adding an extra hidden widget
resp.form.fields['f3_display'] = [Hidden(form=resp.form, tag='input', name='f3_display', pos=10)]
resp.form.field_order.append(('f3_display', resp.form.fields['f3_display'][0]))
resp.form['f3'].force_value('2')
resp.form['f3_display'].force_value('hello2')
with responses.RequestsMock() as rsps:
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submit
assert len(rsps.calls) == 0
assert formdef.data_class().select()[0].data['1'] == '1'
assert formdef.data_class().select()[0].data['1_display'] == 'hello'
assert formdef.data_class().select()[0].data['3'] == '2'
assert formdef.data_class().select()[0].data['3_display'] == 'hello2'
# no _structured data for pure jsonp sources
assert '1_structured' not in formdef.data_class().select()[0].data
assert '3_structured' not in formdef.data_class().select()[0].data
def test_form_data_keywords(pub):
formdef = create_formdef()
formdef.keywords = 'hello,world'
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert 'data-keywords="hello world"' in resp.text
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
assert 'data-keywords="hello world"' in resp.text
resp = resp.form.submit('submit')
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
def test_logged_errors(pub):
Workflow.wipe()
workflow = Workflow.get_default_workflow()
workflow.id = '12'
st1 = workflow.possible_status[0]
jump = st1.add_action('jump', id='_jump', prepend=True)
jump.id = '_jump'
jump.status = 'rejected'
jump.condition = {'type': 'python', 'value': '1//0'} # ZeroDivisionError
workflow.store()
FormDef.wipe()
formdef = FormDef()
formdef.id = '34'
formdef.workflow = workflow
formdef.name = 'test'
formdef.confirmation = False
formdef.fields = []
formdef.store()
app = get_app(pub)
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert pub.loggederror_class.count() == 1
# new expression, but raise the same exception (ZeroDivisionError),
# just update the created logged error
jump.condition = {'type': 'python', 'value': '2//0'}
workflow.store()
resp = app.get('/test/')
resp = resp.form.submit('submit').follow()
resp = resp.form.submit('submit')
assert pub.loggederror_class.count() == 1
error = list(
pub.loggederror_class.get_with_indexed_value(
'tech_id',
'34-12-just_submitted-_jump-failed-to-evaluate-condition-ZeroDivisionError-integer-division-or-modulo-by-zero',
)
)[0]
assert error.occurences_count == 2
assert error.expression == '2//0'
assert len(list(pub.loggederror_class.get_with_indexed_value('formdef_id', '34'))) == 1
assert len(list(pub.loggederror_class.get_with_indexed_value('formdef_id', 'X'))) == 0
assert len(list(pub.loggederror_class.get_with_indexed_value('workflow_id', '12'))) == 1
assert len(list(pub.loggederror_class.get_with_indexed_value('workflow_id', 'X'))) == 0
def test_resubmit(pub):
user = create_user(pub)
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [fields.StringField(id='1', label='string', varname='toto')]
formdef.store()
formdef2 = FormDef()
formdef2.name = 'form title bis'
formdef2.enable_tracking_codes = True
formdef2.fields = [
fields.StringField(id='1', label='string', varname='titi'),
fields.StringField(id='2', label='string', varname='toto'),
]
formdef2.store()
wf = Workflow(name='resubmit')
st1 = wf.add_status('Status1')
st2 = wf.add_status('Status2')
resubmit = st1.add_action('resubmit', id='_resubmit')
resubmit.by = ['_submitter']
resubmit.formdef_slug = formdef2.url_name
jump = st1.add_action('jumponsubmit', id='_jump')
jump.status = st2.id
register_comment = st2.add_action('register-comment', id='_register')
register_comment.comment = 'new draft
'
wf.store()
formdef.workflow_id = wf.id
formdef.store()
formdef2.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.data = {'1': 'XXX'}
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url())
resp = resp.form.submit('button_resubmit')
resp = resp.follow()
assert 'new draft' in resp.text
assert formdef2.data_class().select()[0].status == 'draft'
assert formdef2.data_class().select()[0].data.get('1') is None
assert formdef2.data_class().select()[0].data.get('2') == 'XXX'
resp = resp.click('new draft')
resp = resp.follow()
assert resp.forms[1]['f2'].value == 'XXX'
# anonymous
formdef2.data_class().wipe()
app = get_app(pub)
resp = app.get('/form-title/')
resp.form['f1'] = 'foo'
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
resp = resp.form.submit('button_resubmit')
resp = resp.follow()
assert 'new draft' in resp.text
assert formdef2.data_class().select()[0].status == 'draft'
assert formdef2.data_class().select()[0].data.get('1') is None
assert formdef2.data_class().select()[0].data.get('2') == 'foo'
resp = resp.click('new draft')
resp = resp.follow()
assert resp.forms[1]['f2'].value == 'foo'
def test_form_custom_select_template(pub):
formdef = create_formdef()
formdef.fields = [
fields.ItemField(
id='1', label='select', type='item', required=True, varname='foo', items=['Foo', 'Bar', 'Baz']
)
]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
assert 'TEST TEMPLATE' not in resp.text
formdef.fields[0].extra_css_class = 'template-test'
formdef.store()
resp = get_app(pub).get('/test/')
assert 'TEST TEMPLATE' in resp.text
# make sure request is available in context
assert '' in resp.text
assert '' in resp.text
# test for substitution variables being available
if not pub.site_options.has_section('variables'):
pub.site_options.add_section('variables')
pub.site_options.set('variables', 'example_url', 'http://remote.example.net/')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
resp = get_app(pub).get('/test/')
assert 'substitution variable: http://remote.example.net/' in resp.text
def test_form_status_appearance_keywords(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef = create_formdef()
formdef.fields = [
fields.ItemField(
id='1', label='select', type='item', required=True, varname='foo', items=['Foo', 'Bar', 'Baz']
)
]
formdef.store()
formdata = formdef.data_class()()
formdata.just_created()
formdata.store()
root = PublicFormStatusPage(formdef, formdata, register_workflow_subdirs=False)
template_names = root.get_formdef_template_variants(root.status_templates)
assert list(template_names) == root.status_templates
formdef.appearance_keywords = 'foobar plop'
formdef.store()
template_names = root.get_formdef_template_variants(root.status_templates)
assert list(template_names) == [
'wcs/front/appearance-foobar/formdata_status.html',
'wcs/front/appearance-plop/formdata_status.html',
'wcs/front/formdata_status.html',
'wcs/appearance-foobar/formdata_status.html',
'wcs/appearance-plop/formdata_status.html',
'wcs/formdata_status.html',
]
resp = get_app(pub).get('/test/')
assert 'class="quixote foobar plop"' in resp.text
def test_user_global_action(pub):
create_user(pub)
workflow = Workflow.get_default_workflow()
workflow.id = '2'
action = workflow.add_global_action('FOOBAR')
register_comment = action.add_action('register-comment')
register_comment.comment = 'HELLO WORLD GLOBAL ACTION'
jump = action.add_action('jump')
jump.status = 'finished'
trigger = action.triggers[0]
workflow.store()
formdef = FormDef()
formdef.name = 'test global action'
formdef.fields = []
formdef.workflow_id = workflow.id
formdef.workflow_roles = {}
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
resp = app.get(formdata.get_url())
assert 'button-action-1' not in resp.text
trigger.roles = ['_submitter']
workflow.store()
resp = app.get(formdata.get_url())
assert 'button-action-1' in resp.form.fields
resp = resp.form.submit('button-action-1')
resp = app.get(formdata.get_url())
assert 'HELLO WORLD GLOBAL ACTION' in resp.text
assert formdef.data_class().get(formdata.id).status == 'wf-finished'
def test_user_global_action_same_status_store(pub):
create_user(pub)
workflow = Workflow.get_default_workflow()
workflow.id = '2'
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
workflow.backoffice_fields_formdef.fields = [
fields.StringField(id='bo1', label='bo field 1', type='string'),
]
action = workflow.add_global_action('FOOBAR')
jump = action.add_action('jump')
jump.status = 'new'
trigger = action.triggers[0]
trigger.roles = ['_submitter']
new_status = workflow.possible_status[1]
setbo = new_status.add_action('set-backoffice-fields', prepend=True)
setbo.fields = [{'field_id': 'bo1', 'value': '123'}]
workflow.store()
formdef = FormDef()
formdef.name = 'test global action'
formdef.fields = []
formdef.workflow_id = workflow.id
formdef.workflow_roles = {}
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['bo1'] == '123'
# change global action
setbo.fields = [{'field_id': 'bo1', 'value': '321'}]
workflow.store()
resp = app.get(formdata.get_url())
assert 'button-action-1' in resp.form.fields
resp = resp.form.submit('button-action-1') # click global action
# check status actions are rerun
resp = app.get(formdata.get_url())
assert formdef.data_class().get(formdata.id).status == 'wf-new'
assert formdef.data_class().get(formdata.id).data['bo1'] == '321'
def test_anonymous_user_global_action(pub):
workflow = Workflow.get_default_workflow()
workflow.id = '2'
action = workflow.add_global_action('FOOBAR')
register_comment = action.add_action('register-comment')
register_comment.comment = 'HELLO WORLD GLOBAL ACTION'
jump = action.add_action('jump')
jump.status = 'finished'
trigger = action.triggers[0]
trigger.roles = ['_submitter']
workflow.store()
formdef = FormDef()
formdef.name = 'test global action'
formdef.fields = []
formdef.enable_tracking_codes = True
formdef.workflow_id = workflow.id
formdef.workflow_roles = {}
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
app.cookiejar.clear()
resp = app.get('/')
resp.forms[0]['code'] = formdata.tracking_code
resp = resp.forms[0].submit().follow().follow()
assert 'button-action-1' in resp.form.fields
resp = resp.form.submit('button-action-1')
resp = app.get(formdata.get_url())
assert 'HELLO WORLD GLOBAL ACTION' in resp.text
assert formdef.data_class().get(formdata.id).status == 'wf-finished'
def test_condition_on_action(pub, emails):
create_user(pub)
workflow = Workflow.get_default_workflow()
# change email subjects to differentiate them
workflow.possible_status[0].items[0].subject = 'New form ([name])'
workflow.possible_status[0].items[1].subject = 'New form2 ([name])'
workflow.id = '2'
workflow.store()
formdef = FormDef()
formdef.name = 'test condition on action'
formdef.fields = []
formdef.workflow_id = workflow.id
formdef.workflow_roles = {}
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert not emails.get('New form (test condition on action)') # no receiver
assert emails.get('New form2 (test condition on action)') # submitter
emails.empty()
workflow.possible_status[0].items[1].condition = {'type': 'python', 'value': 'False'}
workflow.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert not emails.get('New form2 (test condition on action)')
# check with a condition on field data
formdef.fields = [fields.StringField(id='0', label='string', varname='foobar')]
formdef.store()
workflow.possible_status[0].items[1].condition = {'type': 'django', 'value': 'form_var_foobar'}
workflow.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp.form['f0'] = ''
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert not emails.get('New form2 (test condition on action)')
# check with condition evaluating positively
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp.form['f0'] = 'toto'
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert emails.get('New form2 (test condition on action)')
def test_user_global_action_along_form(pub):
# check it's possible to click on a global action button even if there's a
# form with required fields.
create_user(pub)
workflow = Workflow.get_default_workflow()
workflow.id = '2'
action = workflow.add_global_action('FOOBAR')
register_comment = action.add_action('register-comment')
register_comment.comment = 'HELLO WORLD GLOBAL ACTION'
jump = action.add_action('jump')
jump.status = 'finished'
trigger = action.triggers[0]
trigger.roles = ['_submitter']
status = workflow.get_status('new')
display_form = status.add_action('form', id='_x')
display_form.id = '_x'
display_form.by = ['_submitter']
display_form.varname = 'xxx'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields.append(fields.StringField(id='1', label='blah', type='string', required=True))
workflow.store()
formdef = FormDef()
formdef.name = 'test global action'
formdef.fields = []
formdef.workflow_id = workflow.id
formdef.workflow_roles = {}
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
resp = app.get(formdata.get_url())
assert resp.form['fxxx_1'].attrs['aria-required'] == 'true'
assert 'button-action-1' in resp.form.fields
resp = resp.form.submit('button-action-1')
resp = app.get(formdata.get_url())
assert 'HELLO WORLD GLOBAL ACTION' in resp.text
assert formdef.data_class().get(formdata.id).status == 'wf-finished'
def test_email_actions(pub, emails):
create_user(pub)
workflow = Workflow.get_default_workflow()
workflow.id = '2'
# change email subjects to differentiate them
workflow.possible_status[0].items[0].subject = 'New form ([name])'
workflow.possible_status[0].items[1].subject = 'New form2 ([name])'
workflow.possible_status[0].items[
1
].body = 'Hello; {% action_button "do-accept" label="Accepté!" %} Adiós.'
workflow.possible_status[1].items[1].identifier = 'do-accept'
workflow.store()
formdef = FormDef()
formdef.name = 'test email action'
formdef.fields = []
formdef.workflow_id = workflow.id
formdef.workflow_roles = {'_receiver': 1}
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
email_data = emails.get('New form2 (test email action)')
action_url = re.findall(r'http.* ', email_data['payload'])[0].strip()
assert '/actions/' in action_url
if docutils:
assert len(email_data['payloads']) == 2
assert action_url in force_text(email_data['payloads'][1])
app = get_app(pub)
resp = app.get(action_url)
assert 'Accepté!' in resp.text
resp = resp.form.submit()
assert 'The action has been confirmed.' in resp.text
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.status == 'wf-accepted'
assert formdata.evolution[-1].parts[-1].event == 'email-button'
# action token has been used, it will now return a custom 404
resp = app.get(action_url, status=404)
assert 'This action link has already been used or has expired.' in resp.text
# check against independently changed status, it should also return a
# custom 404.
emails.empty()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
email_data = emails.get('New form2 (test email action)')
action_url = re.findall(r'http.* ', email_data['payload'])[0].strip()
formdata = formdef.data_class().select()[0]
formdata.jump_status('rejected')
app = get_app(pub)
resp = app.get(action_url, status=404)
assert 'This action link has already been used or has expired.' in resp.text
# check action link referencing a deleted formdata
emails.empty()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
email_data = emails.get('New form2 (test email action)')
action_url = re.findall(r'http.* ', email_data['payload'])[0].strip()
formdata = formdef.data_class().select()[0]
formdata.remove_self()
app = get_app(pub)
resp = app.get(action_url, status=404)
assert 'This action link is no longer valid' in resp.text
# two buttons on the same line, two urls
workflow.possible_status[0].items[
1
].body = '{% action_button "ok" label="OK" %}{% action_button "ko" label="KO" %} '
workflow.store()
emails.empty()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
email_data = emails.get('New form2 (test email action)')
assert len(re.findall(r'http.*? ', email_data['payload'])) == 2
# custom messages
workflow.possible_status[0].items[
1
].body = 'Hello {% action_button "do-accept" label="ok" message="FOOmessageBAR" done_message="FOOdoneBAR" %} bye.'
workflow.store()
emails.empty()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
email_data = emails.get('New form2 (test email action)')
action_url = re.findall(r'http.* ', email_data['payload'])[0].strip()
app = get_app(pub)
resp = app.get(action_url)
assert 'FOOmessageBAR' in resp.text
resp = resp.form.submit()
assert 'FOOdoneBAR' in resp.text
def test_card_email_actions(pub, emails):
create_user(pub)
workflow = Workflow.get_default_workflow()
workflow.id = '2'
workflow.possible_status[0].items[0].subject = None # disable first mail
workflow.possible_status[0].items[1].subject = 'New card'
workflow.possible_status[0].items[1].body = 'XXX {% action_button "do-accept" label="Accepté!" %}'
workflow.possible_status[0].items[1].to = ['test@example.net'] # force recipient
workflow.possible_status[1].items[1].identifier = 'do-accept'
workflow.store()
carddef = CardDef()
carddef.name = 'test email action'
carddef.fields = []
carddef.workflow_id = workflow.id
carddef.workflow_roles = {'_receiver': 1}
carddef.store()
carddef.data_class().wipe()
carddata = carddef.data_class()()
carddata.just_created()
carddata.store()
carddata.perform_workflow()
carddata.store()
email_data = emails.get('New card')
action_url = re.findall(r'\shttp.*\s', email_data['payload'])[0].strip()
assert '/actions/' in action_url
if docutils:
assert len(email_data['payloads']) == 2
assert action_url in force_text(email_data['payloads'][1])
app = get_app(pub)
resp = app.get(action_url)
assert 'Accepté!' in resp.text
resp = resp.form.submit()
assert 'The action has been confirmed.' in resp.text
assert carddef.data_class().count() == 1
carddata = carddef.data_class().select()[0]
assert carddata.status == 'wf-accepted'
def test_manager_public_access(pub):
user, manager = create_user_and_admin(pub)
pub.role_class.wipe()
role = pub.role_class(name='xxx')
role.store()
manager.is_admin = False
manager.roles = [role.id]
manager.store()
assert manager.can_go_in_backoffice()
formdef = create_formdef()
formdef.workflow_roles = {'_receiver': role.id}
formdef.store()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.data = {}
formdata.just_created()
formdata.store()
# user access to own formdata
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url())
assert 'The form has been recorded' in resp.text
# agent access to formdata
app = login(get_app(pub), username='admin', password='admin')
resp = app.get(formdata.get_url())
assert resp.location == formdata.get_url(backoffice=True)
resp = resp.follow()
assert 'The form has been recorded' in resp.text
# agent access to an unauthorized formdata
formdef.workflow_roles = {'_receiver': None}
formdef.store()
resp = app.get(formdata.get_url(), status=403)
# agent access via a tracking code (stays in frontoffice)
formdef.workflow_roles = {'_receiver': role.id}
formdef.enable_tracking_codes = True
formdef.store()
code = pub.tracking_code_class()
code.formdata = formdata
code.store()
resp = app.get('/code/%s/load' % code.id)
resp = resp.follow() # -> /test/1/
assert 'backoffice' not in resp.request.path
assert 'The form has been recorded' in resp.text
# authorized access but not backoffice access
app = login(get_app(pub), username='admin', password='admin') # reset session
resp = app.get(formdata.get_url())
assert resp.location == formdata.get_url(backoffice=True) # check tracking code is no longer effective
role.allows_backoffice_access = False
role.store()
resp = app.get(formdata.get_url())
assert 'The form has been recorded' in resp.text
# agent access to own formdata (stays in frontoffice)
formdata = formdef.data_class()()
formdata.user_id = manager.id
formdata.data = {}
formdata.just_created()
formdata.store()
resp = app.get(formdata.get_url())
assert 'The form has been recorded' in resp.text
def test_form_and_category_same_slug(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'foobar'
formdef.fields = []
formdef.store()
# check we get to the form, not the category
resp = get_app(pub).get('/foobar/')
assert resp.form
def test_field_condition(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(
type='string',
id='1',
label='Bar',
size='40',
required=True,
condition={'type': 'django', 'value': '1'},
),
fields.StringField(
type='string',
id='2',
label='Foo',
size='40',
required=True,
condition={'type': 'django', 'value': '0'},
),
]
formdef.store()
resp = get_app(pub).get('/foo/')
assert 'f1' in resp.form.fields
assert 'f2' not in resp.form.fields
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 'Bar ' in resp.text
assert 'Foo ' not in resp.text
def test_field_unicode_condition(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.PageField(id='3', label='1st page', type='page'),
fields.StringField(
type='string',
id='4',
label='Baz',
size='40',
required=True,
varname='baz',
condition={'type': 'django', 'value': 'form_var_bar == "éléphant"'},
),
]
formdef.store()
resp = get_app(pub).get('/foo/')
resp.form['f1'] = 'hello'
resp = resp.form.submit('submit')
assert 'f4' not in resp.form.fields
resp = get_app(pub).get('/foo/')
resp.form['f1'] = 'éléphant'
resp = resp.form.submit('submit')
assert 'f4' in resp.form.fields
def test_field_unicode_condition_contains_in_list(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.PageField(id='3', label='1st page', type='page'),
fields.StringField(
type='string',
id='4',
label='Baz',
size='40',
required=True,
varname='baz',
condition={'type': 'django', 'value': 'form_var_bar in "éléphant"|split'},
),
]
formdef.store()
resp = get_app(pub).get('/foo/')
resp.form['f1'] = 'hello'
resp = resp.form.submit('submit')
assert 'f4' not in resp.form.fields
resp = get_app(pub).get('/foo/')
resp.form['f1'] = 'éléphant'
resp = resp.form.submit('submit')
assert 'f4' in resp.form.fields
def test_field_unicode_condition_contains_in_string(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.PageField(id='3', label='1st page', type='page'),
fields.StringField(
type='string',
id='4',
label='Baz',
size='40',
required=True,
varname='baz',
condition={'type': 'django', 'value': '"éléphant" in form_var_bar'},
),
]
formdef.store()
resp = get_app(pub).get('/foo/')
resp.form['f1'] = 'hello'
resp = resp.form.submit('submit')
assert 'f4' not in resp.form.fields
resp = get_app(pub).get('/foo/')
resp.form['f1'] = 'éléphant'
resp = resp.form.submit('submit')
assert 'f4' in resp.form.fields
def test_field_unicode_condition_in_array(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.ItemsField(
id='1',
label='items',
type='items',
required=True,
varname='foo',
items=['Pomme', 'Poire', 'Pêche', 'Abricot'],
),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(
type='string',
id='3',
label='Baz',
size='40',
required=True,
varname='baz',
condition={'type': 'django', 'value': '"Pêche" in form_var_foo'},
),
fields.CommentField(type='comment', id='4', label='{{form_var_foo}}'),
fields.CommentField(
type='comment', id='5', label='{% if "Pêche" in form_var_foo %}CHECK OK{% endif %}'
),
]
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
display1 = st1.add_action('displaymsg')
display1.message = 'Message {% if "Pêche" in form_var_foo %}CHECK OK{% endif %}'
display1.to = []
workflow.store()
formdef.workflow = workflow
formdef.store()
resp = get_app(pub).get('/foo/')
resp.form['f1$element1'].checked = True
resp = resp.form.submit('submit')
assert 'f3' not in resp.form.fields
resp = get_app(pub).get('/foo/')
resp.form['f1$element1'].checked = True
resp.form['f1$element2'].checked = True
resp = resp.form.submit('submit')
assert 'f3' in resp.form.fields # check it's ok in field condition
resp.form['f3'] = 'hop'
assert '>Poire, Pêche<' in resp.text # check it's displayed correctly
assert 'CHECK OK' in resp.text # check it's ok in template condition
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp.text
resp = resp.form.submit('submit').follow()
assert 'Message CHECK OK
' in resp.text # check it's ok in workflow template
def test_form_edit_and_backoffice_field_change(pub):
create_user(pub)
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='foo'),
fields.PageField(id='2', label='2nd page', type='page'),
]
formdef.store()
formdef.data_class().wipe()
Workflow.wipe()
workflow = Workflow(name='test')
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
workflow.backoffice_fields_formdef.fields = [
fields.StringField(id='bo1', label='bo field 1', type='string', varname='plop'),
]
st1 = workflow.add_status('Status1', 'st1')
setbo = st1.add_action('set-backoffice-fields')
setbo.fields = [{'field_id': 'bo1', 'value': '=form_var_foo'}]
setbo2 = st1.add_action('set-backoffice-fields')
setbo2.fields = [{'field_id': 'bo1', 'value': '="foo" + form_var_plop'}]
jump = st1.add_action('jump')
jump.status = 'st2'
st2 = workflow.add_status('Status2', 'st2')
editable = st2.add_action('editable', id='_editable')
editable.by = ['_submitter']
editable.status = st1.id
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f1'] = 'bar'
resp = resp.form.submit('submit') # -> page 2
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit').follow() # -> submitted
assert 'The form has been recorded' in resp.text
data_id = formdef.data_class().select()[0].id
assert formdef.data_class().get(data_id).data['bo1'] == 'foobar'
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/%s/' % data_id)
assert 'button_editable-button' in resp.text
resp = resp.form.submit('button_editable')
resp = resp.follow()
assert resp.form['f1'].value == 'bar'
resp.form['f1'].value = 'baz'
resp = resp.form.submit('submit') # -> page 2
resp = resp.form.submit('submit').follow() # -> saved
assert formdef.data_class().get(data_id).data['bo1'] == 'foobaz'
def test_backoffice_fields_just_after_conditional_form_submit(pub):
"""
simulate selection of a structured list via condition on form,
followed by an evaluation on workflow in order to get structured value
from the selected list.
ie: test unfeed on ConditionVars
"""
Workflow.wipe()
workflow = Workflow(name='test')
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
workflow.backoffice_fields_formdef.fields = [
fields.StringField(id='bo1', label='first text', type='string', varname='both_text'),
fields.StringField(id='bo2', label='first more', type='string', varname='both_more'),
]
st1 = workflow.add_status('Status1', 'st1')
setbo = st1.add_action('set-backoffice-fields')
setbo.fields = [
{'field_id': 'bo1', 'value': '{{ form_var_listA }} vs {{ form_var_listB }}'},
{'field_id': 'bo2', 'value': '{{ form_var_listA_more }} vs {{ form_var_listB_more }}'},
]
workflow.store()
items_A = [{'id': '1', 'text': 'A1', 'more': 'moreA1'}]
items_B = [{'id': '1', 'text': 'B1', 'more': 'moreB1'}, {'id': '2', 'text': 'B2', 'more': 'moreB2'}]
formdef = create_formdef()
formdef.fields = [
fields.ItemField(id='1', varname='choice', items=['A', 'B'], type='item', label='list to choice'),
fields.ItemField(
id='2',
varname='listA',
type='item',
label='list A',
data_source={'type': 'formula', 'value': str(items_A)},
condition={'type': 'python', 'value': 'form_var_choice_raw == "A"'},
),
fields.ItemField(
id='3',
varname='listB',
type='item',
label='list B',
data_source={'type': 'formula', 'value': str(items_B)},
condition={'type': 'python', 'value': 'form_var_choice_raw == "B"'},
),
]
formdef.confirmation = False
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
create_user_and_admin(pub)
resp = get_app(pub).get('/test/')
resp.form['f1'].value = 'B'
resp.form['f2'].value = '1'
resp.form['f3'].value = '2'
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'B'
assert formdata.data.get('2') is None
assert formdata.data['3'] == '2'
assert formdata.data['bo1'] == 'None vs B2'
assert formdata.data['bo2'] == ' vs moreB2'
def test_backoffice_fields_just_after_conditional_form_edit_action(pub):
"""
test unfeed on ConditionVars within edit context
"""
Workflow.wipe()
workflow = Workflow(name='test')
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
workflow.backoffice_fields_formdef.fields = [
fields.StringField(id='bo1', label='both text', type='string', varname='both_text'),
fields.StringField(id='bo2', label='both more', type='string', varname='both_more'),
]
st1 = workflow.add_status('Status1', 'st1')
setbo = st1.add_action('set-backoffice-fields')
setbo.fields = [
{'field_id': 'bo1', 'value': '{{ form_var_listA }} vs {{ form_var_listB }}'},
{'field_id': 'bo2', 'value': '{{ form_var_listA_more }} vs {{ form_var_listB_more }}'},
]
editable = st1.add_action('editable', id='_editable')
editable.by = ['_submitter']
editable.status = st1.id
workflow.store()
items_A = [{'id': '1', 'text': 'A1', 'more': 'moreA1'}]
items_B = [{'id': '1', 'text': 'B1', 'more': 'moreB1'}, {'id': '2', 'text': 'B2', 'more': 'moreB2'}]
formdef = create_formdef()
formdef.fields = [
fields.ItemField(id='1', varname='choice', items=['A', 'B'], type='item', label='list to choice'),
fields.ItemField(
id='2',
varname='listA',
type='item',
label='list A',
data_source={'type': 'formula', 'value': str(items_A)},
condition={'type': 'python', 'value': 'form_var_choice_raw == "A"'},
),
fields.ItemField(
id='3',
varname='listB',
type='item',
label='list B',
data_source={'type': 'formula', 'value': str(items_B)},
condition={'type': 'python', 'value': 'form_var_choice_raw == "B"'},
),
]
formdef.confirmation = False
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
create_user(pub)
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp.form['f1'].value = 'B'
resp.form['f2'].value = '1'
resp.form['f3'].value = '2'
resp = resp.form.submit('submit').follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'B'
assert formdata.data.get('2') is None
assert formdata.data['3'] == '2'
# check unfeed on FormPage::submitted()
assert formdata.data['bo1'] == 'None vs B2'
assert formdata.data['bo2'] == ' vs moreB2'
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/%s/' % formdata.id)
assert 'button_editable-button' in resp.text
resp = resp.form.submit('button_editable').follow()
assert resp.form['f1'].value == 'B'
resp.form['f1'].value = 'A'
resp = resp.form.submit('submit').follow() # -> saved
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == 'A'
assert formdata.data['2'] == '1'
assert formdata.data.get('3') is None
# check unfeed on FormPage::submitted_existing()
assert formdata.data['bo1'] == 'A1 vs None'
assert formdata.data['bo2'] == 'moreA1 vs '
def test_backoffice_fields_set_from_live(pub):
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()
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()
Workflow.wipe()
workflow = Workflow(name='test')
workflow.backoffice_fields_formdef = WorkflowBackofficeFieldsFormDef(workflow)
workflow.backoffice_fields_formdef.fields = [
fields.StringField(id='bo1', label='first text', type='string', varname='both_text'),
fields.StringField(id='bo2', label='first more', type='string', varname='both_more'),
]
st1 = workflow.add_status('Status1', 'st1')
setbo = st1.add_action('set-backoffice-fields')
setbo.fields = [
{'field_id': 'bo1', 'value': '{{ form_var.foo.attr }}'},
{'field_id': 'bo2', 'value': '{{ form_var.foo.live.var.attr }}'},
]
workflow.store()
ds = {'type': 'carddef:%s' % carddef.url_name}
formdef = create_formdef()
formdef.fields = [
fields.ItemField(
id='1', label='string', type='item', varname='foo', data_source=ds, display_disabled_items=True
)
]
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
create_user_and_admin(pub)
resp = get_app(pub).get('/test/')
resp.form['f1'].value = '2'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
resp = resp.follow()
assert 'The form has been recorded' in resp.text
assert formdef.data_class().count() == 1
formdata = formdef.data_class().select()[0]
assert formdata.data['1'] == '2'
assert formdata.data['bo1'] == 'attr1'
assert formdata.data['bo2'] == 'attr1'
def test_form_recall_draft(pub):
user = create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
assert 'You already started to fill this form.' not in resp.text
draft = formdef.data_class()()
draft.user_id = user.id
draft.status = 'draft'
draft.data = {}
draft.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
assert 'You already started to fill this form.' in resp.text
assert 'href="%s/"' % draft.id in resp.text
draft2 = formdef.data_class()()
draft2.user_id = user.id
draft2.status = 'draft'
draft2.data = {}
draft2.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
assert 'You already started to fill this form.' in resp.text
assert 'href="%s/"' % draft.id in resp.text
assert 'href="%s/"' % draft2.id in resp.text
def test_frontoffice_workflow_form_with_conditions(pub):
user = create_user(pub)
wf = Workflow.get_default_workflow()
wf.id = '2'
wf.store()
wf = Workflow.get(wf.id)
status = wf.get_status('new')
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.StringField(id='2', label='Test2', varname='str2', type='string', required=True),
]
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = [fields.StringField(id='0', label='string', varname='plop')]
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.status = 'wf-new'
formdata.data = {'0': 'plop'}
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
assert 'fblah_1' in resp.form.fields
assert 'fblah_2' in resp.form.fields
# check with static condition
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.StringField(
id='2',
label='Test2',
varname='str2',
type='string',
required=True,
condition={'type': 'django', 'value': '0'},
),
]
wf.store()
resp = login(get_app(pub), username='foo', password='foo').get(formdata.get_url(backoffice=False))
assert 'fblah_1' in resp.form.fields
assert 'fblah_2' not in resp.form.fields
# check condition based on formdata
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.StringField(
id='2',
label='Test2',
varname='str2',
type='string',
required=True,
condition={'type': 'django', 'value': 'form_var_plop'},
),
]
wf.store()
resp = login(get_app(pub), username='foo', password='foo').get(formdata.get_url(backoffice=False))
assert 'fblah_1' in resp.form.fields
assert 'fblah_2' in resp.form.fields
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.StringField(
id='2',
label='Test2',
varname='str2',
type='string',
required=True,
condition={'type': 'django', 'value': 'form_var_plop != "xxx"'},
),
]
wf.store()
resp = login(get_app(pub), username='foo', password='foo').get(formdata.get_url(backoffice=False))
assert 'fblah_1' in resp.form.fields
assert 'fblah_2' in resp.form.fields
for variable_name in (
'blah_var_str',
'form_workflow_data_blah_var_str',
'form_workflow_form_blah_var_str',
):
# check with live conditions
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.StringField(
id='2',
label='Test2',
varname='str2',
type='string',
required=True,
condition={'type': 'django', 'value': '%s == "xxx"' % variable_name},
),
]
wf.store()
resp = login(get_app(pub), username='foo', password='foo').get(formdata.get_url(backoffice=False))
assert 'fblah_1' in resp.form.fields
assert 'fblah_2' in resp.form.fields
assert resp.html.find('div', {'data-field-id': 'blah_1'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': 'blah_2'}).attrs.get('style') == 'display: none'
live_url = resp.html.find('form').attrs['data-live-url']
resp.form['fblah_1'] = ''
live_resp = app.post(live_url, params=resp.form.submit_fields())
assert live_resp.json['result']['blah_1']['visible']
assert not live_resp.json['result']['blah_2']['visible']
resp.form['fblah_1'] = 'xxx'
live_resp = app.post(live_url, params=resp.form.submit_fields())
assert live_resp.json['result']['blah_1']['visible']
assert live_resp.json['result']['blah_2']['visible']
# check submit doesn't work
resp = resp.form.submit('submit')
assert 'There were errors processing your form.' in resp.text
resp.form['fblah_1'] = 'xxx2'
live_resp = app.post(live_url, params=resp.form.submit_fields())
assert live_resp.json['result']['blah_1']['visible']
assert not live_resp.json['result']['blah_2']['visible']
# check submit does work when second field is hidden
resp = resp.form.submit('submit').follow()
assert formdef.data_class().get(formdata.id).workflow_data == {
'blah_var_str': 'xxx2',
'blah_var_str2': None,
}
def test_frontoffice_workflow_form_with_dynamic_comment(pub):
user = create_user(pub)
wf = Workflow.get_default_workflow()
wf.id = '2'
wf.store()
wf = Workflow.get(wf.id)
status = wf.get_status('new')
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.CommentField(id='2', label='value is {{blah_var_str}}', type='comment'),
]
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = [fields.StringField(id='0', label='string', varname='plop')]
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.status = 'wf-new'
formdata.data = {'0': 'plop'}
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
assert 'fblah_1' in resp.form.fields
live_url = resp.html.find('form').attrs['data-live-url']
resp.form['fblah_1'] = 'test'
live_resp = app.post(live_url, params=resp.form.submit_fields())
assert live_resp.json['result']['blah_2']['visible']
assert live_resp.json['result']['blah_2']['content'] == 'value is test
'
def test_frontoffice_workflow_form_with_dynamic_list(pub):
user = create_user(pub)
wf = Workflow('dynamic list in workflow')
status = wf.add_status('st1')
status2 = wf.add_status('st2')
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.ItemField(id='1', label='Test', varname='foo', type='item', items=['10', '20']),
fields.ItemField(
id='2',
label='Test2',
type='item',
varname='item2',
data_source={'type': 'json', 'value': 'http://example.org/{{form_workflow_form_blah_var_foo}}'},
),
]
jump1 = status.add_action('choice', id='_jump')
jump1.label = 'Jump'
jump1.by = ['_submitter']
jump1.status = status2.id
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
with responses.RequestsMock() as rsps:
rsps.get(
'http://example.org/10',
json={
'data': [
{'id': '1', 'text': 'hello', 'extra': 'foo'},
{'id': '2', 'text': 'world', 'extra': 'bar'},
]
},
)
rsps.get(
'http://example.org/20',
json={
'data': [
{'id': '11', 'text': 'hello2', 'extra': 'foo'},
{'id': '21', 'text': 'world2', 'extra': 'bar'},
]
},
)
live_url = resp.html.find('form').attrs['data-live-url']
live_resp = app.post(live_url + '?modified_field_id=init', params=resp.form.submit_fields())
assert [x['id'] for x in live_resp.json['result']['blah_2']['items']] == ['1', '2']
resp.form['fblah_1'] = '20'
live_resp = app.post(live_url + '?modified_field_id=blah_1', params=resp.form.submit_fields())
assert [x['id'] for x in live_resp.json['result']['blah_2']['items']] == ['11', '21']
resp.form['fblah_2'].force_value('11')
resp = resp.form.submit('submit').follow()
assert 'Technical error, please try again' not in resp.text
formdata.refresh_from_storage()
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
assert context['form_workflow_form_blah_var_item2'] == 'hello2'
assert context['form_workflow_data_blah_var_item2'] == 'hello2'
@pytest.mark.parametrize('button_position', ['before', 'after'])
def test_frontoffice_workflow_form_and_other_button(pub, button_position):
user = create_user(pub)
wf = Workflow('form and other button')
status = wf.add_status('st1')
status2 = wf.add_status('st2')
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='foo', type='string', required=True),
fields.StringField(id='2', label='Test2', varname='foo2', type='string', required=True),
]
jump1 = status.add_action('choice', id='_jump', prepend=bool(button_position == 'before'))
jump1.label = 'Jump'
jump1.by = ['_submitter']
jump1.status = status2.id
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1'] = 'a'
resp.form['fblah_2'] = 'b'
resp = resp.form.submit('submit')
formdata.refresh_from_storage()
pub.substitutions.reset()
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
assert 'form_workflow_form_blah_var_foo' in context
assert context['form_workflow_form_blah_var_foo'] == 'a'
assert context['form_workflow_data_blah_var_foo'] == 'a'
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1'] = 'a'
resp.form['fblah_2'] = 'b'
resp = resp.form.submit('button_jump')
formdata.refresh_from_storage()
pub.substitutions.reset()
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
# check workflow form data is saved if button comes after form action
if button_position == 'before':
assert 'form_workflow_form_blah_var_foo' not in context
else:
assert 'form_workflow_form_blah_var_foo' in context
# but legacy behaviout it leaks into workflow_data :/
assert context['form_workflow_data_blah_var_foo'] == 'a'
# check it also happens with invalid/partial form
jump1.ignore_form_errors = True
wf.store()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1'] = 'a'
resp.form['fblah_2'] = ''
resp = resp.form.submit('button_jump')
formdata.refresh_from_storage()
pub.substitutions.reset()
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
# check workflow form data is not saved (good)
assert 'form_workflow_form_blah_var_foo' not in context
# but legacy behaviout it leaks into workflow_data :/
assert context['form_workflow_data_blah_var_foo'] == 'a'
def test_frontoffice_workflow_form_with_impossible_condition(pub):
user = create_user(pub)
wf = Workflow.get_default_workflow()
wf.id = '2'
wf.store()
wf = Workflow.get(wf.id)
status = wf.get_status('new')
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.StringField(
id='1',
label='Test',
varname='str',
type='string',
condition={'type': 'django', 'value': '0 == 1'},
),
fields.StringField(
id='2',
label='Test2',
type='string',
condition={'type': 'django', 'value': 'blah_var_str == "toto"'},
),
]
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.status = 'wf-new'
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
assert 'fblah_1' not in resp.form.fields
assert 'fblah_2' not in resp.form.fields
def test_frontoffice_workflow_form_with_attachment_and_python_datasource(pub):
# case from https://dev.entrouvert.org/issues/60254
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'formula',
'value': repr([{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]),
}
data_source.store()
user = create_user(pub)
wf = Workflow.get_default_workflow()
wf.id = '2'
wf.store()
wf = Workflow.get(wf.id)
status = wf.get_status('new')
status.items = []
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.StringField(id='1', label='Test', varname='str', type='string', required=True),
fields.ItemField(
id='2', label='Test List', type='item', varname='bar', data_source={'type': data_source.slug}
),
]
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.status = 'wf-new'
formdata.data = {}
formdata.evolution[-1].parts = [
AttachmentEvolutionPart('hello.txt', fp=io.BytesIO(b'hello world'), varname='testfile')
]
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1'].value = 'blah'
resp.form['fblah_2'].value = '1'
resp = resp.form.submit('submit')
@responses.activate
def test_frontoffice_workflow_form_with_disappearing_option(pub, monkeypatch):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {'type': 'json', 'value': 'http://www.example.net/plop'}
data_source.id_parameter = 'id'
data_source.store()
responses.get(
'http://www.example.net/plop', json={'data': [{'id': '1', 'text': 'un'}, {'id': '2', 'text': 'deux'}]}
)
user = create_user(pub)
wf = Workflow(name='select')
st1 = wf.add_status('st1')
st2 = wf.add_status('st2')
display_form = st1.add_action('form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.ItemField(id='1', label='Test', varname='foo', type='item', data_source={'type': 'foobar'}),
]
jump = st1.add_action('jumponsubmit')
jump.status = st2.id
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.data = {}
formdata.just_created()
formdata.store()
# normal case, status changes and data is recorded
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1'] = '1'
resp = resp.form.submit('submit').follow()
formdata.refresh_from_storage()
assert formdata.status == 'wf-%s' % st2.id
assert formdata.workflow_data['blah_var_foo_raw'] == '1'
assert formdata.workflow_data['blah_var_foo'] == 'un'
# simulate an option disappearing during submit
monkeypatch.setattr(
NamedDataSource,
'get_value_by_id',
lambda *args: None,
)
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.data = {}
formdata.just_created()
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1'] = '1'
resp = resp.form.submit('submit')
assert 'Technical error, please try again' in resp.text
formdata.refresh_from_storage()
assert formdata.status == 'wf-%s' % st1.id
assert not formdata.workflow_data
def test_choice_button_ignore_form_errors(pub):
create_user(pub)
formdef = create_formdef()
formdef.roles = [logged_users_role().id]
formdef.store()
wf = Workflow(name='status')
st1 = wf.add_status('Status1', 'st1')
st2 = wf.add_status('Status2', 'st2')
commentable = st1.add_action('commentable', id='_commentable')
commentable.by = [logged_users_role().id]
commentable.required = True
choice = st1.add_action('choice', id='_x1')
choice.label = 'Submit'
choice.by = [logged_users_role().id]
choice.status = st2.id
choice2 = st1.add_action('choice', id='_x2')
choice2.label = 'Submit no check'
choice2.by = [logged_users_role().id]
choice2.status = st2.id
choice2.ignore_form_errors = True
wf.store()
formdef.workflow = wf
formdef.store()
# no comment
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
resp = resp.form.submit('button_x1')
assert 'There were errors processing your form.' in resp.text
# comment
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
resp.form['comment'] = 'plop'
resp = resp.form.submit('button_x1').follow()
assert resp.pyquery('.comment').text() == 'plop'
assert 'Status2' in resp.text
# no comment but no check
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
resp = resp.form.submit('button_x2').follow()
assert 'Status2' in resp.text
def test_form_comment_is_hidden_attribute(pub):
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string 1', varname='choice1'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.StringField(
id='3',
label='string 2',
varname='choice2',
condition={'type': 'django', 'value': 'form_var_choice1 == "1"'},
),
fields.CommentField(
id='5',
label='this should not be displayed',
type='comment',
condition={'type': 'django', 'value': 'False and form_var_choice2 == "???"'},
),
]
formdef.store()
resp = get_app(pub).get('/test/')
formdef.data_class().wipe()
resp.forms[0]['f1'] = '1'
resp = resp.forms[0].submit('submit')
comment = re.compile('.*comment-field.*"')
assert resp.html.find('div', {'data-field-id': '5'})
assert 'style="display: none"' in comment.search(resp.forms[0].text).group(0)
resp = resp.forms[0].submit('previous')
resp.forms[0]['f1'] = '2'
resp = resp.forms[0].submit('submit')
assert not resp.html.find('div', {'data-field-id': '5'})
@pytest.fixture
def create_formdata(pub):
FormDef.wipe()
data = [
{'id': '1', 'text': 'un', 'more': 'foo'},
{'id': '2', 'text': 'deux', 'more': 'bar'},
]
ds = {
'type': 'formula',
'value': repr(data),
}
source_formdef = FormDef()
source_formdef.name = 'source form'
source_formdef.fields = [
fields.StringField(id='0', label='string', varname='toto_string'),
fields.FileField(id='1', label='file', type='file', varname='toto_file'),
fields.ItemField(
id='2', label='item', required=False, data_source=ds, varname='toto_item', hint='hint'
),
]
source_formdef.store()
target_formdef = FormDef()
target_formdef.name = 'target form'
target_formdef.enable_tracking_codes = True
target_formdef.fields = [
fields.StringField(id='0', label='string', varname='foo_string'),
fields.FileField(id='1', label='file', type='file', varname='foo_file'),
fields.ItemField(id='2', label='item', data_source=ds, varname='foo_item'),
]
target_formdef.store()
wf = Workflow(name='create-formdata')
st1 = wf.add_status('New')
st2 = wf.add_status('Resubmit')
jump = st1.add_action('choice', id='_resubmit')
jump.label = 'Resubmit'
jump.by = ['_submitter']
jump.status = st2.id
create_formdata = st2.add_action('create_formdata', id='_create_formdata')
create_formdata.varname = 'resubmitted'
create_formdata.draft = True
create_formdata.formdef_slug = target_formdef.url_name
create_formdata.mappings = [
Mapping(field_id='0', expression='=form_var_toto_string'),
Mapping(field_id='1', expression='=form_var_toto_file_raw'),
Mapping(field_id='2', expression='=form_var_toto_item_raw'),
]
redirect = st2.add_action('redirect_to_url', id='_redirect')
redirect.url = '{{ form_links_resubmitted.form_url }}'
display = st2.add_action('displaymsg', id='_display')
display.message = '''{% if form_links_resubmitted %}
Linked status: {{ form_links_resubmitted.form_status }}
Target formdata field: {{ form_links_resubmitted.form_var_foo_string }}
{% endif %}
'''
display.to = []
wf.store()
source_formdef.workflow_id = wf.id
source_formdef.store()
return locals()
def test_create_formdata_anonymous_draft(create_formdata):
create_formdata['source_formdef'].data_class().wipe()
create_formdata['target_formdef'].data_class().wipe()
app = get_app(create_formdata['pub'])
resp = app.get('/source-form/')
resp.form['f0'] = 'zob'
resp.form['f1$file'] = Upload('test.txt', b'foobar', 'text/plain')
resp.form['f2'] = '2'
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
assert create_formdata['target_formdef'].data_class().count() == 0
resp = resp.form.submit('button_resubmit')
assert create_formdata['target_formdef'].data_class().count() == 1
target_formdata = create_formdata['target_formdef'].data_class().select()[0]
assert target_formdata.data.get('0') == 'zob'
assert target_formdata.data.get('1').get_content() == b'foobar'
assert target_formdata.status == 'draft'
assert target_formdata.submission_context == {
'orig_object_type': 'formdef',
'orig_formdata_id': str(create_formdata['source_formdef'].data_class().select()[0].id),
'orig_formdef_id': str(create_formdata['source_formdef'].id),
}
resp = resp.follow()
resp = resp.follow()
assert 'zob' in resp
assert resp.click('test.txt').text == 'foobar'
resp = resp.forms[1].submit('submit') # -> validation
resp = resp.forms[1].submit('submit') # -> submission
assert create_formdata['target_formdef'].data_class().count() == 1
target_formdata = create_formdata['target_formdef'].data_class().select()[0]
assert target_formdata.data.get('0') == 'zob'
assert target_formdata.data.get('1').get_content() == b'foobar'
assert target_formdata.data.get('1').get_content() == b'foobar'
assert target_formdata.data.get('2') == '2'
assert target_formdata.data.get('2_display') == 'deux'
assert target_formdata.data.get('2_structured') == {'text': 'deux', 'id': '2', 'more': 'bar'}
assert target_formdata.status == 'wf-new'
source_formdata = create_formdata['source_formdef'].data_class().select()[0]
resp = app.get(source_formdata.get_url())
pq = resp.pyquery.remove_namespaces()
assert pq('.linked .status').text() == 'New'
assert pq('.linked .foo_string').text() == 'zob'
def test_create_formdata_anonymous_submitted(create_formdata):
create_formdata['source_formdef'].data_class().wipe()
create_formdata['target_formdef'].data_class().wipe()
# submit directly
create_formdata['wf'].get_status('2').items[0].draft = False
create_formdata['wf'].store()
app = get_app(create_formdata['pub'])
resp = app.get('/source-form/')
resp.form['f0'] = 'zob'
resp.form['f1$file'] = Upload('test.txt', b'foobar', 'text/plain')
resp.form['f2'] = '2'
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
assert create_formdata['target_formdef'].data_class().count() == 0
resp = resp.form.submit('button_resubmit')
assert create_formdata['target_formdef'].data_class().count() == 1
target_formdata = create_formdata['target_formdef'].data_class().select()[0]
assert target_formdata.data.get('0') == 'zob'
assert target_formdata.data.get('1').get_content() == b'foobar'
assert target_formdata.status == 'wf-new'
assert target_formdata.submission_context == {
'orig_object_type': 'formdef',
'orig_formdata_id': str(create_formdata['source_formdef'].data_class().select()[0].id),
'orig_formdef_id': str(create_formdata['source_formdef'].id),
}
resp = resp.follow()
assert 'New' in resp
assert 'zob' in resp
target_formdata = create_formdata['target_formdef'].data_class().select()[0]
assert target_formdata.data.get('1').get_content() == b'foobar'
assert target_formdata.data.get('1').get_content() == b'foobar'
assert target_formdata.status == 'wf-new'
source_formdata = create_formdata['source_formdef'].data_class().select()[0]
resp = app.get(source_formdata.get_url())
pq = resp.pyquery.remove_namespaces()
assert pq('.linked .status').text() == 'New'
assert pq('.linked .foo_string').text() == 'zob'
def test_create_formdata_empty_item_ds_with_id_parameter(pub, create_formdata):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'json',
'value': 'http://remote.example.net/json',
}
data_source.id_parameter = 'id'
data_source.store()
create_formdata['source_formdef'].data_class().wipe()
create_formdata['target_formdef'].data_class().wipe()
create_formdata['source_formdef'].fields[2].data_source = {'type': 'foobar'}
create_formdata['source_formdef'].store()
create_formdata['target_formdef'].fields[2].data_source = {'type': 'foobar'}
create_formdata['target_formdef'].store()
with responses.RequestsMock() as rsps:
data = {'data': create_formdata['data']}
rsps.get('http://remote.example.net/json', json=data)
app = get_app(create_formdata['pub'])
resp = app.get('/source-form/')
resp.form['f0'] = 'zob'
resp.form['f1$file'] = Upload('test.txt', b'foobar', 'text/plain')
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
assert create_formdata['target_formdef'].data_class().count() == 0
assert pub.loggederror_class.count() == 0
resp = resp.form.submit('button_resubmit')
assert pub.loggederror_class.count() == 0
def test_create_formdata_locked_prefill_parent(create_formdata):
create_formdata['source_formdef'].data_class().wipe()
create_formdata['target_formdef'].data_class().wipe()
target_formdef = create_formdata['target_formdef']
target_formdef.fields[0].prefill = {
'type': 'string',
'value': '{{form_parent_form_var_toto_string}}',
'locked': True,
}
target_formdef.store()
app = get_app(create_formdata['pub'])
resp = app.get('/source-form/')
resp.form['f0'] = 'zob'
resp.form['f1$file'] = Upload('test.txt', b'foobar', 'text/plain')
resp.form['f2'] = '2'
resp = resp.form.submit('submit') # -> validation
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
assert create_formdata['target_formdef'].data_class().count() == 0
resp = resp.form.submit('button_resubmit')
assert create_formdata['target_formdef'].data_class().count() == 1
target_formdata = create_formdata['target_formdef'].data_class().select()[0]
assert target_formdata.data.get('0') == 'zob'
assert target_formdata.status == 'draft'
resp = resp.follow()
resp = resp.follow()
assert resp.forms[1]['f0'].value == 'zob'
assert resp.forms[1]['f0'].attrs['readonly']
# try altering readonly field
resp.forms[1]['f0'].value = 'xxx'
resp = resp.forms[1].submit('submit')
resp = resp.forms[1].submit('previous')
assert resp.forms[1]['f0'].value == 'zob'
resp = resp.forms[1].submit('submit')
resp = resp.forms[1].submit('submit')
assert create_formdata['target_formdef'].data_class().count() == 1
target_formdata = create_formdata['target_formdef'].data_class().select()[0]
assert target_formdata.data['0'] == 'zob'
def test_js_libraries(pub):
create_formdef()
resp = get_app(pub).get('/test/', status=200)
assert 'jquery.js' not in resp.text
assert 'jquery.min.js' in resp.text
assert 'qommon.forms.js' in resp.text
pub.cfg['debug'] = {'debug_mode': True}
pub.write_cfg()
resp = get_app(pub).get('/test/', status=200)
assert 'jquery.js' in resp.text
assert 'jquery.min.js' not in resp.text
assert 'qommon.forms.js' in resp.text
pub.cfg['branding'] = {'included_js_libraries': ['jquery.js']}
pub.write_cfg()
resp = get_app(pub).get('/test/', status=200)
assert 'jquery.js' not in resp.text
assert 'jquery.min.js' not in resp.text
assert 'qommon.forms.js' in resp.text
def test_after_submit_location(pub):
create_user(pub)
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
commentable = st1.add_action('commentable', id='_commentable')
commentable.by = [logged_users_role().id]
commentable.required = True
workflow.store()
formdef = create_formdef()
formdef.fields = []
formdef.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
resp = resp.forms[0].submit('submit') # form page
resp = resp.forms[0].submit('submit') # confirmation page
resp = resp.follow()
resp.form['comment'] = 'plop'
resp = resp.form.submit('submit')
assert resp.location == 'http://example.net/test/1/#action-zone'
resp = resp.follow()
display = st1.add_action('displaymsg')
display.message = 'message-to-all'
display.to = []
workflow.store()
resp.form['comment'] = 'plop'
resp = resp.form.submit('submit')
assert resp.location == 'http://example.net/test/1/#'
def test_form_honeypot(pub):
formdef = create_formdef()
formdef.fields = [fields.StringField(id='0', label='string', required=False)]
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp.forms[0]['f00'] = 'honey?'
resp = resp.forms[0].submit('submit')
assert 'Honey pot should be left untouched.' in resp
def test_structured_workflow_options(pub):
create_user_and_admin(pub)
workflow = Workflow(name='test')
workflow.variables_formdef = WorkflowVariablesFieldsFormDef(workflow=workflow)
data_source = {
'type': 'formula',
'value': repr([{'id': '1', 'text': 'un', 'more': 'foo'}, {'id': '2', 'text': 'deux', 'more': 'bar'}]),
}
workflow.variables_formdef.fields = [
fields.StringField(id='1', label='Test', type='string', varname='foo'),
fields.ItemField(id='2', label='Test List', type='item', varname='bar', data_source=data_source),
fields.ItemsField(id='3', label='Test Multi', type='items', varname='baz', data_source=data_source),
fields.DateField(id='4', label='Date', type='date', varname='date'),
]
st1 = workflow.add_status('Status1', 'st1')
comment = st1.add_action('register-comment', id='_comment')
comment.comment = 'Date option: {{ form_option_date }}'
workflow.store()
formdef = create_formdef()
formdef.fields = [
fields.ItemField(
id='1',
label='Test List',
type='item',
varname='bar',
data_source={'type': 'formula', 'value': 'form_option_baz_structured'},
),
]
formdef.store()
formdef.workflow_id = workflow.id
formdef.store()
formdef.data_class().wipe()
# configure workflow options
resp = login(get_app(pub), username='admin', password='admin').get('/backoffice/forms/%s/' % formdef.id)
resp = resp.click('Options')
resp.form['f1'].value = 'plop'
resp.form['f2'].value = '1'
resp.form['f3$element1'].checked = True
resp.form['f4'].value = '2020-04-18'
resp = resp.form.submit('submit')
formdef = FormDef.get(formdef.id)
assert formdef.workflow_options == {
'foo': 'plop',
'bar': '1',
'bar_display': 'un',
'bar_structured': {'id': '1', 'more': 'foo', 'text': 'un'},
'baz': ['1'],
'baz_display': 'un',
'baz_structured': [{'id': '1', 'more': 'foo', 'text': 'un'}],
'date': time.strptime('2020-04-18', '%Y-%m-%d'),
}
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/')
assert resp.form['f1'].options == [('1', False, 'un')]
resp = resp.form.submit('submit') # form page
resp = resp.form.submit('submit') # confirmation page
resp = resp.follow()
formdata = formdef.data_class().select()[0]
assert formdata.data == {
'1': '1',
'1_display': 'un',
'1_structured': {'id': '1', 'text': 'un', 'more': 'foo'},
}
assert '2020-04-18' in formdata.evolution[0].parts[0].content
def test_exclude_self_condition(pub):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.PageField(
id='1',
label='1st page',
type='page',
post_conditions=[
{
'condition': {
'type': 'django',
'value': 'form_objects|filter_by:"foo"|filter_value:form_var_foo|exclude_self|count == 0',
},
'error_message': 'You shall not pass.',
}
],
),
fields.StringField(id='1', label='string', type='string', varname='foo'),
]
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
editable = st1.add_action('editable', id='_editable')
editable.by = ['_submitter', '_receiver']
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
app = get_app(pub)
resp = app.get(formdef.get_url())
resp.form['f1'] = 'test'
resp = resp.form.submit('submit') # -> validation page
assert 'You shall not pass.' not in resp.text
resp = resp.form.submit('submit') # -> submit
resp = resp.follow()
# edit is ok
resp = resp.form.submit('button_editable').follow()
resp = resp.form.submit('submit') # -> validation page
assert 'You shall not pass.' not in resp
# 2nd submission
resp = app.get(formdef.get_url())
resp.form['f1'] = 'test'
resp = resp.form.submit('submit') # -> validation page
assert 'You shall not pass.' in resp.text
# submission with other value
resp = app.get(formdef.get_url())
resp.form['f1'] = 'other'
resp = resp.form.submit('submit') # -> validation page
assert 'You shall not pass.' not in resp.text
resp = resp.form.submit('submit') # -> submit
resp = resp.follow()
# edit is ok
resp = resp.form.submit('button_editable').follow()
resp.form['f1'] = 'test'
resp = resp.form.submit('submit') # -> validation page
assert 'You shall not pass.' in resp
def test_form_item_map_data_source(pub, http_requests):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'geojson',
'value': 'http://remote.example.net/geojson',
}
data_source.id_property = 'id'
data_source.label_template_property = '{{ text }}'
data_source.cache_duration = '5'
data_source.store()
formdef = create_formdef()
formdef.fields = [
fields.ItemField(id='1', label='map', display_mode='map'),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
assert resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-url'] == ''
assert resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-radio-name'] == 'f1$marker_id'
formdef.fields[0].data_source = {'type': 'foobar'}
formdef.store()
resp = app.get('/test/')
assert resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-url'] == '/api/geojson/foobar'
assert resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-radio-name'] == 'f1$marker_id'
app.get('/api/geojson/wrong-foobar', status=404)
resp_geojson = app.get('/api/geojson/foobar')
assert len(resp_geojson.json['features']) == 2
assert http_requests.count() == 1
assert http_requests.get_last('url') == 'http://remote.example.net/geojson'
resp_geojson = app.get('/api/geojson/foobar')
assert http_requests.count() == 1 # cache was used
assert len(resp_geojson.json['features']) == 2
# simulate qommon.map.js that will create radio inputs
resp.form.fields['f1$marker_id'] = [Radio(form=resp.form, tag='input', name='f1$marker_id', pos=5)]
resp.form.fields['f1$marker_id'][0].options.append(('1', False, None))
resp.form.fields['f1$marker_id'][0].options.append(('2', False, None))
resp.form.fields['f1$marker_id'][0].optionPositions.append(5)
resp.form.fields['f1$marker_id'][0].optionPositions.append(6)
resp.form.field_order.append(('f1$marker_id', resp.form.fields['f1$marker_id'][0]))
resp.form['f1$marker_id'].value = '1' # click on marker
resp.form['f1$latlng'] = '1;2' # set via js
resp = resp.form.submit('submit')
assert 'Check values then click submit.' in resp
# selected option is displayed as readonly:
assert resp.pyquery('input[type=text][value=foo][readonly]')
resp = resp.form.submit('submit')
resp = resp.follow()
assert 'The form has been recorded' in resp
assert 'foo
' in resp
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
formdata = formdef.data_class().get(data_id)
assert formdata.data['1_structured']['geometry']['coordinates'] == [1, 2]
def test_form_item_dynamic_map_data_source(pub, http_requests):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'geojson',
'value': 'http://remote.example.net/geojson?{{ form_var_test }}',
}
data_source.id_property = 'id'
data_source.label_template_property = '{{ text }}'
data_source.cache_duration = '5'
data_source.store()
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='0', label='1st page', type='page'),
fields.StringField(id='1', label='string', varname='test'),
fields.PageField(id='2', label='2nd page', type='page'),
fields.ItemField(id='3', label='map', display_mode='map', data_source={'type': 'foobar'}),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/test/')
resp.form['f1'] = 'plop'
resp = resp.form.submit('submit') # -> 2nd page
markers_url = resp.pyquery('div[data-markers-radio-name]')[0].attrib['data-markers-url']
assert markers_url.startswith('/api/geojson/')
resp_geojson = app.get(markers_url)
assert len(resp_geojson.json['features']) == 2
assert http_requests.count() == 1
assert http_requests.get_last('url') == 'http://remote.example.net/geojson?plop'
resp_geojson = app.get(markers_url)
assert http_requests.count() == 1 # cache was used
assert len(resp_geojson.json['features']) == 2
def test_form_item_timetable_data_source(pub, http_requests):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'json',
'value': 'http://remote.example.net/api/datetimes',
}
data_source.store()
formdef = create_formdef()
formdef.fields = [
fields.ItemField(id='1', label='datetime', display_mode='timetable', data_source={'type': 'foobar'}),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
with responses.RequestsMock() as rsps:
data = {
"data": [
{"id": "1", "datetime": "2021-01-12 10:00:00", "text": "event 1"},
{"id": "2", "datetime": "2021-01-13 10:20:00", "text": "event 2"},
{"id": "3", "datetime": "2021-01-14 10:40:00", "text": "event 3"},
]
}
rsps.get('http://remote.example.net/api/datetimes', json=data)
resp = app.get('/test/')
assert 'data-date="2021-01-12"' in resp and 'data-time="10:00"' in resp
assert 'data-date="2021-01-13"' in resp and 'data-time="10:20"' in resp
assert 'data-date="2021-01-14"' in resp and 'data-time="10:40"' in resp
resp.form['f1'] = '2' # would happen via javascript
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
data_id = formdef.data_class().select()[0].id
formdata = formdef.data_class().get(data_id)
assert formdata.data == {
'1': '2',
'1_display': 'event 2',
'1_structured': {'id': '2', 'datetime': '2021-01-13 10:20:00', 'text': 'event 2'},
}
def test_form_item_timetable_data_source_with_date_alignment(pub, http_requests):
NamedDataSource.wipe()
data_source = NamedDataSource(name='foobar')
data_source.data_source = {
'type': 'json',
'value': 'http://remote.example.net/api/datetimes',
}
data_source.store()
formdef = create_formdef()
formdef.fields = [
fields.PageField(id='1', label='page1', type='page'),
fields.DateField(id='2', label='date', type='date', varname='date'),
fields.PageField(id='3', label='page2', type='page'),
fields.ItemField(
id='4',
label='datetime',
display_mode='timetable',
data_source={'type': 'foobar'},
initial_date_alignment='{{ form_var_date }}',
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
with responses.RequestsMock() as rsps:
data = {
"data": [
{"id": "1", "datetime": "2021-01-12 10:00:00", "text": "event 1"},
{"id": "2", "datetime": "2021-01-13 10:20:00", "text": "event 2"},
{"id": "3", "datetime": "2021-01-14 10:40:00", "text": "event 3"},
]
}
rsps.get('http://remote.example.net/api/datetimes', json=data)
resp = app.get('/test/')
resp.form['f2'] = '2021-01-14'
resp = resp.form.submit('submit') # -> 2nd page
assert 'var ALIGN_DATE = "2021-01-14";' in resp
resp.form['f4'] = '2' # would happen via javascript
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
assert formdef.data_class().count() == 1
def test_file_prefill_on_edit(pub, http_requests):
user = create_user(pub)
formdef = create_formdef()
formdef.fields = [fields.FileField(id='0', label='file', type='file', varname='foo_file')]
formdef.store()
formdef.data_class().wipe()
workflow = Workflow(name='test')
st1 = workflow.add_status('New', 'st1')
st2 = workflow.add_status('CreateFormdata')
editable = st1.add_action('editable', id='_editable')
editable.by = ['_submitter', '_receiver']
jump = st1.add_action('choice', id='_resubmit')
jump.label = 'Resubmit'
jump.by = ['_submitter']
jump.status = st2.id
create_formdata = st2.add_action('create_formdata', id='_create_formdata')
create_formdata.formdef_slug = formdef.url_name
create_formdata.mappings = [
Mapping(field_id='0', expression='{{form_var_foo_file}}'),
]
workflow.store()
formdef.workflow_id = workflow.id
formdef.store()
upload = Upload('test.txt', b'foobar', 'text/plain')
resp = get_app(pub).get('/test/')
resp.forms[0]['f0$file'] = upload
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submit
assert resp.status_int == 302
resp = resp.follow()
assert 'The form has been recorded' in resp.text
resp = resp.click('test.txt')
assert resp.location.endswith('/test.txt')
resp = resp.follow()
assert resp.content_type == 'text/plain'
assert resp.text == 'foobar'
formdata = formdef.data_class().select()[0]
formdata.user_id = user.id
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get('/test/%s/' % formdata.id)
assert 'button_editable-button' in resp.text
# go to edition page
resp = resp.form.submit('button_editable').follow()
# file is "prefilled"
assert 'test.txt' in resp.text
# go back to form page and trigger formdata creation
resp = app.get('/test/%s/' % formdata.id)
resp = resp.form.submit('button_resubmit')
assert resp.status == '303 See Other'
resp = resp.follow()
assert formdef.data_class().count() == 2
new_formdata = formdef.data_class().select(lambda x: str(x.id) != str(formdata.id))[0]
assert new_formdata.data['0'].orig_filename == 'test.txt'
assert new_formdata.data['0'].get_content() == b'foobar'
resp = app.get('/test/%s/' % new_formdata.id)
assert 'button_editable-button' in resp.text
# go to edition page
resp = resp.form.submit('button_editable').follow()
# file is "prefilled"
assert 'test.txt' in resp.text
# and persist after being saved again
resp = resp.form.submit('submit').follow()
assert 'test.txt ' in resp.text
def test_workflow_form_structured_data(pub):
FormDef.wipe()
Workflow.wipe()
BlockDef.wipe()
user = create_user(pub)
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='123', required=True, label='Test', type='string', varname='test'),
]
block.store()
wf = Workflow(name='test')
status = wf.add_status('New', 'st1')
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.BlockField(id='1', label='test', type='block:foobar', varname='fooblock'),
]
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1$element0$f123'] = 'ABC'
resp = resp.form.submit('submit').follow()
resp.form['fblah_1$element0$f123'] = 'XYZ'
resp = resp.form.submit('submit').follow()
formdata.refresh_from_storage()
assert formdata.workflow_data == {
'blah_var_fooblock_raw': {'data': [{'123': 'XYZ'}], 'schema': {'123': 'string'}},
'blah_var_fooblock': 'foobar',
}
substvars = CompatibilityNamesDict()
substvars.update(formdata.get_substitution_variables())
keys = substvars.get_flat_keys()
for key in keys:
# noqa pylint: disable=unused-variable
var = substvars[key] # check it doesn't raise, ignore the value
assert substvars['form_workflow_form_blah_var_fooblock_var_test'] == 'XYZ'
assert substvars['form_workflow_form_blah_0_var_fooblock_var_test'] == 'ABC'
assert substvars['form_workflow_form_blah_1_var_fooblock_var_test'] == 'XYZ'
# disable dumping in workflow_data
pub.load_site_options()
if not pub.site_options.has_section('options'):
pub.site_options.add_section('options')
pub.site_options.set('options', 'disable-workflow-form-to-workflow-data', 'true')
with open(os.path.join(pub.app_dir, 'site-options.cfg'), 'w') as fd:
pub.site_options.write(fd)
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1$element0$f123'] = 'ABC'
resp = resp.form.submit('submit').follow()
resp.form['fblah_1$element0$f123'] = 'XYZ'
resp = resp.form.submit('submit').follow()
formdata.refresh_from_storage()
assert not formdata.workflow_data
def test_workflow_form_file_access(pub):
FormDef.wipe()
Workflow.wipe()
BlockDef.wipe()
user = create_user(pub)
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.FileField(id='123', required=True, label='Test', type='file', varname='test'),
]
block.store()
wf = Workflow(name='test')
status = wf.add_status('New', 'st1')
next_status = wf.add_status('Next', 'st2')
status.items = []
display_form = status.add_action('form', id='_display_form')
display_form.by = ['_submitter']
display_form.varname = 'blah'
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form)
display_form.formdef.fields = [
fields.BlockField(id='1', label='test', type='block:foobar', varname='fooblock', max_items=3),
fields.FileField(id='2', label='test2', type='file', varname='file'),
]
jump = status.add_action('jumponsubmit', id='_jump')
jump.status = next_status.id
register_comment = next_status.add_action('register-comment', id='_register')
register_comment.comment = '''
1st file in block
2nd file in block
again 1st file in block
file field
'''
wf.store()
formdef = create_formdef()
formdef.workflow_id = wf.id
formdef.fields = []
formdef.store()
formdef.data_class().wipe()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
resp.form['fblah_1$element0$f123$file'] = Upload('test1.txt', b'foobar1', 'text/plain')
resp = resp.form.submit('fblah_1$add_element')
resp.form['fblah_1$element1$f123$file'] = Upload('test2.txt', b'foobar2', 'text/plain')
resp.form['fblah_2$file'] = Upload('test3.txt', b'foobar3', 'text/plain')
resp = resp.form.submit('submit').follow()
assert app.get(resp.pyquery('#t1').attr.href).body == b'foobar1'
assert app.get(resp.pyquery('#t2').attr.href).body == b'foobar2'
assert app.get(resp.pyquery('#t3').attr.href).body == b'foobar1'
assert app.get(resp.pyquery('#t4').attr.href).body == b'foobar3'
app.get(resp.pyquery('#t4').attr.href + 'X', status=404) # wrong URL, unknown file
# unlogged user
assert '/login' in get_app(pub).get(resp.pyquery('#t1').attr.href).location
# other user
user = pub.user_class()
user.name = 'Second user'
user.store()
account = PasswordAccount(id='foo2')
account.set_password('foo2')
account.user_id = user.id
account.store()
login(get_app(pub), username='foo2', password='foo2').get(resp.pyquery('#t1').attr.href, status=403)
def test_rich_commentable_action(pub):
create_user(pub)
formdef = create_formdef()
formdef.data_class().wipe()
formdef.roles = [logged_users_role().id]
formdef.store()
wf = Workflow(name='status')
st1 = wf.add_status('Status1', 'st1')
commentable = st1.add_action('commentable', id='_commentable')
commentable.by = [logged_users_role().id]
commentable.required = True
choice = st1.add_action('choice', id='_x1')
choice.label = 'Submit'
choice.by = [logged_users_role().id]
choice.status = st1.id
wf.store()
formdef.workflow = wf
formdef.store()
# comment
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> submission
resp = resp.follow()
resp.form['comment'] = 'hello world
'
resp = resp.form.submit('button_x1').follow()
assert resp.pyquery('div.comment').text() == 'hello world'
assert 'hello world
' in resp.text
formdata = formdef.data_class().select()[0]
assert formdata.evolution[-1].parts[-1].comment == 'hello world
'
resp.form['comment'] = 'hello
'
resp = resp.form.submit('button_x1').follow()
assert 'hello evil
' in resp.text
formdata = formdef.data_class().select()[0]
assert formdata.evolution[-1].parts[-1].comment == 'hello evil
'
resp.form['comment'] = '
' # left empty
resp = resp.form.submit('button_x1')
assert resp.pyquery('.error').text() == 'required field'
resp.form['comment'] = '
' # left ~empty
resp = resp.form.submit('button_x1')
assert resp.pyquery('.error').text() == 'required field'
# url to links
resp.form['comment'] = 'Here is the address: https://example.net
'
resp = resp.form.submit('button_x1').follow()
assert (
'Here is the address: https://example.net
'
in resp.text
)
# test paragraphs are converted to newlines in plain text view
resp.form['comment'] = 'hello
world
'
resp = resp.form.submit('button_x1').follow()
formdata = formdef.data_class().select()[0]
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
tmpl = Template('{{form_comment}}')
assert tmpl.render(context) == 'hello\n\nworld'
# test are accepted and converted to single-newline in plain text view
resp.form['comment'] = 'hello world
'
resp = resp.form.submit('button_x1').follow()
formdata = formdef.data_class().select()[0]
assert formdata.evolution[-1].parts[-1].comment == 'hello world
'
pub.substitutions.feed(formdata)
context = pub.substitutions.get_context_variables(mode='lazy')
tmpl = Template('{{form_comment}}')
assert tmpl.render(context) == 'hello\nworld'
def test_jumps_with_by_and_no_trigger(pub):
FormDef.wipe()
Workflow.wipe()
pub.role_class.wipe()
role = pub.role_class(name='xxx')
role.store()
workflow = Workflow(name='test')
st1 = workflow.add_status('Status1', 'st1')
jump = st1.add_action('jump')
jump.status = 'st2'
jump.by = [role.id]
jump = st1.add_action('jump')
jump.status = 'st3'
jump.by = []
workflow.add_status('Status2', 'st2')
workflow.add_status('Status3', 'st3')
workflow.store()
formdef = create_formdef()
formdef.fields = []
formdef.workflow = workflow
formdef.store()
formdef.data_class().wipe()
resp = get_app(pub).get('/test/')
resp = resp.form.submit('submit')
resp = resp.form.submit('submit')
# it jumps to st2, as jump.by is only related to triggers
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].status == 'wf-st2'
def test_user_filter_auto_custom_view(pub):
user = create_user(pub)
CardDef.wipe()
carddef = CardDef()
carddef.name = 'items'
carddef.user_support = 'optional'
carddef.digest_templates = {'default': '{{form_var_name}}'}
carddef.fields = [
fields.StringField(id='0', label='string', varname='name'),
]
carddef.store()
for i, value in enumerate(['foo', 'bar', 'baz']):
carddata = carddef.data_class()()
carddata.data = {
'0': value,
'1': 'attr%s' % i,
}
carddata.user_id = user.id
carddata.just_created()
carddata.store()
carddata.user_id = None # don't associate latest (baz) with user
carddata.store()
ds = {'type': 'carddef:%s' % carddef.url_name}
formdef = FormDef()
formdef.name = 'foobar'
formdef.fields = [
fields.ItemField(id='0', label='item', type='item', varname='foo', data_source=ds),
]
formdef.store()
app = get_app(pub)
resp = app.get(formdef.get_url())
assert [x[2] for x in resp.form['f0'].options] == ['bar', 'baz', 'foo']
formdef.fields[0].data_source['type'] = 'carddef:%s:_with_user_filter' % carddef.url_name
formdef.store()
resp = app.get(formdef.get_url())
assert [x[2] for x in resp.form['f0'].options] == ['---']
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdef.get_url())
assert [x[2] for x in resp.form['f0'].options] == ['bar', 'foo']
def test_go_to_backoffice(pub):
formdef = create_formdef()
app = get_app(pub)
resp = app.get('/test/go-to-backoffice')
assert resp.location.endswith('/backoffice/forms/%s/' % formdef.id)
def test_global_interactive_action(pub):
user = create_user(pub)
formdef = FormDef()
formdef.name = 'test global action'
formdef.fields = []
workflow = Workflow.get_default_workflow()
workflow.id = '2'
action = workflow.add_global_action('FOOBAR')
display = action.add_action('displaymsg')
display.message = 'This is a message'
display.to = []
form_action = action.add_action('form')
form_action.varname = 'blah'
form_action.formdef = WorkflowFormFieldsFormDef(item=form_action)
form_action.formdef.fields.append(
fields.StringField(id='1', label='Test', varname='test', type='string', required=True)
)
register_comment = action.add_action('register-comment')
register_comment.comment = 'HELLO {{ form_workflow_form_blah_var_test }}'
trigger = action.triggers[0]
trigger.roles = ['_submitter']
workflow.store()
formdef.workflow_id = workflow.id
formdef.workflow_roles = {'_receiver': 1}
formdef.store()
formdata = formdef.data_class()()
formdata.user_id = user.id
formdata.just_created()
formdata.perform_workflow()
formdata.store()
app = login(get_app(pub), username='foo', password='foo')
resp = app.get(formdata.get_url(backoffice=False))
assert 'button-action-1' in resp.form.fields
resp = resp.form.submit('button-action-1')
resp = resp.follow() # -> error, empty action
resp = resp.follow() # -> back to form
assert 'Error: empty action' in resp.text
form_action.by = trigger.roles
workflow.store()
resp = app.get(formdata.get_url(backoffice=False))
resp = resp.form.submit('button-action-1')
resp = resp.follow()
assert 'This is a message' in resp.text
resp = resp.form.submit('submit')
assert resp.pyquery('#form_error_fblah_1').text() == 'required field'
resp.form['fblah_1'] = 'GLOBAL INTERACTIVE ACTION'
resp = resp.form.submit('submit')
assert resp.location == formdata.get_url(backoffice=False)
resp = resp.follow()
assert 'HELLO GLOBAL INTERACTIVE ACTION' in resp.text