misc: add actual prefill support for multiple choices widgets (#76568) #668

Merged
fpeters merged 1 commits from wip/76568-multi-prefill into main 2023-09-15 08:00:49 +02:00
2 changed files with 146 additions and 3 deletions

View File

@ -573,7 +573,10 @@ def test_form_page_date_prefill(pub):
assert resp.forms[0]['f0'].value == '2023-07-07'
def test_form_page_template_prefill_items_field(pub):
@pytest.mark.parametrize('prefill_value', ['foo,baz', 'foo|baz'])
def test_form_page_template_prefill_items_field_checkboxes(pub, prefill_value):
# prefill value should be given as foo|baz but foo,baz has been used for a while
# and must be kept working (even if it worked by chance).
BlockDef.wipe()
create_user(pub)
@ -591,7 +594,7 @@ def test_form_page_template_prefill_items_field(pub):
id='0',
label='items',
items=['foo', 'bar', 'baz'],
prefill={'type': 'string', 'value': 'foo,baz'},
prefill={'type': 'string', 'value': prefill_value},
),
fields.FileField(id='1', label='file', varname='file'),
fields.BlockField(id='2', label='test', block_slug='foobar', varname='foobar'),
@ -619,7 +622,7 @@ def test_form_page_template_prefill_items_field(pub):
label='items',
data_source=ds,
display_disabled_items=True,
prefill={'type': 'string', 'value': 'foo,baz'},
prefill={'type': 'string', 'value': prefill_value},
)
formdef.store()
@ -708,6 +711,135 @@ def test_form_page_template_prefill_items_field(pub):
assert pub.loggederror_class.count() == 0
def test_form_page_template_prefill_items_field_autocomplete(pub):
BlockDef.wipe()
create_user(pub)
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='123', required=True, label='Test', 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'},
display_mode='autocomplete',
),
fields.FileField(id='1', label='file', varname='file'),
fields.BlockField(id='2', label='test', block_slug='foobar', varname='foobar'),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0[]'].value == ['foo', 'baz']
# this selection will be reused in the complex data test
resp.form['f0[]'].value = ['foo', 'bar']
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'},
display_mode='autocomplete',
)
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[]'].value == ['foo', 'baz']
# 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"}}'},
display_mode='autocomplete',
)
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[]'].value == ['foo', 'bar']
# 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,
display_mode='autocomplete',
)
formdef.store()
resp = get_app(pub).get('/test/')
assert resp.form['f0[]'].value is None
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,
display_mode='autocomplete',
)
formdef.store()
# all checkboxes will be left unchecked
resp = get_app(pub).get('/test/')
assert resp.form['f0[]'].value is None
assert pub.loggederror_class.count() == 0
def test_form_page_changing_prefill(pub):
formdef = create_formdef()
formdef.data_class().wipe()

View File

@ -1789,6 +1789,12 @@ class CheckboxesWidget(Widget):
def get_too_long_message(self):
return _('You must select at most %d answers.') % self.max_choices
def set_value(self, value):
if isinstance(value, str) and '|' in value:
self.value = value.split('|')
else:
super().set_value(value)
def transfer_form_value(self, request):
for v in self.value or []:
for option in self.get_options():
@ -2801,6 +2807,11 @@ class MultiSelectWidget(MultipleSelectWidget):
def get_selected_options_labels(self):
return list(x.get('label') for x in self.get_options() if x.get('selected'))
def set_value(self, value):
if isinstance(value, str) and '|' in value:
value = value.split('|')
super().set_value(value)
def transfer_form_value(self, request):
request.form[self.name + '[]'] = self.value