0001-forms-add-option-for-prefilling-fields-as-readonly-3.patch
tests/test_admin_pages.py | ||
---|---|---|
1312 | 1312 |
assert resp.location == 'http://example.net/backoffice/forms/1/fields/#itemId_1' |
1313 | 1313 |
resp = resp.follow() |
1314 | 1314 | |
1315 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': 'test'} |
|
1315 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': 'test', 'readonly': False}
|
|
1316 | 1316 | |
1317 | 1317 |
# do the same with 'data sources' field |
1318 | 1318 |
resp = resp.click('Edit', href='1/') |
... | ... | |
1351 | 1351 |
resp.form['prefill$type'] = 'String / Template' |
1352 | 1352 |
resp.form['prefill$value_string'] = 'test' |
1353 | 1353 |
resp = resp.form.submit('submit').follow() |
1354 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': 'test'} |
|
1354 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': 'test', 'readonly': False}
|
|
1355 | 1355 | |
1356 | 1356 |
resp = app.get('/backoffice/forms/1/fields/1/') |
1357 | 1357 |
resp.form['prefill$type'] = 'Python Expression' |
1358 | 1358 |
resp.form['prefill$value_formula'] = 'True' |
1359 | 1359 |
resp = resp.form.submit('submit').follow() |
1360 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'formula', 'value': 'True'} |
|
1360 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'formula', 'value': 'True', 'readonly': False}
|
|
1361 | 1361 | |
1362 | 1362 |
resp = app.get('/backoffice/forms/1/fields/1/') |
1363 | 1363 |
resp.form['prefill$type'] = 'String / Template' |
1364 | 1364 |
resp.form['prefill$value_string'] = '{{form_var_toto}}' |
1365 | 1365 |
resp = resp.form.submit('submit').follow() |
1366 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': '{{form_var_toto}}'} |
|
1366 |
assert FormDef.get(formdef.id).fields[0].prefill == {'type': 'string', 'value': '{{form_var_toto}}', 'readonly': False}
|
|
1367 | 1367 | |
1368 | 1368 |
# check error handling |
1369 | 1369 |
resp = app.get('/backoffice/forms/1/fields/1/') |
... | ... | |
1757 | 1757 |
resp.form['prefill$value_geolocation'].value = 'Position' |
1758 | 1758 |
resp = resp.form.submit('submit') |
1759 | 1759 |
assert FormDef.get(formdef.id).fields[0].prefill == { |
1760 |
'type': 'geolocation', 'value': 'position'} |
|
1760 |
'type': 'geolocation', 'value': 'position', 'readonly': False}
|
|
1761 | 1761 | |
1762 | 1762 | |
1763 | 1763 |
def test_form_edit_field_warnings(pub): |
tests/test_form_pages.py | ||
---|---|---|
5084 | 5084 |
user.verified_fields = ['email'] |
5085 | 5085 |
user.store() |
5086 | 5086 | |
5087 |
resp = login(get_app(pub), username='foo', password='foo').get('/test/') |
|
5088 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5089 |
assert 'readonly' in resp.form['f0'].attrs |
|
5087 |
for prefill_settings in ( |
|
5088 |
{'type': 'user', 'value': 'email'}, # verified profile |
|
5089 |
{'type': 'string', 'value': 'foo@localhost', 'readonly': True}, # readonly value |
|
5090 |
): |
|
5091 |
formdef.confirmation = True |
|
5092 |
formdef.fields[0].prefill = prefill_settings |
|
5093 |
formdef.store() |
|
5094 |
formdef.data_class().wipe() |
|
5095 |
resp = login(get_app(pub), username='foo', password='foo').get('/test/') |
|
5096 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5097 |
assert 'readonly' in resp.form['f0'].attrs |
|
5090 | 5098 | |
5091 |
resp.form['f0'].value = 'Hello' # try changing the value |
|
5092 |
resp = resp.form.submit('submit') |
|
5093 |
assert 'Check values then click submit.' in resp.text |
|
5094 |
assert resp.form['f0'].value == 'foo@localhost' # it is reverted |
|
5099 |
resp.form['f0'].value = 'Hello' # try changing the value
|
|
5100 |
resp = resp.form.submit('submit')
|
|
5101 |
assert 'Check values then click submit.' in resp.text
|
|
5102 |
assert resp.form['f0'].value == 'foo@localhost' # it is reverted
|
|
5095 | 5103 | |
5096 |
resp.form['f0'].value = 'Hello' # try again changing the value |
|
5097 |
resp = resp.form.submit('submit') |
|
5104 |
resp.form['f0'].value = 'Hello' # try again changing the value
|
|
5105 |
resp = resp.form.submit('submit')
|
|
5098 | 5106 | |
5099 |
formdatas = [x for x in formdef.data_class().select() if not x.is_draft()] |
|
5100 |
assert len(formdatas) == 1 |
|
5101 |
assert formdatas[0].data['0'] == 'foo@localhost' |
|
5107 |
formdatas = [x for x in formdef.data_class().select() if not x.is_draft()]
|
|
5108 |
assert len(formdatas) == 1
|
|
5109 |
assert formdatas[0].data['0'] == 'foo@localhost'
|
|
5102 | 5110 | |
5103 |
resp = login(get_app(pub), username='foo', password='foo').get('/test/') |
|
5104 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5105 |
resp = resp.form.submit('submit') |
|
5106 |
assert 'Check values then click submit.' in resp.text |
|
5107 |
resp.form['f0'].value = 'Hello' # try changing |
|
5108 |
resp = resp.form.submit('previous') |
|
5109 |
assert 'readonly' in resp.form['f0'].attrs |
|
5110 |
assert not 'Check values then click submit.' in resp.text |
|
5111 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5111 |
resp = login(get_app(pub), username='foo', password='foo').get('/test/') |
|
5112 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5113 |
resp = resp.form.submit('submit') |
|
5114 |
assert 'Check values then click submit.' in resp.text |
|
5115 |
resp.form['f0'].value = 'Hello' # try changing |
|
5116 |
resp = resp.form.submit('previous') |
|
5117 |
assert 'readonly' in resp.form['f0'].attrs |
|
5118 |
assert not 'Check values then click submit.' in resp.text |
|
5119 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5120 | ||
5121 |
# try it without validation page |
|
5122 |
formdef.confirmation = False |
|
5123 |
formdef.store() |
|
5124 |
formdef.data_class().wipe() |
|
5125 | ||
5126 |
resp = login(get_app(pub), username='foo', password='foo').get('/test/') |
|
5127 |
assert resp.form['f0'].value == 'foo@localhost' |
|
5128 |
assert 'readonly' in resp.form['f0'].attrs |
|
5129 | ||
5130 |
resp.form['f0'].value = 'Hello' # try changing the value |
|
5131 |
resp = resp.form.submit('submit') |
|
5132 | ||
5133 |
formdatas = [x for x in formdef.data_class().select() if not x.is_draft()] |
|
5134 |
assert len(formdatas) == 1 |
|
5135 |
assert formdatas[0].data['0'] == 'foo@localhost' |
|
5112 | 5136 | |
5113 | 5137 | |
5114 | 5138 |
def test_form_page_profile_verified_date_prefill(pub): |
... | ... | |
7339 | 7363 |
resp.forms[0]['f1'] = '2' |
7340 | 7364 |
resp = resp.forms[0].submit('submit') |
7341 | 7365 |
assert 'style="display: none"' in comment.search(resp.forms[0].text).group(0) |
7366 | ||
7367 | ||
7368 |
def test_field_live_readonly_prefilled_field(pub, http_requests): |
|
7369 |
FormDef.wipe() |
|
7370 |
formdef = FormDef() |
|
7371 |
formdef.name = 'Foo' |
|
7372 |
formdef.fields = [ |
|
7373 |
fields.StringField(type='string', id='1', label='Bar', size='40', |
|
7374 |
required=True, varname='bar'), |
|
7375 |
fields.StringField(type='string', id='2', label='readonly', size='40', |
|
7376 |
required=True, |
|
7377 |
prefill={'type': 'string', 'value': 'bla {{form_var_bar}} bla', 'readonly': True}), |
|
7378 |
] |
|
7379 |
formdef.store() |
|
7380 |
formdef.data_class().wipe() |
|
7381 | ||
7382 |
app = get_app(pub) |
|
7383 |
resp = app.get('/foo/') |
|
7384 |
assert 'f1' in resp.form.fields |
|
7385 |
assert resp.html.find('div', {'data-field-id': '1'}).attrs['data-live-source'] == 'true' |
|
7386 |
resp.form['f1'] = 'hello' |
|
7387 |
live_resp = app.post('/foo/live', params=resp.form.submit_fields()) |
|
7388 |
assert live_resp.json['result']['2']['content'] == 'bla hello bla' |
|
7389 |
resp.form['f1'] = 'toto' |
|
7390 |
live_resp = app.post('/foo/live?modified_field_id=1', params=resp.form.submit_fields()) |
|
7391 |
assert live_resp.json['result']['2']['content'] == 'bla toto bla' |
tests/test_formdef_import.py | ||
---|---|---|
593 | 593 |
formdef.digest_template = '{{form_number}}' |
594 | 594 |
f2 = assert_xml_import_export_works(formdef) |
595 | 595 |
assert f2.digest_template == formdef.digest_template |
596 | ||
597 | ||
598 |
def test_field_prefill(): |
|
599 |
formdef = FormDef() |
|
600 |
formdef.name = 'Foo' |
|
601 |
formdef.fields = [ |
|
602 |
fields.StringField(type='string', id=1, label='Bar', size='40', |
|
603 |
prefill={'type': 'string', 'value': 'plop'}) |
|
604 |
] |
|
605 |
f2 = assert_xml_import_export_works(formdef) |
|
606 |
assert len(f2.fields) == len(formdef.fields) |
|
607 |
assert f2.fields[0].prefill == {'type': 'string', 'value': 'plop'} |
|
608 | ||
609 |
formdef.fields = [ |
|
610 |
fields.StringField(type='string', id=1, label='Bar', size='40', |
|
611 |
prefill={'type': 'string', 'value': 'plop', 'readonly': True}) |
|
612 |
] |
|
613 |
f2 = assert_xml_import_export_works(formdef) |
|
614 |
assert len(f2.fields) == len(formdef.fields) |
|
615 |
assert f2.fields[0].prefill == {'type': 'string', 'value': 'plop', 'readonly': True} |
|
616 | ||
617 |
formdef.fields = [ |
|
618 |
fields.StringField(type='string', id=1, label='Bar', size='40', |
|
619 |
prefill={'type': 'string', 'value': 'plop', 'readonly': False}) |
|
620 |
] |
|
621 |
formdef_xml = formdef.export_to_xml() |
|
622 |
f2 = FormDef.import_from_xml_tree(formdef_xml) |
|
623 |
assert len(f2.fields) == len(formdef.fields) |
|
624 |
assert f2.fields[0].prefill == {'type': 'string', 'value': 'plop'} |
wcs/fields.py | ||
---|---|---|
122 | 122 |
attrs={'data-dynamic-display-child-of': 'prefill$type', |
123 | 123 |
'data-dynamic-display-value': prefill_types.get('geolocation')}) |
124 | 124 | |
125 |
# exclude geolocation from readonly prefill as the data necessarily |
|
126 |
# comes from the user device. |
|
127 |
self.add(CheckboxWidget, |
|
128 |
'readonly', |
|
129 |
value=value.get('readonly'), |
|
130 |
attrs={'data-dynamic-display-child-of': 'prefill$type', |
|
131 |
'data-dynamic-display-value-in': '|'.join( |
|
132 |
[x[1] for x in options if x[0] not in ('none', 'geolocation')]), |
|
133 |
'inline_title': _('Readonly'), |
|
134 |
} |
|
135 |
) |
|
136 | ||
125 | 137 |
self._parsed = False |
126 | 138 | |
127 | 139 | |
... | ... | |
130 | 142 |
type_ = self.get('type') |
131 | 143 |
if type_: |
132 | 144 |
values['type'] = type_ |
145 |
values['readonly'] = self.get('readonly') |
|
133 | 146 |
value = self.get('value_%s' % type_) |
134 | 147 |
if value: |
135 | 148 |
values['value'] = value |
... | ... | |
310 | 323 |
elif node.text: |
311 | 324 |
self.condition = {'type': 'python', 'value': force_str(node.text).strip()} |
312 | 325 | |
326 |
def prefill_init_with_xml(self, node, charset, include_id=False): |
|
327 |
self.prefill = {} |
|
328 |
if node is not None and node.findall('type'): |
|
329 |
self.prefill = { |
|
330 |
'type': force_str(node.find('type').text), |
|
331 |
} |
|
332 |
if self.prefill['type'] and self.prefill['type'] != 'none': |
|
333 |
self.prefill['value'] = force_str(node.find('value').text) |
|
334 |
if node.find('readonly') is not None and force_str(node.find('readonly').text) == 'True': |
|
335 |
self.prefill['readonly'] = True |
|
336 | ||
313 | 337 |
def get_rst_view_value(self, value, indent=''): |
314 | 338 |
return indent + self.get_view_value(value) |
315 | 339 | |
... | ... | |
327 | 351 |
def get_prefill_value(self, user=None, force_string=True): |
328 | 352 |
# returns a tuple with two items, |
329 | 353 |
# 1. value[str], the value that will be used to prefill |
330 |
# 2. verified[bool], a flag to know if this is a "verified" value |
|
331 |
# (that will therefore be marked as readonly etc.) |
|
354 |
# 2. readonly[bool], a flag to know if this is a readonly value |
|
355 |
# (because it has been explicitely marked so or because it |
|
356 |
# comes from verified identity data). |
|
332 | 357 |
t = self.prefill.get('type') |
358 |
explicit_readonly = bool(self.prefill.get('readonly')) |
|
333 | 359 |
if t == 'string': |
334 | 360 |
value = self.prefill.get('value') |
335 | 361 |
if not Template.is_template_string(value): |
336 |
return (value, False)
|
|
362 |
return (value, explicit_readonly)
|
|
337 | 363 | |
338 | 364 |
context = get_publisher().substitutions.get_context_variables() |
339 | 365 |
try: |
340 |
return (Template(value, autoescape=False, raises=True).render(context), False)
|
|
366 |
return (Template(value, autoescape=False, raises=True).render(context), explicit_readonly)
|
|
341 | 367 |
except TemplateError: |
342 | 368 |
return (None, False) |
343 | 369 | |
344 | 370 |
elif t == 'user' and user: |
345 | 371 |
x = self.prefill.get('value') |
346 | 372 |
if x == 'email': |
347 |
return (user.email, 'email' in (user.verified_fields or [])) |
|
373 |
return (user.email, explicit_readonly or 'email' in (user.verified_fields or []))
|
|
348 | 374 |
elif user.form_data: |
349 | 375 |
userform = user.get_formdef() |
350 | 376 |
for userfield in userform.fields: |
351 | 377 |
if userfield.id == x: |
352 | 378 |
return (user.form_data.get(x), |
353 |
str(userfield.id) in (user.verified_fields or [])) |
|
379 |
explicit_readonly or str(userfield.id) in (user.verified_fields or []))
|
|
354 | 380 | |
355 | 381 |
elif t == 'formula': |
356 | 382 |
formula = self.prefill.get('value') |
... | ... | |
369 | 395 |
# (items field are prefilled with list of strings, and |
370 | 396 |
# will get the native python object) |
371 | 397 |
ret = str(ret) |
372 |
return (ret, False)
|
|
398 |
return (ret, explicit_readonly)
|
|
373 | 399 |
except: |
374 | 400 |
pass |
375 | 401 |
wcs/formdef.py | ||
---|---|---|
684 | 684 |
if not varname in live_condition_fields: |
685 | 685 |
live_condition_fields[varname] = [] |
686 | 686 |
live_condition_fields[varname].append(field) |
687 |
if field.prefill and field.prefill.get('readonly') and field.prefill.get('type') == 'string': |
|
688 |
for varname in field.get_referenced_varnames(formdef=self, value=field.prefill.get('value', '')): |
|
689 |
if varname not in live_condition_fields: |
|
690 |
live_condition_fields[varname] = [] |
|
691 |
live_condition_fields[varname].append(field) |
|
687 | 692 |
if field.key == 'comment': |
688 | 693 |
for varname in field.get_referenced_varnames(formdef=self, value=field.label): |
689 | 694 |
if not varname in live_condition_fields: |
wcs/forms/common.py | ||
---|---|---|
674 | 674 |
continue |
675 | 675 |
if widget.field.key == 'comment': |
676 | 676 |
result[widget.field.id]['content'] = widget.content |
677 |
elif widget.field.prefill and widget.field.prefill.get('readonly') and widget.field.prefill.get('type') == 'string': |
|
678 |
value, verified = widget.field.get_prefill_value() |
|
679 |
result[widget.field.id]['content'] = value |
|
677 | 680 | |
678 | 681 |
return json.dumps({'result': result}) |
679 | 682 |
wcs/forms/root.py | ||
---|---|---|
733 | 733 |
except (TypeError, ValueError): |
734 | 734 |
step = 0 |
735 | 735 | |
736 |
# reset verified fields, making sure the user cannot alter them. |
|
737 |
prefill_user = get_request().user |
|
738 |
if get_request().is_in_backoffice(): |
|
739 |
prefill_user = get_publisher().substitutions.get_context_variables().get('form_user') |
|
740 |
if prefill_user: |
|
741 |
for field in self.formdef.fields: |
|
742 |
if not field.prefill: |
|
743 |
continue |
|
744 |
if not 'f%s' % field.id in get_request().form: |
|
745 |
continue |
|
746 |
v, verified = field.get_prefill_value(user=prefill_user) |
|
747 |
if verified: |
|
748 |
if not isinstance(v, six.string_types) and field.convert_value_to_str: |
|
749 |
# convert structured data to strings as if they were |
|
750 |
# submitted by the browser. |
|
751 |
v = field.convert_value_to_str(v) |
|
752 |
get_request().form['f%s' % field.id] = v |
|
753 | ||
754 | 736 |
if step == 0: |
755 | 737 |
try: |
756 | 738 |
page_no = int(form.get_widget('page').parse()) |
... | ... | |
789 | 771 |
form_data = session.get_by_magictoken(magictoken, {}) |
790 | 772 |
with get_publisher().substitutions.temporary_feed( |
791 | 773 |
transient_formdata, force_mode='lazy'): |
774 |
# reset readonly data with newly submitted values, this allows |
|
775 |
# for templates referencing fields from the sampe page. |
|
776 |
self.reset_readonly_data() |
|
792 | 777 |
data = self.formdef.get_data(form) |
778 | ||
793 | 779 |
form_data.update(data) |
794 | 780 | |
795 | 781 |
if self.has_draft_support() and form.get_submit() == 'savedraft': |
... | ... | |
892 | 878 |
else: |
893 | 879 |
return self.page(self.pages[page_no]) |
894 | 880 | |
881 |
self.reset_readonly_data() |
|
895 | 882 |
if step == 1: |
896 | 883 |
form.add_submit('previous') |
897 | 884 |
magictoken = form.get_widget('magictoken').parse() |
... | ... | |
942 | 929 | |
943 | 930 |
return self.submitted(form, existing_formdata) |
944 | 931 | |
932 |
def reset_readonly_data(self): |
|
933 |
# reset readonly fields, making sure the user cannot alter them. |
|
934 |
prefill_user = get_request().user |
|
935 |
if get_request().is_in_backoffice(): |
|
936 |
prefill_user = get_publisher().substitutions.get_context_variables().get('form_user') |
|
937 |
for field in self.formdef.fields: |
|
938 |
if not field.prefill: |
|
939 |
continue |
|
940 |
if not 'f%s' % field.id in get_request().form: |
|
941 |
continue |
|
942 |
v, verified = field.get_prefill_value(user=prefill_user) |
|
943 |
if verified: |
|
944 |
if not isinstance(v, six.string_types) and field.convert_value_to_str: |
|
945 |
# convert structured data to strings as if they were |
|
946 |
# submitted by the browser. |
|
947 |
v = field.convert_value_to_str(v) |
|
948 |
get_request().form['f%s' % field.id] = v |
|
945 | 949 | |
946 | 950 |
def previous_page(self, page_no, magictoken): |
947 | 951 |
session = get_session() |
wcs/qommon/form.py | ||
---|---|---|
230 | 230 |
attrs = {'id': 'form_' + self.name} |
231 | 231 |
if self.required: |
232 | 232 |
attrs['aria-required'] = 'true' |
233 |
inline_title = self.attrs.pop('inline_title', '') |
|
233 | 234 |
if self.attrs: |
234 | 235 |
attrs.update(self.attrs) |
235 | 236 |
checkbox = htmltag("input", xml_end=True, type="checkbox", name=self.name, |
236 | 237 |
value="yes", checked=self.value and "checked" or None, |
237 | 238 |
**attrs) |
238 | 239 |
if standalone: |
239 |
return htmltext('<label>%s<span></span></label>' % checkbox) # for custom style |
|
240 |
data_attrs = ' '.join('%s="%s"' % x for x in attrs.items() if x[0].startswith('data-')) |
|
241 |
# more elaborate markup so standalone checkboxes can be applied a |
|
242 |
# custom style. |
|
243 |
return htmltext('<label %s>%s<span>' % (data_attrs, checkbox)) + inline_title + htmltext('</span></label>') |
|
240 | 244 |
return checkbox |
241 | 245 |
CheckboxWidget.render_content = checkbox_render_content |
242 | 246 |
wcs/qommon/static/js/qommon.forms.js | ||
---|---|---|
114 | 114 |
} |
115 | 115 |
} |
116 | 116 |
if (value.content) { |
117 |
// replace comment content |
|
118 | 117 |
var $widget = $('[data-field-id="' + key + '"]'); |
119 |
$widget.html(value.content); |
|
118 |
if ($widget.hasClass('comment-field')) { |
|
119 |
// replace comment content |
|
120 |
$widget.html(value.content); |
|
121 |
} else { |
|
122 |
// replace text input value |
|
123 |
$widget.find('input, textarea').val(value.content); |
|
124 |
} |
|
120 | 125 |
} |
121 | 126 |
if (value.source_url) { |
122 | 127 |
// json change of URL |
123 |
- |