0001-forms-add-post-conditions-to-page-fields-8962.patch
tests/test_admin_pages.py | ||
---|---|---|
917 | 917 |
assert resp.location == 'http://example.net/backoffice/forms/1/fields/' |
918 | 918 |
assert FormDef.get(1).fields[0].items == ['XXX'] |
919 | 919 | |
920 |
def test_form_edit_page_field(pub): |
|
921 |
create_superuser(pub) |
|
922 |
create_role() |
|
923 | ||
924 |
FormDef.wipe() |
|
925 |
formdef = FormDef() |
|
926 |
formdef.name = 'form title' |
|
927 |
formdef.fields = [] |
|
928 |
formdef.store() |
|
929 | ||
930 |
app = login(get_app(pub)) |
|
931 |
resp = app.get('/backoffice/forms/1/') |
|
932 |
resp = resp.click(href='fields/') |
|
933 |
assert 'There are not yet any fields for this form' in resp.body |
|
934 | ||
935 |
resp.forms[0]['label'] = 'foobar' |
|
936 |
resp.forms[0]['type'] = 'New Page' |
|
937 |
resp = resp.forms[0].submit() |
|
938 |
assert resp.location == 'http://example.net/backoffice/forms/1/fields/' |
|
939 |
resp = resp.follow() |
|
940 |
assert 'foobar' in resp.body |
|
941 |
assert 'Use drag and drop to reorder fields.' in resp.body |
|
942 | ||
943 |
assert len(FormDef.get(1).fields) == 1 |
|
944 |
assert FormDef.get(1).fields[0].key == 'page' |
|
945 |
assert FormDef.get(1).fields[0].label == 'foobar' |
|
946 | ||
947 |
resp = resp.click('Edit', href='1/') |
|
948 |
resp.form['post_conditions$element0$condition'] = 'foo' |
|
949 |
resp.form['post_conditions$element0$error_message'] = 'bar' |
|
950 |
resp = resp.form.submit('post_conditions$add_element') |
|
951 |
resp.form['post_conditions$element1$condition'] = 'foo2' |
|
952 |
resp.form['post_conditions$element1$error_message'] = 'bar2' |
|
953 |
resp = resp.form.submit('submit') |
|
954 | ||
955 |
assert FormDef.get(1).fields[0].post_conditions == [ |
|
956 |
{'condition': 'foo', 'error_message': 'bar'}, |
|
957 |
{'condition': 'foo2', 'error_message': 'bar2'}, |
|
958 |
] |
|
959 | ||
920 | 960 |
def test_form_legacy_int_id(pub): |
921 | 961 |
create_superuser(pub) |
922 | 962 |
create_role() |
tests/test_form_pages.py | ||
---|---|---|
522 | 522 |
resp = resp.forms[0].submit('submit') # should go to second page |
523 | 523 |
assert 'f3' in resp.forms[0].fields |
524 | 524 | |
525 |
def test_form_multi_page_post_conditions(pub): |
|
526 |
formdef = create_formdef() |
|
527 |
formdef.fields = [fields.PageField(id='0', label='1st page', type='page'), |
|
528 |
fields.StringField(id='1', label='string', varname='foo'), |
|
529 |
fields.PageField(id='2', label='2nd page', type='page', condition='False'), |
|
530 |
fields.StringField(id='3', label='string 2'), |
|
531 |
fields.PageField(id='4', label='3rd page', type='page'), |
|
532 |
fields.StringField(id='5', label='string 3'), |
|
533 |
] |
|
534 | ||
535 |
formdef.store() |
|
536 |
formdef.data_class().wipe() |
|
537 |
resp = get_app(pub).get('/test/') |
|
538 |
resp.forms[0]['f1'] = 'foo' |
|
539 |
resp = resp.forms[0].submit('submit') |
|
540 |
resp.forms[0]['f5'] = 'bar' |
|
541 |
resp = resp.forms[0].submit('submit') |
|
542 |
assert 'Check values then click submit.' in resp.body |
|
543 |
resp = resp.forms[0].submit('submit') |
|
544 |
assert formdef.data_class().count() == 1 |
|
545 |
assert formdef.data_class().select()[0].data['1'] == 'foo' |
|
546 |
assert formdef.data_class().select()[0].data['5'] == 'bar' |
|
547 | ||
548 |
formdef.fields[4].post_conditions = [ |
|
549 |
{'condition': 'False', 'error_message': 'You shall not pass.'}, |
|
550 |
] |
|
551 |
formdef.store() |
|
552 |
formdef.data_class().wipe() |
|
553 |
resp = get_app(pub).get('/test/') |
|
554 |
resp.forms[0]['f1'] = 'foo' |
|
555 |
resp = resp.forms[0].submit('submit') |
|
556 |
resp.forms[0]['f5'] = 'bar' |
|
557 |
resp = resp.forms[0].submit('submit') |
|
558 |
assert 'errornotice' in resp.body |
|
559 |
assert 'You shall not pass.' in resp.body |
|
560 | ||
561 |
formdef.fields[4].post_conditions = [ |
|
562 |
{'condition': 'form_var_foo == "foo"', 'error_message': 'You shall not pass.'}, |
|
563 |
] |
|
564 |
formdef.store() |
|
565 |
formdef.data_class().wipe() |
|
566 |
resp = get_app(pub).get('/test/') |
|
567 |
resp.forms[0]['f1'] = 'bar' |
|
568 |
resp = resp.forms[0].submit('submit') |
|
569 |
resp.forms[0]['f5'] = 'bar' |
|
570 |
resp = resp.forms[0].submit('submit') |
|
571 |
assert 'errornotice' in resp.body |
|
572 |
assert 'You shall not pass.' in resp.body |
|
573 | ||
574 |
resp = get_app(pub).get('/test/') |
|
575 |
resp.forms[0]['f1'] = 'foo' |
|
576 |
resp = resp.forms[0].submit('submit') |
|
577 |
resp.forms[0]['f5'] = 'bar' |
|
578 |
resp = resp.forms[0].submit('submit') |
|
579 |
assert 'Check values then click submit.' in resp.body |
|
580 | ||
581 |
# check a post-condition raising an exception, they should always fail. |
|
582 |
formdef.fields[4].post_conditions = [ |
|
583 |
{'condition': '1/0', 'error_message': 'You shall not pass.'}, |
|
584 |
] |
|
585 |
formdef.store() |
|
586 |
formdef.data_class().wipe() |
|
587 |
resp = get_app(pub).get('/test/') |
|
588 |
resp.forms[0]['f1'] = 'bar' |
|
589 |
resp = resp.forms[0].submit('submit') |
|
590 |
resp.forms[0]['f5'] = 'bar' |
|
591 |
resp = resp.forms[0].submit('submit') |
|
592 |
assert 'errornotice' in resp.body |
|
593 |
assert 'You shall not pass.' in resp.body |
|
594 | ||
525 | 595 |
def test_form_submit_with_user(pub): |
526 | 596 |
create_user(pub) |
527 | 597 |
formdef = create_formdef() |
tests/test_formdef_import.py | ||
---|---|---|
282 | 282 | |
283 | 283 |
formdef2 = FormDef.import_from_xml(StringIO.StringIO(export), include_id=True) |
284 | 284 |
assert formdef2.max_field_id == 2 |
285 | ||
286 |
def test_page_post_conditions(): |
|
287 |
formdef = FormDef() |
|
288 |
formdef.name = 'foo' |
|
289 |
formdef.fields = [ |
|
290 |
fields.PageField(id='1', type='page', |
|
291 |
post_conditions=[{'condition': 'blah', 'error_message': 'bar'}]), |
|
292 |
] |
|
293 |
fd2 = assert_xml_import_export_works(formdef) |
|
294 |
assert fd2.fields[0].type == 'page' |
|
295 |
assert fd2.fields[0].post_conditions == formdef.fields[0].post_conditions |
wcs/fields.py | ||
---|---|---|
181 | 181 |
else: |
182 | 182 |
extra_fields = [] |
183 | 183 |
for attribute in self.get_admin_attributes() + extra_fields: |
184 |
if hasattr(self, '%s_export_to_xml' % attribute): |
|
185 |
getattr(self, '%s_export_to_xml' % attribute)(field, charset, |
|
186 |
include_id=include_id) |
|
187 |
continue |
|
184 | 188 |
if hasattr(self, attribute) and getattr(self, attribute) is not None: |
185 | 189 |
val = getattr(self, attribute) |
186 | 190 |
if type(val) is dict and not val: |
... | ... | |
214 | 218 |
def init_with_xml(self, elem, charset, include_id=False): |
215 | 219 |
for attribute in self.get_admin_attributes(): |
216 | 220 |
el = elem.find(attribute) |
221 |
if hasattr(self, '%s_init_with_xml' % attribute): |
|
222 |
getattr(self, '%s_init_with_xml' % attribute)(el, charset, |
|
223 |
include_id=include_id) |
|
224 |
continue |
|
217 | 225 |
if el is None: |
218 | 226 |
continue |
219 | 227 |
if el.getchildren(): |
... | ... | |
1257 | 1265 |
register_field_class(ItemsField) |
1258 | 1266 | |
1259 | 1267 | |
1268 |
class PostConditionsRowWidget(CompositeWidget): |
|
1269 |
def __init__(self, name, value=None, **kwargs): |
|
1270 |
CompositeWidget.__init__(self, name, value, **kwargs) |
|
1271 |
if not value: |
|
1272 |
value = {} |
|
1273 |
self.add(StringWidget, name='condition', title=_('Condition'), |
|
1274 |
value=value.get('condition'), size=50) |
|
1275 |
self.add(StringWidget, name='error_message', title=_('Error Message'), |
|
1276 |
value=value.get('error_message'), size=50) |
|
1277 | ||
1278 |
def _parse(self, request): |
|
1279 |
if self.get('condition') or self.get('error_message'): |
|
1280 |
self.value = { |
|
1281 |
'condition': self.get('condition'), |
|
1282 |
'error_message': self.get('error_message') |
|
1283 |
} |
|
1284 |
else: |
|
1285 |
self.value = None |
|
1286 | ||
1287 | ||
1288 |
class PostConditionsTableWidget(WidgetListAsTable): |
|
1289 |
readonly = False |
|
1290 |
def __init__(self, name, **kwargs): |
|
1291 |
super(PostConditionsTableWidget, self).__init__(name, |
|
1292 |
element_type=PostConditionsRowWidget, **kwargs) |
|
1293 | ||
1294 | ||
1260 | 1295 |
class PageField(Field): |
1261 | 1296 |
key = 'page' |
1262 | 1297 |
description = N_('New Page') |
1263 | 1298 | |
1264 | 1299 |
condition = None |
1300 |
post_conditions = None |
|
1301 | ||
1302 |
def post_conditions_init_with_xml(self, node, charset, include_id=False): |
|
1303 |
if node is None: |
|
1304 |
return |
|
1305 |
self.post_conditions = [] |
|
1306 |
for post_condition_node in node.findall('post_condition'): |
|
1307 |
self.post_conditions.append({ |
|
1308 |
'condition': post_condition_node.find('condition').text.encode(charset), |
|
1309 |
'error_message': post_condition_node.find('error_message').text.encode(charset), |
|
1310 |
}) |
|
1311 | ||
1312 |
def post_conditions_export_to_xml(self, node, charset, include_id=False): |
|
1313 |
if not self.post_conditions: |
|
1314 |
return |
|
1315 | ||
1316 |
conditions_node = ET.SubElement(node, 'post_conditions') |
|
1317 |
for post_condition in self.post_conditions: |
|
1318 |
condition_node = ET.SubElement(conditions_node, 'post_condition') |
|
1319 |
ET.SubElement(condition_node, 'condition').text = unicode( |
|
1320 |
post_condition['condition'], charset, 'replace') |
|
1321 |
ET.SubElement(condition_node, 'error_message').text = unicode( |
|
1322 |
post_condition['error_message'], charset, 'replace') |
|
1265 | 1323 | |
1266 | 1324 |
def fill_admin_form(self, form): |
1267 | 1325 |
form.add(StringWidget, 'label', title = _('Label'), value = self.label, |
1268 | 1326 |
required = True, size = 50) |
1269 | 1327 |
form.add(StringWidget, 'condition', title = _('Condition'), value = self.condition, |
1270 | 1328 |
required = False, size = 50) |
1329 |
form.add(PostConditionsTableWidget, 'post_conditions', |
|
1330 |
title=_('Post Conditions'), |
|
1331 |
value=self.post_conditions, |
|
1332 |
advanced=not(self.post_conditions)) |
|
1271 | 1333 | |
1272 | 1334 |
def get_admin_attributes(self): |
1273 |
return Field.get_admin_attributes(self) + ['condition'] |
|
1335 |
return Field.get_admin_attributes(self) + ['condition', 'post_conditions']
|
|
1274 | 1336 | |
1275 | 1337 |
def add_to_view_form(self, *args): |
1276 | 1338 |
pass |
1277 | 1339 | |
1278 |
def is_visible(self, dict, formdef): |
|
1279 |
if dict is None: |
|
1280 |
return True |
|
1281 | ||
1282 |
if not self.condition: |
|
1340 |
def evaluate_condition(self, dict, formdef, condition): |
|
1341 |
if not condition: |
|
1283 | 1342 |
return True |
1284 | 1343 | |
1285 | 1344 |
# create variables with values currently being evaluated, not yet |
... | ... | |
1301 | 1360 |
data = get_publisher().substitutions.get_context_variables() |
1302 | 1361 | |
1303 | 1362 |
try: |
1304 |
if eval(self.condition, get_publisher().get_global_eval_dict(), data):
|
|
1363 |
if eval(condition, get_publisher().get_global_eval_dict(), data): |
|
1305 | 1364 |
return True |
1306 | 1365 |
except Exception, e: |
1307 | 1366 |
get_logger().warn('failed to evaluate condition "%s" (%r)' % (self.condition, e)) |
1308 |
return True
|
|
1367 |
raise RuntimeError()
|
|
1309 | 1368 | |
1310 | 1369 |
return False |
1311 | 1370 | |
1371 |
def is_visible(self, dict, formdef): |
|
1372 |
if dict is None: |
|
1373 |
return True |
|
1374 |
try: |
|
1375 |
return self.evaluate_condition(dict, formdef, self.condition) |
|
1376 |
except RuntimeError: |
|
1377 |
return True |
|
1378 | ||
1312 | 1379 |
register_field_class(PageField) |
1313 | 1380 | |
1314 | 1381 |
wcs/forms/root.py | ||
---|---|---|
330 | 330 | |
331 | 331 |
return self.page(0) |
332 | 332 | |
333 |
def page(self, page_no, page_change=True, log_detail=None): |
|
333 |
def page(self, page_no, page_change=True, log_detail=None, page_error_messages=None):
|
|
334 | 334 |
r = TemplateIO(html=True) |
335 | 335 |
displayed_fields = [] |
336 | 336 | |
... | ... | |
345 | 345 |
self.feed_current_data(magictoken) |
346 | 346 | |
347 | 347 |
form = self.formdef.create_form(page_no, displayed_fields) |
348 |
if page_error_messages: |
|
349 |
form.add_global_errors(page_error_messages) |
|
348 | 350 |
if getattr(session, 'ajax_form_token', None): |
349 | 351 |
form.add_hidden('_ajax_form_token', session.ajax_form_token) |
350 | 352 |
if get_request().is_in_backoffice(): |
... | ... | |
661 | 663 |
filled = self.save_draft(form_data, page_no) |
662 | 664 |
return redirect(filled.get_url().rstrip('/')) |
663 | 665 | |
666 |
page_error_messages = [] |
|
667 |
if form.get_submit() == 'submit': |
|
668 |
pages = [x for x in self.formdef.fields if x.type == 'page'] |
|
669 |
try: |
|
670 |
page = pages[page_no] |
|
671 |
post_conditions = page.post_conditions or [] |
|
672 |
except IndexError: |
|
673 |
post_conditions = [] |
|
674 |
form_data = session.get_by_magictoken(magictoken, {}) |
|
675 |
for i, post_condition in enumerate(post_conditions): |
|
676 |
condition = post_condition.get('condition') |
|
677 |
error_message = post_condition.get('error_message') |
|
678 |
errored = False |
|
679 |
try: |
|
680 |
if not page.evaluate_condition(form_data, self.formdef, condition): |
|
681 |
errored = True |
|
682 |
except RuntimeError: |
|
683 |
errored = True |
|
684 |
if errored: |
|
685 |
form.add(HiddenErrorWidget, 'post_condition%d' % i) |
|
686 |
form.set_error('post_condition%d' % i, 'error') |
|
687 |
page_error_messages.append(error_message) |
|
688 | ||
664 | 689 |
# form.get_submit() returns the name of the clicked button, and |
665 | 690 |
# it will return True if the form has been submitted, but not |
666 | 691 |
# by clicking on a submit widget; for example if an "add row" |
... | ... | |
671 | 696 |
# page hidden field had an error in its submission), in |
672 | 697 |
# that case we just fall back to the first page. |
673 | 698 |
page_no = 0 |
674 |
return self.page(page_no, page_change=False) |
|
699 |
return self.page(page_no, page_change=False, |
|
700 |
page_error_messages=page_error_messages) |
|
675 | 701 | |
676 | 702 |
form_data = session.get_by_magictoken(magictoken, {}) |
677 | 703 |
data = self.formdef.get_data(form) |
wcs/qommon/form.py | ||
---|---|---|
261 | 261 |
if kwargs.get('advanced_label'): |
262 | 262 |
self.advanced_label = kwargs.pop('advanced_label') |
263 | 263 |
QuixoteForm.__init__(self, *args, **kwargs) |
264 |
self.global_error_messages = None |
|
264 | 265 | |
265 | 266 |
def keep_referer(self): |
266 | 267 |
self.add(HiddenWidget, '__keep_referer', |
... | ... | |
297 | 298 |
l.append(self.captcha) |
298 | 299 |
return l |
299 | 300 | |
301 |
def add_global_errors(self, error_messages): |
|
302 |
self.global_error_messages = error_messages |
|
303 | ||
300 | 304 |
def _get_default_action(self): |
301 | 305 |
if get_request().get_header('x-popup') == 'true': |
302 | 306 |
# do not leave action empty for popups, as they get embedded into |
... | ... | |
331 | 335 |
return htmltext('<div class="infonotice">%s</div>' % _(self.info)) |
332 | 336 | |
333 | 337 |
def _render_error_notice(self): |
334 |
return htmltext('<div class="errornotice">%s</div>' % _( |
|
335 |
QuixoteForm._render_error_notice(self))) |
|
338 |
errors = [] |
|
339 |
if self.has_errors(): |
|
340 |
errors.append(_(QuixoteForm._render_error_notice(self))) |
|
341 |
if self.global_error_messages: |
|
342 |
errors.extend(self.global_error_messages) |
|
343 |
t = TemplateIO(html=True) |
|
344 |
t += htmltext('<div class="errornotice">') |
|
345 |
for error in errors: |
|
346 |
t += htmltext('<p>%s</p>') % error |
|
347 |
t += htmltext('</div>') |
|
348 |
return t.getvalue() |
|
336 | 349 | |
337 | 350 |
def _render_body(self): |
338 | 351 |
r = TemplateIO(html=True) |
339 |
if self.has_errors(): |
|
352 |
if self.has_errors() or self.global_error_messages:
|
|
340 | 353 |
r += self._render_error_notice() |
341 | 354 |
if self.info: |
342 | 355 |
r += self._render_info_notice() |
... | ... | |
2086 | 2099 |
def _parse(self, request): |
2087 | 2100 |
CompositeWidget._parse(self, request) |
2088 | 2101 |
self.value = self.get('latlng') |
2102 | ||
2103 | ||
2104 |
class HiddenErrorWidget(HiddenWidget): |
|
2105 |
def set_error(self, error): |
|
2106 |
Widget.set_error(self, error) |
|
2089 |
- |