Projet

Général

Profil

0001-forms-add-post-conditions-to-page-fields-8962.patch

Frédéric Péters, 04 février 2016 16:47

Télécharger (17,6 ko)

Voir les différences:

Subject: [PATCH] forms: add post-conditions to page fields (#8962)

 tests/test_admin_pages.py    | 40 +++++++++++++++++++
 tests/test_form_pages.py     | 91 ++++++++++++++++++++++++++++++++++++++++++++
 tests/test_formdef_import.py | 11 ++++++
 wcs/fields.py                | 83 ++++++++++++++++++++++++++++++++++++----
 wcs/forms/root.py            | 32 +++++++++++++++-
 wcs/qommon/form.py           | 24 ++++++++++--
 6 files changed, 268 insertions(+), 13 deletions(-)
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
608 608
    resp = resp.forms[0].submit('submit') # should go to second page
609 609
    assert 'f3' in resp.forms[0].fields
610 610

  
611
def test_form_multi_page_post_conditions(pub):
612
    formdef = create_formdef()
613
    formdef.fields = [fields.PageField(id='0', label='1st page', type='page'),
614
            fields.StringField(id='1', label='string', varname='foo'),
615
            fields.PageField(id='2', label='2nd page', type='page', condition='False'),
616
            fields.StringField(id='3', label='string 2'),
617
            fields.PageField(id='4', label='3rd page', type='page'),
618
            fields.StringField(id='5', label='string 3', varname='bar'),
619
            ]
620

  
621
    formdef.store()
622
    formdef.data_class().wipe()
623
    resp = get_app(pub).get('/test/')
624
    resp.forms[0]['f1'] = 'foo'
625
    resp = resp.forms[0].submit('submit')
626
    resp.forms[0]['f5'] = 'bar'
627
    resp = resp.forms[0].submit('submit')
628
    assert 'Check values then click submit.' in resp.body
629
    resp = resp.forms[0].submit('submit')
630
    assert formdef.data_class().count() == 1
631
    assert formdef.data_class().select()[0].data['1'] == 'foo'
632
    assert formdef.data_class().select()[0].data['5'] == 'bar'
633

  
634
    formdef.fields[4].post_conditions = [
635
        {'condition': 'False', 'error_message': 'You shall not pass.'},
636
    ]
637
    formdef.store()
638
    formdef.data_class().wipe()
639
    resp = get_app(pub).get('/test/')
640
    resp.forms[0]['f1'] = 'foo'
641
    resp = resp.forms[0].submit('submit')
642
    resp.forms[0]['f5'] = 'bar'
643
    resp = resp.forms[0].submit('submit')
644
    assert 'errornotice' in resp.body
645
    assert 'You shall not pass.' in resp.body
646

  
647
    formdef.fields[4].post_conditions = [
648
        {'condition': 'form_var_foo == "foo"', 'error_message': 'You shall not pass.'},
649
    ]
650
    formdef.store()
651
    formdef.data_class().wipe()
652
    resp = get_app(pub).get('/test/')
653
    resp.forms[0]['f1'] = 'bar'
654
    resp = resp.forms[0].submit('submit')
655
    resp.forms[0]['f5'] = 'bar'
656
    resp = resp.forms[0].submit('submit')
657
    assert 'errornotice' in resp.body
658
    assert 'You shall not pass.' in resp.body
659

  
660
    resp = get_app(pub).get('/test/')
661
    resp.forms[0]['f1'] = 'foo'
662
    resp = resp.forms[0].submit('submit')
663
    resp.forms[0]['f5'] = 'bar'
664
    resp = resp.forms[0].submit('submit')
665
    assert 'Check values then click submit.' in resp.body
666

  
667
    # check a post-condition raising an exception, they should always fail.
668
    formdef.fields[4].post_conditions = [
669
        {'condition': '1/0', 'error_message': 'You shall not pass.'},
670
    ]
671
    formdef.store()
672
    formdef.data_class().wipe()
673
    resp = get_app(pub).get('/test/')
674
    resp.forms[0]['f1'] = 'bar'
675
    resp = resp.forms[0].submit('submit')
676
    resp.forms[0]['f5'] = 'bar'
677
    resp = resp.forms[0].submit('submit')
678
    assert 'errornotice' in resp.body
679
    assert 'You shall not pass.' in resp.body
680

  
681
    # check a post-condition referring to a field on the same page
682
    formdef.fields[4].post_conditions = [
683
        {'condition': 'form_var_bar == "bar"', 'error_message': 'You shall not pass.'},
684
    ]
685
    formdef.store()
686
    formdef.data_class().wipe()
687
    resp = get_app(pub).get('/test/')
688
    resp.forms[0]['f1'] = 'bar'
689
    resp = resp.forms[0].submit('submit')
690
    resp.forms[0]['f5'] = 'foo'
691
    resp = resp.forms[0].submit('submit')
692
    assert 'errornotice' in resp.body
693
    assert 'You shall not pass.' in resp.body
694

  
695
    resp = get_app(pub).get('/test/')
696
    resp.forms[0]['f1'] = 'bar'
697
    resp = resp.forms[0].submit('submit')
698
    resp.forms[0]['f5'] = 'bar'
699
    resp = resp.forms[0].submit('submit')
700
    assert 'Check values then click submit.' in resp.body
701

  
611 702
def test_form_submit_with_user(pub):
612 703
    create_user(pub)
613 704
    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
                data = self.formdef.get_data(form)
676
                form_data.update(data)
677
                for i, post_condition in enumerate(post_conditions):
678
                    condition = post_condition.get('condition')
679
                    error_message = post_condition.get('error_message')
680
                    errored = False
681
                    try:
682
                        if not page.evaluate_condition(form_data, self.formdef, condition):
683
                            errored = True
684
                    except RuntimeError:
685
                        errored = True
686
                    if errored:
687
                        form.add(HiddenErrorWidget, 'post_condition%d' % i)
688
                        form.set_error('post_condition%d' % i, 'error')
689
                        page_error_messages.append(error_message)
690

  
664 691
            # form.get_submit() returns the name of the clicked button, and
665 692
            # it will return True if the form has been submitted, but not
666 693
            # by clicking on a submit widget; for example if an "add row"
......
671 698
                    # page hidden field had an error in its submission), in
672 699
                    # that case we just fall back to the first page.
673 700
                    page_no = 0
674
                return self.page(page_no, page_change=False)
701
                return self.page(page_no, page_change=False,
702
                        page_error_messages=page_error_messages)
675 703

  
676 704
            form_data = session.get_by_magictoken(magictoken, {})
677 705
            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
-