general: add support for prefilling blocks (#45264)
This commit is contained in:
parent
9e837f2ffe
commit
3a78582845
|
@ -8841,6 +8841,151 @@ def test_block_geoloc_prefill(pub, blocks_feature):
|
|||
assert resp.html.find('div', {'data-geolocation': 'road'})
|
||||
|
||||
|
||||
def test_block_string_prefill(pub, blocks_feature):
|
||||
create_user(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'),
|
||||
]
|
||||
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 resp.form['f3$element0$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'
|
||||
|
||||
# check unmodified prefilled field
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f1'] = 'Hello'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
assert resp.form['f3$element0$f123'].value == 'Hello World'
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
resp = resp.form.submit('previous') # -> 2nd page
|
||||
resp = resp.form.submit('previous') # -> 1st page
|
||||
resp.form['f1'] = 'Test'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
assert resp.form['f3$element0$f123'].value == 'Test World'
|
||||
|
||||
# check modified prefilled field
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
resp.form['f1'] = 'Hello'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
assert resp.form['f3$element0$f123'].value == 'Hello World'
|
||||
resp.form['f3$element0$f123'] = 'Foobar'
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
resp = resp.form.submit('previous') # -> 2nd page
|
||||
resp = resp.form.submit('previous') # -> 1st page
|
||||
resp.form['f1'] = 'Test'
|
||||
resp = resp.form.submit('submit') # -> 2nd page
|
||||
assert resp.form['f3$element0$f123'].value == 'Foobar'
|
||||
|
||||
|
||||
def test_block_locked_prefill(pub, blocks_feature):
|
||||
create_user(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', 'locked': True}),
|
||||
]
|
||||
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'),
|
||||
]
|
||||
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 resp.form['f3$element0$f123'].value == 'Hello World'
|
||||
assert 'readonly' in resp.form['f3$element0$f123'].attrs
|
||||
resp.form['f3$element0$f123'].value = 'Hello' # try changing the value
|
||||
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' # value got reverted
|
||||
|
||||
|
||||
def test_workflow_form_block_prefill(pub):
|
||||
create_user(pub)
|
||||
FormDef.wipe()
|
||||
BlockDef.wipe()
|
||||
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.StringField(id='123', required=True, label='Test', type='string',
|
||||
prefill={'type': 'user', 'value': 'email'}),
|
||||
]
|
||||
block.store()
|
||||
|
||||
wf = Workflow(name='status')
|
||||
st1 = wf.add_status('Status1', 'st1')
|
||||
|
||||
display_form = FormWorkflowStatusItem()
|
||||
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.BlockField(id='3', label='test', type='block:foobar')
|
||||
)
|
||||
st1.items.append(display_form)
|
||||
display_form.parent = st1
|
||||
|
||||
wf.store()
|
||||
|
||||
formdef = create_formdef()
|
||||
formdef.workflow_id = wf.id
|
||||
formdef.fields = []
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
resp = login(get_app(pub), username='foo', password='foo').get('/test/')
|
||||
resp = resp.form.submit('submit') # -> validation
|
||||
resp = resp.form.submit('submit').follow()
|
||||
assert 'The form has been recorded' in resp
|
||||
assert resp.form['f3$element0$f123'].value == 'foo@localhost'
|
||||
|
||||
|
||||
def test_block_title_and_comment(pub, blocks_feature):
|
||||
create_user(pub)
|
||||
FormDef.wipe()
|
||||
|
|
|
@ -298,6 +298,104 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
'current_page_no': current_position,
|
||||
})
|
||||
|
||||
@classmethod
|
||||
def iter_with_block_fields(cls, form, fields):
|
||||
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
|
||||
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
|
||||
|
||||
@classmethod
|
||||
def apply_field_prefills(cls, data, form, displayed_fields):
|
||||
req = get_request()
|
||||
had_prefill = False
|
||||
for field, field_key, widget, block in cls.iter_with_block_fields(form, displayed_fields):
|
||||
v = None
|
||||
prefilled = False
|
||||
locked = False
|
||||
|
||||
if field.prefill:
|
||||
prefill_user = get_request().user
|
||||
if get_request().is_in_backoffice():
|
||||
prefill_user = get_publisher().substitutions.get_context_variables(
|
||||
).get('form_user')
|
||||
v, locked = field.get_prefill_value(user=prefill_user)
|
||||
|
||||
# always set additional attributes as they will be used for
|
||||
# "live prefill", regardless of existing data.
|
||||
widget.prefill_attributes = field.get_prefill_attributes()
|
||||
|
||||
should_prefill = bool(field.prefill)
|
||||
|
||||
has_current_value = False
|
||||
if block:
|
||||
try:
|
||||
current_value = data[block.id]['data'][0][field.id]
|
||||
has_current_value = True
|
||||
except (IndexError, KeyError, ValueError):
|
||||
pass
|
||||
else:
|
||||
try:
|
||||
current_value = data[field_key]
|
||||
has_current_value = True
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
if has_current_value:
|
||||
# existing value, update it with the new computed value
|
||||
# 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:
|
||||
# replace value with new value computed for prefill
|
||||
v = prefill_value
|
||||
else:
|
||||
should_prefill = False
|
||||
|
||||
if should_prefill:
|
||||
if get_request().is_in_backoffice() and (
|
||||
field.prefill and field.prefill.get('type') == 'geoloc'):
|
||||
# turn off prefilling from geolocation attributes if
|
||||
# the form is filled from the backoffice
|
||||
v = None
|
||||
if v:
|
||||
prefilled = True
|
||||
widget.prefilled = True
|
||||
|
||||
if not prefilled and widget:
|
||||
widget.clear_error()
|
||||
widget._parsed = False
|
||||
|
||||
if v is not None:
|
||||
# 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
|
||||
if not isinstance(v, str) and field.convert_value_to_str:
|
||||
v = field.convert_value_to_str(v)
|
||||
widget.set_value(v)
|
||||
widget.transfer_form_value(req)
|
||||
if field.type == 'item' and v and widget.value != v:
|
||||
# mark field as invalid if the value was not accepted
|
||||
# (this is required by quixote>=3 as the value would
|
||||
# not be evaluated in the initial GET request of the
|
||||
# page).
|
||||
widget.set_error(get_selection_error_text())
|
||||
if locked:
|
||||
widget.readonly = 'readonly'
|
||||
widget.attrs['readonly'] = 'readonly'
|
||||
had_prefill = True
|
||||
return had_prefill
|
||||
|
||||
def page(self, page, page_change=True, page_error_messages=None, submit_button=None):
|
||||
displayed_fields = []
|
||||
|
||||
|
@ -360,70 +458,8 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
# visited, we restore values; otherwise we set req.form as empty.
|
||||
req = get_request()
|
||||
req.environ['REQUEST_METHOD'] = 'GET'
|
||||
for field in displayed_fields:
|
||||
k = field.id
|
||||
v = None
|
||||
prefilled = False
|
||||
locked = False
|
||||
|
||||
if field.prefill:
|
||||
prefill_user = get_request().user
|
||||
if get_request().is_in_backoffice():
|
||||
prefill_user = get_publisher().substitutions.get_context_variables(
|
||||
).get('form_user')
|
||||
v, locked = field.get_prefill_value(user=prefill_user)
|
||||
|
||||
# always set additional attributes as they will be used for
|
||||
# "live prefill", regardless of existing data.
|
||||
form.get_widget('f%s' % k).prefill_attributes = field.get_prefill_attributes()
|
||||
|
||||
should_prefill = bool(field.prefill)
|
||||
|
||||
if k in data:
|
||||
# existing value, update it with the new computed value
|
||||
# if it's the same that was previously computed.
|
||||
prefill_value = v
|
||||
v = data[k]
|
||||
if data.get('prefilling_data', {}).get(k) == data[k]:
|
||||
# replace value with new value computed for prefill
|
||||
v = prefill_value
|
||||
else:
|
||||
should_prefill = False
|
||||
|
||||
if should_prefill:
|
||||
if get_request().is_in_backoffice() and (
|
||||
field.prefill and field.prefill.get('type') == 'geoloc'):
|
||||
# turn off prefilling from geolocation attributes if
|
||||
# the form is filled from the backoffice
|
||||
v = None
|
||||
if v:
|
||||
prefilled = True
|
||||
form.get_widget('f%s' % k).prefilled = True
|
||||
|
||||
if not prefilled and form.get_widget('f%s' % k):
|
||||
form.get_widget('f%s' % k).clear_error()
|
||||
|
||||
if v is not None:
|
||||
# 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'][k] = v
|
||||
if not isinstance(v, str) and field.convert_value_to_str:
|
||||
v = field.convert_value_to_str(v)
|
||||
form.get_widget('f%s' % k).set_value(v)
|
||||
form.get_widget('f%s' % k).transfer_form_value(req)
|
||||
if field.type == 'item' and v and form.get_widget('f%s' % k).value != v:
|
||||
# mark field as invalid if the value was not accepted
|
||||
# (this is required by quixote>=3 as the value would
|
||||
# not be evaluated in the initial GET request of the
|
||||
# page).
|
||||
form.get_widget('f%s' % k).set_error(get_selection_error_text())
|
||||
if locked:
|
||||
form.get_widget('f%s' % k).readonly = 'readonly'
|
||||
form.get_widget('f%s' % k).attrs['readonly'] = 'readonly'
|
||||
had_prefill = True
|
||||
had_prefill = self.apply_field_prefills(data, form, displayed_fields)
|
||||
|
||||
if had_prefill:
|
||||
# include prefilled data
|
||||
|
@ -438,16 +474,16 @@ 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 in displayed_fields:
|
||||
if get_request().form.get('__locked_f%s' % field.id):
|
||||
form.get_widget('f%s' % field.id).readonly = 'readonly'
|
||||
form.get_widget('f%s' % field.id).attrs['readonly'] = 'readonly'
|
||||
for field, field_key, widget, block 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 in displayed_fields:
|
||||
for field, field_key, widget, block 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.
|
||||
form.get_widget('f%s' % field.id).prefill_attributes = field.get_prefill_attributes()
|
||||
widget.prefill_attributes = field.get_prefill_attributes()
|
||||
|
||||
self.formdef.set_live_condition_sources(form, displayed_fields)
|
||||
|
||||
|
@ -455,8 +491,7 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
# pass over prefilled fields that are used as live source of item
|
||||
# fields
|
||||
fields_to_update = set()
|
||||
for field in displayed_fields:
|
||||
widget = form.get_widget('f%s' % field.id)
|
||||
for field, field_key, widget, block 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':
|
||||
|
@ -857,7 +892,7 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
transient_formdata, force_mode='lazy'):
|
||||
# reset locked data with newly submitted values, this allows
|
||||
# for templates referencing fields from the sampe page.
|
||||
self.reset_locked_data()
|
||||
self.reset_locked_data(form)
|
||||
data = self.formdef.get_data(form)
|
||||
|
||||
form_data.update(data)
|
||||
|
@ -969,7 +1004,7 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
else:
|
||||
return self.page(self.pages[page_no])
|
||||
|
||||
self.reset_locked_data()
|
||||
self.reset_locked_data(form)
|
||||
if step == 1:
|
||||
form.add_submit('previous')
|
||||
magictoken = form.get_widget('magictoken').parse()
|
||||
|
@ -1020,15 +1055,18 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
|
||||
return self.submitted(form, existing_formdata)
|
||||
|
||||
def reset_locked_data(self):
|
||||
def reset_locked_data(self, form):
|
||||
# reset locked fields, making sure the user cannot alter them.
|
||||
prefill_user = get_request().user
|
||||
if get_request().is_in_backoffice():
|
||||
prefill_user = get_publisher().substitutions.get_context_variables().get('form_user')
|
||||
for field in self.formdef.fields:
|
||||
for field, field_key, widget, block in self.iter_with_block_fields(form, self.formdef.fields):
|
||||
if not field.prefill:
|
||||
continue
|
||||
if not 'f%s' % field.id in get_request().form:
|
||||
post_key = 'f%s' % field_key
|
||||
if block:
|
||||
post_key = 'f%s$element0$f%s' % (block.id, field.id)
|
||||
if post_key not in get_request().form:
|
||||
continue
|
||||
v, locked = field.get_prefill_value(user=prefill_user)
|
||||
if locked:
|
||||
|
@ -1036,10 +1074,19 @@ class FormPage(Directory, FormTemplateMixin):
|
|||
# convert structured data to strings as if they were
|
||||
# submitted by the browser.
|
||||
v = field.convert_value_to_str(v)
|
||||
get_request().form['f%s' % field.id] = v
|
||||
get_request().form[post_key] = v
|
||||
if widget:
|
||||
widget.set_value(v)
|
||||
if block:
|
||||
# child widget value was changed, mark parent widgets
|
||||
# as unparsed
|
||||
block_widget = form.get_widget('f%s' % block.id)
|
||||
block_widget._parsed = False
|
||||
block_widget.widgets[0]._parsed = False
|
||||
|
||||
# keep track of locked field, this will be used when
|
||||
# redisplaying the same page in case of errors.
|
||||
get_request().form['__locked_f%s' % field.id] = True
|
||||
get_request().form['__locked_f%s' % field_key] = True
|
||||
|
||||
def previous_page(self, page_no, magictoken):
|
||||
session = get_session()
|
||||
|
|
|
@ -26,6 +26,7 @@ from wcs.admin.fields import FieldDefPage, FieldsDirectory
|
|||
from wcs.formdata import get_dict_with_varnames
|
||||
|
||||
from wcs.forms.common import FileDirectory
|
||||
from wcs.forms.root import FormPage
|
||||
|
||||
|
||||
def lookup_wf_form_file(self, filename):
|
||||
|
@ -177,7 +178,6 @@ class FormWorkflowStatusItem(WorkflowStatusItem):
|
|||
|
||||
formdata.feed_session()
|
||||
|
||||
req = get_request()
|
||||
self.formdef.set_live_condition_sources(form, self.formdef.fields)
|
||||
|
||||
if form.is_submitted():
|
||||
|
@ -188,25 +188,7 @@ class FormWorkflowStatusItem(WorkflowStatusItem):
|
|||
if displayed_fields is not None:
|
||||
fields = displayed_fields
|
||||
|
||||
for field in fields:
|
||||
if ('f%s' % field.id) in req.form:
|
||||
continue
|
||||
if not field.prefill or field.prefill.get('type') == 'none':
|
||||
continue
|
||||
# FIXME: this code duplicates code from wcs/forms/root.py :/
|
||||
prefill_user = get_request().user
|
||||
if get_request().is_in_backoffice():
|
||||
prefill_user = get_publisher().substitutions.get_context_variables(
|
||||
).get('form_user')
|
||||
v, verified = field.get_prefill_value(user=prefill_user)
|
||||
if get_request().is_in_backoffice() and (
|
||||
field.prefill and field.prefill.get('type') == 'geoloc'):
|
||||
# turn off prefilling from geolocation attributes if
|
||||
# the form is filled from the backoffice
|
||||
v = None
|
||||
if v:
|
||||
form.get_widget('f%s' % field.id).set_value(v)
|
||||
req.form['f%s' % field.id] = v
|
||||
FormPage.apply_field_prefills({}, form, fields)
|
||||
|
||||
def evaluate_live_form(self, form, formdata, user):
|
||||
workflow_data = {}
|
||||
|
|
Loading…
Reference in New Issue