misc: re-prefill if value was not modified (#28117)

This commit is contained in:
Frédéric Péters 2020-10-05 14:20:26 +02:00
parent b517204ca0
commit 0c04b5e3ee
6 changed files with 143 additions and 21 deletions

View File

@ -2826,6 +2826,101 @@ def test_form_page_formula_prefill_items_field(pub):
assert resp.form['f0$element2'].checked
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):
user = create_user(pub)
formdef = create_formdef()

View File

@ -279,8 +279,8 @@ class FormFillPage(PublicFormFillPage):
formdata.remove_self()
return redirect(url)
def save_draft(self, data, page_no):
formdata = super(FormFillPage, self).save_draft(data, page_no)
def save_draft(self, data, page_no=None):
formdata = super(FormFillPage, self).save_draft(data, page_no=page_no)
formdata.backoffice_submission = True
if not formdata.submission_context:
formdata.submission_context = {}

View File

@ -244,6 +244,7 @@ class FormData(StorableObject):
criticality_level = 0
digest = None
prefilling_data = None
workflow_data = None
workflow_roles = None
geolocations = None

View File

@ -311,6 +311,7 @@ class FormStatusPage(Directory, FormTemplateMixin):
# add back file to session
tempfile = session.add_tempfile(form_data[field.id], storage=field.storage)
form_data[field.id].token = tempfile['token']
form_data['prefilling_data'] = filled.prefilling_data or {}
form_data['is_recalled_draft'] = True
form_data['draft_formdata_id'] = filled.id
form_data['page_no'] = filled.page_no

View File

@ -377,9 +377,20 @@ class FormPage(Directory, FormTemplateMixin):
# "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]
elif field.prefill:
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
@ -393,6 +404,12 @@ class FormPage(Directory, FormTemplateMixin):
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)
@ -412,6 +429,9 @@ class FormPage(Directory, FormTemplateMixin):
# include prefilled data
transient_formdata = self.get_transient_formdata()
transient_formdata.data.update(self.formdef.get_data(form))
if self.has_draft_support():
# save to get prefilling data in database
self.save_draft(form_data)
else:
req.form = {}
@ -553,6 +573,7 @@ class FormPage(Directory, FormTemplateMixin):
formdata._formdef = self.formdef
formdata.user = get_request().user
formdata.data = get_session().get_by_magictoken(magictoken, {})
formdata.prefilling_data = formdata.data.get('prefilling_data', {})
if self.edit_mode:
# keep associated user as it may be required as a parameter in
@ -1117,13 +1138,15 @@ class FormPage(Directory, FormTemplateMixin):
return json.dumps({'result': 'success'})
def save_draft(self, data, page_no):
def save_draft(self, data, page_no=None):
filled = self.get_current_draft() or self.formdef.data_class()()
if filled.id and filled.status != 'draft':
raise SubmittedDraftException()
filled.data = data
filled.prefilling_data = data.get('prefilling_data')
filled.status = 'draft'
filled.page_no = page_no
if page_no is not None:
filled.page_no = page_no
filled.receipt_time = time.localtime()
session = get_session()
if get_request().is_in_backoffice():

View File

@ -488,7 +488,7 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild
'actions_roles_array', 'backoffice_submission',
'submission_context', 'submission_agent_id', 'submission_channel',
'criticality_level', 'last_update_time',
'digest', 'user_label'])
'digest', 'user_label', 'prefilling_data',])
# migrations
if not 'fts' in existing_fields:
@ -538,6 +538,9 @@ def do_formdef_tables(formdef, conn=None, cur=None, rebuild_views=False, rebuild
if not 'user_label' in existing_fields:
cur.execute('''ALTER TABLE %s ADD COLUMN user_label varchar''' % table_name)
if 'prefilling_data' not in existing_fields:
cur.execute('''ALTER TABLE %s ADD COLUMN prefilling_data bytea''' % table_name)
# add new fields
for field in formdef.get_all_fields():
assert field.id is not None
@ -1484,6 +1487,7 @@ class SqlDataMixin(SqlMixin):
('page_no', 'varchar'),
('anonymised', 'timestamptz'),
('workflow_data', 'bytea'),
('prefilling_data', 'bytea'),
('id_display', 'varchar'),
('workflow_roles', 'bytea'),
('workflow_roles_array', 'text[]'),
@ -1586,12 +1590,13 @@ class SqlDataMixin(SqlMixin):
'user_id': self.user_id,
'status': self.status,
'page_no': self.page_no,
'workflow_data': bytearray(pickle.dumps(self.workflow_data, protocol=2)),
'workflow_data': self.workflow_data,
'id_display': self.id_display,
'anonymised': self.anonymised,
'tracking_code': self.tracking_code,
'backoffice_submission': self.backoffice_submission,
'submission_context': self.submission_context,
'prefilling_data': self.prefilling_data,
'submission_agent_id': self.submission_agent_id,
'submission_channel': self.submission_channel,
'criticality_level': self.criticality_level,
@ -1605,15 +1610,14 @@ class SqlDataMixin(SqlMixin):
else:
sql_dict['receipt_time'] = None
if self.workflow_roles:
sql_dict['workflow_roles'] = bytearray(pickle.dumps(self.workflow_roles, protocol=2))
sql_dict['workflow_roles_array'] = [str(x) for x in self.workflow_roles.values() if x is not None]
else:
sql_dict['workflow_roles'] = None
sql_dict['workflow_roles_array'] = None
if self.submission_context:
sql_dict['submission_context'] = bytearray(pickle.dumps(self.submission_context, protocol=2))
else:
sql_dict['submission_context'] = None
for attr in ('workflow_data', 'workflow_roles', 'submission_context', 'prefilling_data'):
if getattr(self, attr):
sql_dict[attr] = bytearray(pickle.dumps(getattr(self, attr), protocol=2))
else:
sql_dict[attr] = None
for field in (self._formdef.geolocations or {}).keys():
value = (self.geolocations or {}).get(field)
@ -1753,12 +1757,9 @@ class SqlDataMixin(SqlMixin):
setattr(o, static_field[0], str_encode(value))
if o.receipt_time:
o.receipt_time = o.receipt_time.timetuple()
if o.workflow_data:
o.workflow_data = pickle_loads(o.workflow_data)
if o.workflow_roles:
o.workflow_roles = pickle_loads(o.workflow_roles)
if o.submission_context:
o.submission_context = pickle_loads(o.submission_context)
for attr in ('workflow_data', 'workflow_roles', 'submission_context', 'prefilling_data'):
if getattr(o, attr):
setattr(o, attr, pickle_loads(getattr(o, attr)))
o.geolocations = {}
for i, field in enumerate((cls._formdef.geolocations or {}).keys()):
@ -2679,7 +2680,7 @@ def get_yearly_totals(period_start=None, period_end=None, criterias=None):
return result
SQL_LEVEL = 42
SQL_LEVEL = 43
def migrate_global_views(conn, cur):
@ -2753,7 +2754,7 @@ def migrate():
raise RuntimeError()
if sql_level < 1: # 1: introduction of tracking_code table
do_tracking_code_table()
if sql_level < 38:
if sql_level < 43:
# 2: introduction of formdef_id in views
# 5: add concerned_roles_array, is_at_endpoint and fts to views
# 7: add backoffice_submission to tables
@ -2772,6 +2773,7 @@ def migrate():
# 31: add user_label to formdata
# 33: add anonymised field to global view
# 38: extract submission_agent_id to its own column
# 43: add prefilling_data to formdata
migrate_views(conn, cur)
if sql_level < 40:
# 3: introduction of _structured for user fields