0001-general-add-support-for-prefilling-blocks-45264.patch
tests/test_form_pages.py | ||
---|---|---|
8841 | 8841 |
assert resp.html.find('div', {'data-geolocation': 'road'}) |
8842 | 8842 | |
8843 | 8843 | |
8844 |
def test_block_string_prefill(pub, blocks_feature): |
|
8845 |
create_user(pub) |
|
8846 |
FormDef.wipe() |
|
8847 |
BlockDef.wipe() |
|
8848 | ||
8849 |
block = BlockDef() |
|
8850 |
block.name = 'foobar' |
|
8851 |
block.fields = [ |
|
8852 |
fields.StringField(id='123', required=True, label='Test', type='string', |
|
8853 |
prefill={'type': 'string', 'value': '{{ form_var_foo }} World'}), |
|
8854 |
] |
|
8855 |
block.store() |
|
8856 | ||
8857 |
formdef = FormDef() |
|
8858 |
formdef.name = 'form title' |
|
8859 |
formdef.fields = [ |
|
8860 |
fields.PageField(id='0', label='1st page', type='page'), |
|
8861 |
fields.StringField(id='1', label='string', varname='foo'), |
|
8862 |
fields.PageField(id='2', label='2nd page', type='page'), |
|
8863 |
fields.BlockField(id='3', label='test', type='block:foobar'), |
|
8864 |
] |
|
8865 |
formdef.store() |
|
8866 |
formdef.data_class().wipe() |
|
8867 | ||
8868 |
app = get_app(pub) |
|
8869 |
resp = app.get(formdef.get_url()) |
|
8870 |
resp.form['f1'] = 'Hello' |
|
8871 |
resp = resp.form.submit('submit') # -> 2nd page |
|
8872 |
assert resp.form['f3$element0$f123'].value == 'Hello World' |
|
8873 |
resp = resp.form.submit('submit') # -> validation page |
|
8874 |
resp = resp.form.submit('submit') # -> end page |
|
8875 |
resp = resp.follow() |
|
8876 | ||
8877 |
formdata = formdef.data_class().select()[0] |
|
8878 |
assert formdata.data['3']['data'][0]['123'] == 'Hello World' |
|
8879 | ||
8880 |
# check unmodified prefilled field |
|
8881 |
app = get_app(pub) |
|
8882 |
resp = app.get(formdef.get_url()) |
|
8883 |
resp.form['f1'] = 'Hello' |
|
8884 |
resp = resp.form.submit('submit') # -> 2nd page |
|
8885 |
assert resp.form['f3$element0$f123'].value == 'Hello World' |
|
8886 |
resp = resp.form.submit('submit') # -> validation page |
|
8887 |
resp = resp.form.submit('previous') # -> 2nd page |
|
8888 |
resp = resp.form.submit('previous') # -> 1st page |
|
8889 |
resp.form['f1'] = 'Test' |
|
8890 |
resp = resp.form.submit('submit') # -> 2nd page |
|
8891 |
assert resp.form['f3$element0$f123'].value == 'Test World' |
|
8892 | ||
8893 |
# check modified prefilled field |
|
8894 |
app = get_app(pub) |
|
8895 |
resp = app.get(formdef.get_url()) |
|
8896 |
resp.form['f1'] = 'Hello' |
|
8897 |
resp = resp.form.submit('submit') # -> 2nd page |
|
8898 |
assert resp.form['f3$element0$f123'].value == 'Hello World' |
|
8899 |
resp.form['f3$element0$f123'] = 'Foobar' |
|
8900 |
resp = resp.form.submit('submit') # -> validation page |
|
8901 |
resp = resp.form.submit('previous') # -> 2nd page |
|
8902 |
resp = resp.form.submit('previous') # -> 1st page |
|
8903 |
resp.form['f1'] = 'Test' |
|
8904 |
resp = resp.form.submit('submit') # -> 2nd page |
|
8905 |
assert resp.form['f3$element0$f123'].value == 'Foobar' |
|
8906 | ||
8907 | ||
8908 |
def test_block_locked_prefill(pub, blocks_feature): |
|
8909 |
create_user(pub) |
|
8910 |
FormDef.wipe() |
|
8911 |
BlockDef.wipe() |
|
8912 | ||
8913 |
block = BlockDef() |
|
8914 |
block.name = 'foobar' |
|
8915 |
block.fields = [ |
|
8916 |
fields.StringField(id='123', required=True, label='Test', type='string', |
|
8917 |
prefill={'type': 'string', 'value': '{{ form_var_foo }} World', 'locked': True}), |
|
8918 |
] |
|
8919 |
block.store() |
|
8920 | ||
8921 |
formdef = FormDef() |
|
8922 |
formdef.name = 'form title' |
|
8923 |
formdef.fields = [ |
|
8924 |
fields.PageField(id='0', label='1st page', type='page'), |
|
8925 |
fields.StringField(id='1', label='string', varname='foo'), |
|
8926 |
fields.PageField(id='2', label='2nd page', type='page'), |
|
8927 |
fields.BlockField(id='3', label='test', type='block:foobar'), |
|
8928 |
] |
|
8929 |
formdef.store() |
|
8930 |
formdef.data_class().wipe() |
|
8931 | ||
8932 |
app = get_app(pub) |
|
8933 |
resp = app.get(formdef.get_url()) |
|
8934 |
resp.form['f1'] = 'Hello' |
|
8935 |
resp = resp.form.submit('submit') # -> 2nd page |
|
8936 |
assert resp.form['f3$element0$f123'].value == 'Hello World' |
|
8937 |
assert 'readonly' in resp.form['f3$element0$f123'].attrs |
|
8938 |
resp.form['f3$element0$f123'].value = 'Hello' # try changing the value |
|
8939 |
resp = resp.form.submit('submit') # -> validation page |
|
8940 |
resp = resp.form.submit('submit') # -> end page |
|
8941 |
resp = resp.follow() |
|
8942 | ||
8943 |
formdata = formdef.data_class().select()[0] |
|
8944 |
assert formdata.data['3']['data'][0]['123'] == 'Hello World' # value got reverted |
|
8945 | ||
8946 | ||
8947 |
def test_workflow_form_block_prefill(pub): |
|
8948 |
create_user(pub) |
|
8949 |
FormDef.wipe() |
|
8950 |
BlockDef.wipe() |
|
8951 | ||
8952 |
block = BlockDef() |
|
8953 |
block.name = 'foobar' |
|
8954 |
block.fields = [ |
|
8955 |
fields.StringField(id='123', required=True, label='Test', type='string', |
|
8956 |
prefill={'type': 'user', 'value': 'email'}), |
|
8957 |
] |
|
8958 |
block.store() |
|
8959 | ||
8960 |
wf = Workflow(name='status') |
|
8961 |
st1 = wf.add_status('Status1', 'st1') |
|
8962 | ||
8963 |
display_form = FormWorkflowStatusItem() |
|
8964 |
display_form.id = '_x' |
|
8965 |
display_form.by = ['_submitter'] |
|
8966 |
display_form.varname = 'xxx' |
|
8967 |
display_form.formdef = WorkflowFormFieldsFormDef(item=display_form) |
|
8968 |
display_form.formdef.fields.append( |
|
8969 |
fields.BlockField(id='3', label='test', type='block:foobar') |
|
8970 |
) |
|
8971 |
st1.items.append(display_form) |
|
8972 |
display_form.parent = st1 |
|
8973 | ||
8974 |
wf.store() |
|
8975 | ||
8976 |
formdef = create_formdef() |
|
8977 |
formdef.workflow_id = wf.id |
|
8978 |
formdef.fields = [] |
|
8979 |
formdef.store() |
|
8980 |
formdef.data_class().wipe() |
|
8981 | ||
8982 |
resp = login(get_app(pub), username='foo', password='foo').get('/test/') |
|
8983 |
resp = resp.form.submit('submit') # -> validation |
|
8984 |
resp = resp.form.submit('submit').follow() |
|
8985 |
assert 'The form has been recorded' in resp |
|
8986 |
assert resp.form['f3$element0$f123'].value == 'foo@localhost' |
|
8987 | ||
8988 | ||
8844 | 8989 |
def test_block_title_and_comment(pub, blocks_feature): |
8845 | 8990 |
create_user(pub) |
8846 | 8991 |
FormDef.wipe() |
wcs/forms/root.py | ||
---|---|---|
298 | 298 |
'current_page_no': current_position, |
299 | 299 |
}) |
300 | 300 | |
301 |
@classmethod |
|
302 |
def iter_with_block_fields(cls, form, fields): |
|
303 |
for field in fields: |
|
304 |
field_key = '%s' % field.id |
|
305 |
widget = form.get_widget('f%s' % field_key) if form else None |
|
306 |
yield field, field_key, widget, None |
|
307 |
if field.key == 'block': |
|
308 |
# we only ever prefill the first item |
|
309 |
subwidget = widget.widgets[0] if widget else None |
|
310 |
for subfield in field.block.fields: |
|
311 |
subfield_key = '%s$%s' % (field.id, subfield.id) |
|
312 |
subfield_widget = subwidget.get_widget('f%s' % subfield.id) if subwidget else None |
|
313 |
yield subfield, subfield_key, subfield_widget, field |
|
314 | ||
315 |
@classmethod |
|
316 |
def apply_field_prefills(cls, data, form, displayed_fields): |
|
317 |
req = get_request() |
|
318 |
had_prefill = False |
|
319 |
for field, field_key, widget, block in cls.iter_with_block_fields(form, displayed_fields): |
|
320 |
v = None |
|
321 |
prefilled = False |
|
322 |
locked = False |
|
323 | ||
324 |
if field.prefill: |
|
325 |
prefill_user = get_request().user |
|
326 |
if get_request().is_in_backoffice(): |
|
327 |
prefill_user = get_publisher().substitutions.get_context_variables( |
|
328 |
).get('form_user') |
|
329 |
v, locked = field.get_prefill_value(user=prefill_user) |
|
330 | ||
331 |
# always set additional attributes as they will be used for |
|
332 |
# "live prefill", regardless of existing data. |
|
333 |
widget.prefill_attributes = field.get_prefill_attributes() |
|
334 | ||
335 |
should_prefill = bool(field.prefill) |
|
336 | ||
337 |
has_current_value = False |
|
338 |
if block: |
|
339 |
try: |
|
340 |
current_value = data[block.id]['data'][0][field.id] |
|
341 |
has_current_value = True |
|
342 |
except (IndexError, KeyError, ValueError): |
|
343 |
pass |
|
344 |
else: |
|
345 |
try: |
|
346 |
current_value = data[field_key] |
|
347 |
has_current_value = True |
|
348 |
except KeyError: |
|
349 |
pass |
|
350 | ||
351 |
if has_current_value: |
|
352 |
# existing value, update it with the new computed value |
|
353 |
# if it's the same that was previously computed. |
|
354 |
prefill_value = v |
|
355 |
v = current_value |
|
356 |
if data.get('prefilling_data', {}).get(field_key) == current_value: |
|
357 |
# replace value with new value computed for prefill |
|
358 |
v = prefill_value |
|
359 |
else: |
|
360 |
should_prefill = False |
|
361 | ||
362 |
if should_prefill: |
|
363 |
if get_request().is_in_backoffice() and ( |
|
364 |
field.prefill and field.prefill.get('type') == 'geoloc'): |
|
365 |
# turn off prefilling from geolocation attributes if |
|
366 |
# the form is filled from the backoffice |
|
367 |
v = None |
|
368 |
if v: |
|
369 |
prefilled = True |
|
370 |
widget.prefilled = True |
|
371 | ||
372 |
if not prefilled and widget: |
|
373 |
widget.clear_error() |
|
374 |
widget._parsed = False |
|
375 | ||
376 |
if v is not None: |
|
377 |
# store computed value, it will be used to compare with |
|
378 |
# submitted value if page is visited again. |
|
379 |
if should_prefill: |
|
380 |
if 'prefilling_data' not in data: |
|
381 |
data['prefilling_data'] = {} |
|
382 |
data['prefilling_data'][field_key] = v |
|
383 |
if not isinstance(v, str) and field.convert_value_to_str: |
|
384 |
v = field.convert_value_to_str(v) |
|
385 |
widget.set_value(v) |
|
386 |
widget.transfer_form_value(req) |
|
387 |
if field.type == 'item' and v and widget.value != v: |
|
388 |
# mark field as invalid if the value was not accepted |
|
389 |
# (this is required by quixote>=3 as the value would |
|
390 |
# not be evaluated in the initial GET request of the |
|
391 |
# page). |
|
392 |
widget.set_error(get_selection_error_text()) |
|
393 |
if locked: |
|
394 |
widget.readonly = 'readonly' |
|
395 |
widget.attrs['readonly'] = 'readonly' |
|
396 |
had_prefill = True |
|
397 |
return had_prefill |
|
398 | ||
301 | 399 |
def page(self, page, page_change=True, page_error_messages=None, submit_button=None): |
302 | 400 |
displayed_fields = [] |
303 | 401 | |
... | ... | |
360 | 458 |
# visited, we restore values; otherwise we set req.form as empty. |
361 | 459 |
req = get_request() |
362 | 460 |
req.environ['REQUEST_METHOD'] = 'GET' |
363 |
for field in displayed_fields: |
|
364 |
k = field.id |
|
365 |
v = None |
|
366 |
prefilled = False |
|
367 |
locked = False |
|
368 | ||
369 |
if field.prefill: |
|
370 |
prefill_user = get_request().user |
|
371 |
if get_request().is_in_backoffice(): |
|
372 |
prefill_user = get_publisher().substitutions.get_context_variables( |
|
373 |
).get('form_user') |
|
374 |
v, locked = field.get_prefill_value(user=prefill_user) |
|
375 | ||
376 |
# always set additional attributes as they will be used for |
|
377 |
# "live prefill", regardless of existing data. |
|
378 |
form.get_widget('f%s' % k).prefill_attributes = field.get_prefill_attributes() |
|
379 | ||
380 |
should_prefill = bool(field.prefill) |
|
381 | ||
382 |
if k in data: |
|
383 |
# existing value, update it with the new computed value |
|
384 |
# if it's the same that was previously computed. |
|
385 |
prefill_value = v |
|
386 |
v = data[k] |
|
387 |
if data.get('prefilling_data', {}).get(k) == data[k]: |
|
388 |
# replace value with new value computed for prefill |
|
389 |
v = prefill_value |
|
390 |
else: |
|
391 |
should_prefill = False |
|
392 | 461 | |
393 |
if should_prefill: |
|
394 |
if get_request().is_in_backoffice() and ( |
|
395 |
field.prefill and field.prefill.get('type') == 'geoloc'): |
|
396 |
# turn off prefilling from geolocation attributes if |
|
397 |
# the form is filled from the backoffice |
|
398 |
v = None |
|
399 |
if v: |
|
400 |
prefilled = True |
|
401 |
form.get_widget('f%s' % k).prefilled = True |
|
402 | ||
403 |
if not prefilled and form.get_widget('f%s' % k): |
|
404 |
form.get_widget('f%s' % k).clear_error() |
|
405 | ||
406 |
if v is not None: |
|
407 |
# store computed value, it will be used to compare with |
|
408 |
# submitted value if page is visited again. |
|
409 |
if should_prefill: |
|
410 |
if 'prefilling_data' not in data: |
|
411 |
data['prefilling_data'] = {} |
|
412 |
data['prefilling_data'][k] = v |
|
413 |
if not isinstance(v, str) and field.convert_value_to_str: |
|
414 |
v = field.convert_value_to_str(v) |
|
415 |
form.get_widget('f%s' % k).set_value(v) |
|
416 |
form.get_widget('f%s' % k).transfer_form_value(req) |
|
417 |
if field.type == 'item' and v and form.get_widget('f%s' % k).value != v: |
|
418 |
# mark field as invalid if the value was not accepted |
|
419 |
# (this is required by quixote>=3 as the value would |
|
420 |
# not be evaluated in the initial GET request of the |
|
421 |
# page). |
|
422 |
form.get_widget('f%s' % k).set_error(get_selection_error_text()) |
|
423 |
if locked: |
|
424 |
form.get_widget('f%s' % k).readonly = 'readonly' |
|
425 |
form.get_widget('f%s' % k).attrs['readonly'] = 'readonly' |
|
426 |
had_prefill = True |
|
462 |
had_prefill = self.apply_field_prefills(data, form, displayed_fields) |
|
427 | 463 | |
428 | 464 |
if had_prefill: |
429 | 465 |
# include prefilled data |
... | ... | |
438 | 474 |
else: |
439 | 475 |
# not a page change, reset_locked_data() will have been called |
440 | 476 |
# earlier, we use that to set appropriate fields as readonly. |
441 |
for field in displayed_fields:
|
|
442 |
if get_request().form.get('__locked_f%s' % field.id):
|
|
443 |
form.get_widget('f%s' % field.id).readonly = 'readonly'
|
|
444 |
form.get_widget('f%s' % field.id).attrs['readonly'] = 'readonly'
|
|
477 |
for field, field_key, widget, block in self.iter_with_block_fields(form, displayed_fields):
|
|
478 |
if get_request().form.get('__locked_f%s' % field_key):
|
|
479 |
widget.readonly = 'readonly'
|
|
480 |
widget.attrs['readonly'] = 'readonly'
|
|
445 | 481 | |
446 |
for field in displayed_fields:
|
|
482 |
for field, field_key, widget, block in self.iter_with_block_fields(form, displayed_fields):
|
|
447 | 483 |
if field.prefill: |
448 | 484 |
# always set additional attributes as they will be used for |
449 | 485 |
# "live prefill", regardless of existing data. |
450 |
form.get_widget('f%s' % field.id).prefill_attributes = field.get_prefill_attributes()
|
|
486 |
widget.prefill_attributes = field.get_prefill_attributes()
|
|
451 | 487 | |
452 | 488 |
self.formdef.set_live_condition_sources(form, displayed_fields) |
453 | 489 | |
... | ... | |
455 | 491 |
# pass over prefilled fields that are used as live source of item |
456 | 492 |
# fields |
457 | 493 |
fields_to_update = set() |
458 |
for field in displayed_fields: |
|
459 |
widget = form.get_widget('f%s' % field.id) |
|
494 |
for field, field_key, widget, block in self.iter_with_block_fields(form, displayed_fields): |
|
460 | 495 |
if getattr(widget, 'prefilled', False) and getattr(widget, 'live_condition_source', False): |
461 | 496 |
fields_to_update.update(widget.live_condition_fields) |
462 | 497 |
elif field in fields_to_update and field.type == 'item': |
... | ... | |
857 | 892 |
transient_formdata, force_mode='lazy'): |
858 | 893 |
# reset locked data with newly submitted values, this allows |
859 | 894 |
# for templates referencing fields from the sampe page. |
860 |
self.reset_locked_data() |
|
895 |
self.reset_locked_data(form)
|
|
861 | 896 |
data = self.formdef.get_data(form) |
862 | 897 | |
863 | 898 |
form_data.update(data) |
... | ... | |
969 | 1004 |
else: |
970 | 1005 |
return self.page(self.pages[page_no]) |
971 | 1006 | |
972 |
self.reset_locked_data() |
|
1007 |
self.reset_locked_data(form)
|
|
973 | 1008 |
if step == 1: |
974 | 1009 |
form.add_submit('previous') |
975 | 1010 |
magictoken = form.get_widget('magictoken').parse() |
... | ... | |
1020 | 1055 | |
1021 | 1056 |
return self.submitted(form, existing_formdata) |
1022 | 1057 | |
1023 |
def reset_locked_data(self): |
|
1058 |
def reset_locked_data(self, form):
|
|
1024 | 1059 |
# reset locked fields, making sure the user cannot alter them. |
1025 | 1060 |
prefill_user = get_request().user |
1026 | 1061 |
if get_request().is_in_backoffice(): |
1027 | 1062 |
prefill_user = get_publisher().substitutions.get_context_variables().get('form_user') |
1028 |
for field in self.formdef.fields:
|
|
1063 |
for field, field_key, widget, block in self.iter_with_block_fields(form, self.formdef.fields):
|
|
1029 | 1064 |
if not field.prefill: |
1030 | 1065 |
continue |
1031 |
if not 'f%s' % field.id in get_request().form: |
|
1066 |
post_key = 'f%s' % field_key |
|
1067 |
if block: |
|
1068 |
post_key = 'f%s$element0$f%s' % (block.id, field.id) |
|
1069 |
if post_key not in get_request().form: |
|
1032 | 1070 |
continue |
1033 | 1071 |
v, locked = field.get_prefill_value(user=prefill_user) |
1034 | 1072 |
if locked: |
... | ... | |
1036 | 1074 |
# convert structured data to strings as if they were |
1037 | 1075 |
# submitted by the browser. |
1038 | 1076 |
v = field.convert_value_to_str(v) |
1039 |
get_request().form['f%s' % field.id] = v |
|
1077 |
get_request().form[post_key] = v |
|
1078 |
if widget: |
|
1079 |
widget.set_value(v) |
|
1080 |
if block: |
|
1081 |
# child widget value was changed, mark parent widgets |
|
1082 |
# as unparsed |
|
1083 |
block_widget = form.get_widget('f%s' % block.id) |
|
1084 |
block_widget._parsed = False |
|
1085 |
block_widget.widgets[0]._parsed = False |
|
1086 | ||
1040 | 1087 |
# keep track of locked field, this will be used when |
1041 | 1088 |
# redisplaying the same page in case of errors. |
1042 |
get_request().form['__locked_f%s' % field.id] = True
|
|
1089 |
get_request().form['__locked_f%s' % field_key] = True
|
|
1043 | 1090 | |
1044 | 1091 |
def previous_page(self, page_no, magictoken): |
1045 | 1092 |
session = get_session() |
wcs/wf/form.py | ||
---|---|---|
26 | 26 |
from wcs.formdata import get_dict_with_varnames |
27 | 27 | |
28 | 28 |
from wcs.forms.common import FileDirectory |
29 |
from wcs.forms.root import FormPage |
|
29 | 30 | |
30 | 31 | |
31 | 32 |
def lookup_wf_form_file(self, filename): |
... | ... | |
177 | 178 | |
178 | 179 |
formdata.feed_session() |
179 | 180 | |
180 |
req = get_request() |
|
181 | 181 |
self.formdef.set_live_condition_sources(form, self.formdef.fields) |
182 | 182 | |
183 | 183 |
if form.is_submitted(): |
... | ... | |
188 | 188 |
if displayed_fields is not None: |
189 | 189 |
fields = displayed_fields |
190 | 190 | |
191 |
for field in fields: |
|
192 |
if ('f%s' % field.id) in req.form: |
|
193 |
continue |
|
194 |
if not field.prefill or field.prefill.get('type') == 'none': |
|
195 |
continue |
|
196 |
# FIXME: this code duplicates code from wcs/forms/root.py :/ |
|
197 |
prefill_user = get_request().user |
|
198 |
if get_request().is_in_backoffice(): |
|
199 |
prefill_user = get_publisher().substitutions.get_context_variables( |
|
200 |
).get('form_user') |
|
201 |
v, verified = field.get_prefill_value(user=prefill_user) |
|
202 |
if get_request().is_in_backoffice() and ( |
|
203 |
field.prefill and field.prefill.get('type') == 'geoloc'): |
|
204 |
# turn off prefilling from geolocation attributes if |
|
205 |
# the form is filled from the backoffice |
|
206 |
v = None |
|
207 |
if v: |
|
208 |
form.get_widget('f%s' % field.id).set_value(v) |
|
209 |
req.form['f%s' % field.id] = v |
|
191 |
FormPage.apply_field_prefills({}, form, fields) |
|
210 | 192 | |
211 | 193 |
def evaluate_live_form(self, form, formdata, user): |
212 | 194 |
workflow_data = {} |
213 |
- |