blocks: apply prefill to added rows (#50289)

This commit is contained in:
Frédéric Péters 2021-01-19 11:03:15 +01:00
parent 1831aff4b0
commit 86c70cb2a9
2 changed files with 139 additions and 23 deletions

View File

@ -571,6 +571,101 @@ def test_block_locked_prefill(pub, blocks_feature):
assert formdata.data['3']['data'][0]['123'] == 'Hello World' # value got reverted
def test_block_multi_string_prefill(pub):
FormDef.wipe()
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(
id='123', required=True, label='Test', type='string',
prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}),
]
block.store()
formdef = FormDef()
formdef.name = 'form title'
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.BlockField(id='3', label='test', type='block:foobar', max_items=5),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get(formdef.get_url())
resp.form['f1'] = 'Hello'
resp = resp.form.submit('submit') # -> 2nd page
assert not resp.pyquery('#form_error_f3') # not marked as error
assert resp.form['f3$element0$f123'].value == 'Hello World'
resp = resp.form.submit('f3$add_element') # add second row
assert resp.form['f3$element1$f123'].value == 'Hello World'
resp.form['f3$element1$f123'].value = 'Something else'
resp = resp.form.submit('f3$add_element') # add third row
assert resp.form['f3$element0$f123'].value == 'Hello World'
assert resp.form['f3$element1$f123'].value == 'Something else' # unchanged
assert resp.form['f3$element2$f123'].value == 'Hello World'
resp = resp.form.submit('submit') # -> validation page
resp = resp.form.submit('submit') # -> end page
resp = resp.follow()
formdata = formdef.data_class().select()[0]
assert formdata.data['3']['data'][0]['123'] == 'Hello World'
assert formdata.data['3']['data'][1]['123'] == 'Something else'
assert formdata.data['3']['data'][2]['123'] == 'Hello World'
def test_block_multi_string_modify_prefill(pub):
FormDef.wipe()
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(
id='123', required=True, label='Test', type='string',
prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}),
]
block.store()
formdef = FormDef()
formdef.name = 'form title'
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.BlockField(id='3', label='test', type='block:foobar', max_items=5),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
formdef.data_class().wipe()
resp = app.get(formdef.get_url())
resp.form['f1'] = 'Hello'
resp = resp.form.submit('submit') # -> 2nd page
assert not resp.pyquery('#form_error_f3') # not marked as error
assert resp.form['f3$element0$f123'].value == 'Hello World'
resp = resp.form.submit('f3$add_element') # add second row
assert resp.form['f3$element1$f123'].value == 'Hello World'
resp.form['f3$element1$f123'].value = 'Something else'
resp = resp.form.submit('f3$add_element') # add third row
assert resp.form['f3$element0$f123'].value == 'Hello World'
assert resp.form['f3$element1$f123'].value == 'Something else' # unchanged
assert resp.form['f3$element2$f123'].value == 'Hello World'
resp = resp.form.submit('previous') # -> 1st page
resp.form['f1'] = 'Bye'
resp = resp.form.submit('submit') # -> 2nd page
assert resp.form['f3$element0$f123'].value == 'Bye World' # updated
assert resp.form['f3$element1$f123'].value == 'Something else' # unchanged
assert resp.form['f3$element2$f123'].value == 'Bye World' # updated
def test_workflow_form_block_prefill(pub):
FormDef.wipe()
BlockDef.wipe()

View File

@ -327,27 +327,47 @@ class FormPage(Directory, FormTemplateMixin):
@classmethod
def iter_with_block_fields(cls, form, fields):
from wcs.blocks import BlockSubWidget
for field in fields:
field_key = '%s' % field.id
widget = form.get_widget('f%s' % field_key) if form else None
yield field, field_key, widget, None
yield field, field_key, widget, None, None
if field.key == 'block':
# we only ever prefill the first item
subwidget = widget.widgets[0] if widget else None
for subfield in field.block.fields:
subfield_key = '%s$%s' % (field.id, subfield.id)
subfield_widget = subwidget.get_widget('f%s' % subfield.id) if subwidget else None
yield subfield, subfield_key, subfield_widget, field
# we prefill all items
for idx, subwidget in enumerate(
[x for x in widget.widgets if isinstance(x, BlockSubWidget)]
if widget else []):
if not isinstance(subwidget, BlockSubWidget):
continue
for subfield in field.block.fields:
subfield_key = '%s$%s' % (field.id, subfield.id)
subfield_widget = subwidget.get_widget('f%s' % subfield.id) if subwidget else None
yield subfield, subfield_key, subfield_widget, field, idx
@classmethod
def apply_field_prefills(cls, data, form, displayed_fields):
def apply_field_prefills(cls, data, form, displayed_fields, add_button_clicked=False):
req = get_request()
had_prefill = False
for field, field_key, widget, block in cls.iter_with_block_fields(form, displayed_fields):
if 'prefilling_data' not in data:
data['prefilling_data'] = {}
prefilling_new_data = data['prefilling_data']
prefilling_current_data = copy.copy(prefilling_new_data)
for field, field_key, widget, block, block_idx in cls.iter_with_block_fields(form, displayed_fields):
v = None
prefilled = False
locked = False
if add_button_clicked:
if not block:
# do not replay filling fields that are not part of a block
continue
if widget and widget.value:
# do not alter subwidget values that may not yet have been
# "commited" to data when an "add row" button is clicked
continue
if field.prefill:
prefill_user = get_request().user
if get_request().is_in_backoffice():
@ -363,7 +383,7 @@ class FormPage(Directory, FormTemplateMixin):
has_current_value = False
if block:
try:
current_value = data[block.id]['data'][0][field.id]
current_value = data[block.id]['data'][block_idx][field.id]
has_current_value = True
except (IndexError, KeyError, TypeError, ValueError):
pass
@ -379,7 +399,7 @@ class FormPage(Directory, FormTemplateMixin):
# if it's the same that was previously computed.
prefill_value = v
v = current_value
if data.get('prefilling_data', {}).get(field_key) == current_value:
if prefilling_current_data.get(field_key) == current_value:
# replace value with new value computed for prefill
v = prefill_value
else:
@ -409,9 +429,7 @@ class FormPage(Directory, FormTemplateMixin):
# store computed value, it will be used to compare with
# submitted value if page is visited again.
if should_prefill:
if 'prefilling_data' not in data:
data['prefilling_data'] = {}
data['prefilling_data'][field_key] = v
prefilling_new_data[field_key] = v
if not isinstance(v, str) and field.convert_value_to_str:
v = field.convert_value_to_str(v)
widget.set_value(v)
@ -498,14 +516,17 @@ class FormPage(Directory, FormTemplateMixin):
form.add_submit('previous', _('Previous'))
had_prefill = False
if page_change:
# on page change, we fake a GET request so the form is not altered
# with errors from the previous submit; if the page was already
if page_change or submit_button is True:
# on page change (or when a "add row" button is clicked), we
# fake a GET request so the form is not altered# with errors
# from the previous submit; if the page was already
# visited, we restore values; otherwise we set req.form as empty.
req = get_request()
req.environ['REQUEST_METHOD'] = 'GET'
had_prefill = self.apply_field_prefills(data, form, displayed_fields)
had_prefill = self.apply_field_prefills(data, form,
displayed_fields,
add_button_clicked=bool(submit_button is True))
if had_prefill:
# include prefilled data
@ -520,12 +541,12 @@ class FormPage(Directory, FormTemplateMixin):
else:
# not a page change, reset_locked_data() will have been called
# earlier, we use that to set appropriate fields as readonly.
for field, field_key, widget, block in self.iter_with_block_fields(form, displayed_fields):
for field, field_key, widget, block, block_idx in self.iter_with_block_fields(form, displayed_fields):
if get_request().form.get('__locked_f%s' % field_key):
widget.readonly = 'readonly'
widget.attrs['readonly'] = 'readonly'
for field, field_key, widget, block in self.iter_with_block_fields(form, displayed_fields):
for field, field_key, widget, block, block_idx in self.iter_with_block_fields(form, displayed_fields):
if field.prefill:
# always set additional attributes as they will be used for
# "live prefill", regardless of existing data.
@ -537,7 +558,7 @@ class FormPage(Directory, FormTemplateMixin):
# pass over prefilled fields that are used as live source of item
# fields
fields_to_update = set()
for field, field_key, widget, block in self.iter_with_block_fields(form, displayed_fields):
for field, field_key, widget, block, block_idx in self.iter_with_block_fields(form, displayed_fields):
if getattr(widget, 'prefilled', False) and getattr(widget, 'live_condition_source', False):
fields_to_update.update(widget.live_condition_fields)
elif field in fields_to_update and field.type == 'item':
@ -1121,12 +1142,12 @@ class FormPage(Directory, FormTemplateMixin):
prefill_user = get_request().user
if get_request().is_in_backoffice():
prefill_user = get_publisher().substitutions.get_context_variables().get('form_user')
for field, field_key, widget, block in self.iter_with_block_fields(form, self.formdef.fields):
for field, field_key, widget, block, block_idx in self.iter_with_block_fields(form, self.formdef.fields):
if not field.prefill:
continue
post_key = 'f%s' % field_key
if block:
post_key = 'f%s$element0$f%s' % (block.id, field.id)
post_key = 'f%s$element%s$f%s' % (block.id, block_idx, field.id)
if post_key not in get_request().form:
continue
v, locked = field.get_prefill_value(user=prefill_user)