blocks: add support for post conditions (#71778)
gitea/wcs/pipeline/head Build queued... Details

This commit is contained in:
Frédéric Péters 2024-03-12 08:37:26 +01:00
parent 26668ba3ea
commit 0abc13aab0
3 changed files with 127 additions and 1 deletions

View File

@ -2881,3 +2881,108 @@ def test_block_multiple_rows_single_draft(pub, logged_user, tracking_code):
assert formdef.data_class().count() == 1
assert formdef.data_class().select()[0].status == 'wf-new'
def test_block_field_post_condition(pub):
FormDef.wipe()
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.StringField(id='123', label='Foo', varname='foo'),
fields.StringField(id='234', label='Bar', varname='bar'),
]
block.post_conditions = [
{
'condition': {'type': 'django', 'value': 'block_var_foo|startswith:"b"'},
'error_message': 'foo must start with a b.',
},
{
'condition': {'type': 'django', 'value': 'block_var_foo == block_var_bar'},
'error_message': 'foo and bar must be identical.',
},
]
block.store()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.BlockField(id='1', label='test', block_slug='foobar'),
]
formdef.store()
app = get_app(pub)
resp = app.get(formdef.get_url())
resp.form['f1$element0$f123'] = 'foo'
resp.form['f1$element0$f234'] = 'bar'
resp = resp.form.submit('submit') # -> error
assert (
resp.pyquery('.widget-with-error .error').text()
== 'foo must start with a b. foo and bar must be identical.'
)
resp.form['f1$element0$f123'] = 'baz'
resp.form['f1$element0$f234'] = 'bar'
resp = resp.form.submit('submit') # -> error
assert resp.pyquery('.widget-with-error .error').text() == 'foo and bar must be identical.'
resp.form['f1$element0$f123'] = 'baz'
resp.form['f1$element0$f234'] = 'baz'
resp = resp.form.submit('submit') # -> validation page
assert resp.form['f1$element0$f123'].attrs['readonly']
resp = resp.form.submit('submit') # -> end page
formdata = formdef.data_class().select()[0]
assert formdata.status == 'wf-new'
assert formdata.data == {
'1': {'data': [{'123': 'baz', '234': 'baz'}], 'schema': {'123': 'string', '234': 'string'}},
'1_display': 'foobar',
}
# multiple rows
formdef.fields[0].max_items = 3
formdef.store()
formdef.data_class().wipe()
resp = app.get(formdef.get_url())
resp.form['f1$element0$f123'] = 'baz'
resp.form['f1$element0$f234'] = 'bar'
resp = resp.form.submit('f1$add_element')
assert not resp.pyquery('.widget-with-error')
resp.form['f1$element1$f123'] = 'bar'
resp.form['f1$element1$f234'] = 'bar'
resp = resp.form.submit('submit') # -> error
assert (
resp.pyquery('.widget-with-error[data-block-row="element0"] .error').text()
== 'foo and bar must be identical.'
)
assert resp.pyquery('.widget-with-error[data-block-row="element1"] .error').text() == ''
resp.form['f1$element1$f234'] = 'baz'
resp = resp.form.submit('submit') # -> error
assert (
resp.pyquery('.widget-with-error[data-block-row="element0"] .error').text()
== 'foo and bar must be identical.'
)
assert (
resp.pyquery('.widget-with-error[data-block-row="element1"] .error').text()
== 'foo and bar must be identical.'
)
resp.form['f1$element0$f123'] = 'bar'
resp.form['f1$element1$f234'] = 'bar'
resp = resp.form.submit('submit') # -> validation page
assert resp.form['f1$element0$f123'].attrs['readonly']
resp = resp.form.submit('submit') # -> end page
formdata = formdef.data_class().select()[0]
assert formdata.status == 'wf-new'
assert formdata.data == {
'1': {
'data': [{'123': 'bar', '234': 'bar'}, {'123': 'bar', '234': 'bar'}],
'schema': {'123': 'string', '234': 'string'},
},
'1_display': 'foobar, foobar',
}

View File

@ -24,7 +24,7 @@ from contextlib import contextmanager
from quixote import get_publisher, get_request, get_response
from quixote.html import htmltag, htmltext
from . import data_sources, fields
from . import conditions, data_sources, fields
from .categories import BlockCategory
from .formdata import FormData
from .qommon import _, misc
@ -455,6 +455,22 @@ class BlockSubWidget(CompositeWidget):
all_lists = False
if widget_value.get(widget.field.id) not in empty_values:
empty = False
if not empty and self.block.post_conditions:
error_messages = []
with self.block.visibility_context(value, self.index):
for i, post_condition in enumerate(self.block.post_conditions):
condition = post_condition.get('condition')
try:
if conditions.Condition(condition, record_errors=False).evaluate():
continue
except RuntimeError:
pass
error_message = post_condition.get('error_message')
error_messages.append(get_publisher().translate(error_message))
if error_messages:
self.set_error(' '.join(error_messages))
if empty and not all_lists and not get_publisher().keep_all_block_rows_mode:
value = None
for widget in self.get_widgets(): # reset "required" errors
@ -559,6 +575,10 @@ class BlockWidget(WidgetList):
self._parse(request)
if self.required and self.value is None:
self.set_error(_(self.REQUIRED_ERROR))
for widget in self.widgets:
# mark required rows with a special attribute, to avoid doubling the
# error messages in the template.
widget.is_required_error = bool(widget.error == self.REQUIRED_ERROR)
return self.value
def add_media(self):

View File

@ -6,6 +6,7 @@
{% endblock %}
{% block widget-content %}
{% if not widget.readonly and widget.error and not widget.is_required_error %}<div class="error"><p>{{ widget.error }}</p></div>{% endif %}
{% for subwidget in widget.get_widgets %}
{% if widget.readonly and not subwidget.field.include_in_validation_page %}<div style="display: none">{% endif %}
{{ subwidget.render|safe }}