general: add support for conditions in block fields (#54761)
This commit is contained in:
parent
41ecc47858
commit
bc64d54502
|
@ -1672,3 +1672,220 @@ def test_removed_block_in_form_page(pub):
|
|||
assert '(id:%s)' % logged_error.id in resp.text
|
||||
logged_error = pub.loggederror_class.select()[0]
|
||||
assert logged_error.occurences_count == 2
|
||||
|
||||
|
||||
def test_block_with_static_condition(pub):
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.StringField(id='123', required=True, label='One', type='string', varname='one'),
|
||||
fields.StringField(
|
||||
id='234',
|
||||
required=True,
|
||||
label='Two',
|
||||
type='string',
|
||||
condition={'type': 'django', 'value': 'False'},
|
||||
),
|
||||
fields.StringField(
|
||||
id='345',
|
||||
required=True,
|
||||
label='Three',
|
||||
type='string',
|
||||
condition={'type': 'django', 'value': 'True'},
|
||||
),
|
||||
]
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.BlockField(id='1', label='test', type='block:foobar'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
resp = get_app(pub).get(formdef.get_url())
|
||||
assert 'f1$element0$f123' in resp.form.fields
|
||||
assert 'f1$element0$f234' in resp.form.fields
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f234"]').attr.style == 'display: none'
|
||||
assert 'f1$element0$f345' in resp.form.fields
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f345"]').attr.style is None
|
||||
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
resp.form['f1$element0$f345'] = 'bar'
|
||||
resp = resp.form.submit('submit') # -> validation page
|
||||
assert resp.form['f1$element0$f123'].attrs['readonly']
|
||||
assert resp.form['f1$element0$f123'].value == 'foo'
|
||||
assert resp.form['f1$element0$f234'].value == ''
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f234"]').attr.style == 'display: none'
|
||||
assert resp.form['f1$element0$f345'].value == 'bar'
|
||||
resp = resp.form.submit('submit') # -> end page
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data == {
|
||||
'1': {
|
||||
'data': [{'123': 'foo', '345': 'bar'}],
|
||||
'schema': {'123': 'string', '234': 'string', '345': 'string'},
|
||||
},
|
||||
'1_display': 'foobar',
|
||||
}
|
||||
|
||||
|
||||
def test_block_with_block_field_condition(pub):
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.StringField(id='123', required=True, label='One', type='string', varname='one'),
|
||||
fields.StringField(
|
||||
id='234',
|
||||
required=True,
|
||||
label='Two',
|
||||
type='string',
|
||||
condition={'type': 'django', 'value': 'block_var_one == "test"'},
|
||||
),
|
||||
]
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.BlockField(id='1', label='test', type='block:foobar'),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f123"]').attr['data-live-source'] == 'true'
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f234"]').attr.style == 'display: none'
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
live_resp = app.post(
|
||||
formdef.get_url() + 'live?modified_field_id=123&modified_block_id=1&modified_block_row=0',
|
||||
params=resp.form.submit_fields(),
|
||||
)
|
||||
assert live_resp.json['result']['1-123-0']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-0']['visible'] is False
|
||||
resp.form['f1$element0$f123'] = 'test'
|
||||
live_resp = app.post(
|
||||
formdef.get_url() + 'live?modified_field_id=123&modified_block_id=1&modified_block_row=0',
|
||||
params=resp.form.submit_fields(),
|
||||
)
|
||||
assert live_resp.json['result']['1-123-0']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-0']['visible'] is True
|
||||
resp = resp.form.submit('submit') # -> error as 1-234-0 is required
|
||||
assert 'There were errors processing the form' in resp
|
||||
|
||||
resp.form['f1$element0$f234'] = 'test'
|
||||
resp = resp.form.submit('submit') # validation
|
||||
assert 'There were errors processing the form' not in resp
|
||||
|
||||
resp = resp.form.submit('previous') # -> 1st page
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
resp.form['f1$element0$f234'] = ''
|
||||
resp = resp.form.submit('submit') # validation
|
||||
assert 'There were errors processing the form' not in resp
|
||||
resp = resp.form.submit('submit') # -> end page
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data == {
|
||||
'1': {
|
||||
'data': [{'123': 'foo'}],
|
||||
'schema': {'123': 'string', '234': 'string'},
|
||||
},
|
||||
'1_display': 'foobar',
|
||||
}
|
||||
formdef.data_class().wipe()
|
||||
|
||||
# check with repetition
|
||||
formdef.fields[0].max_items = 3
|
||||
formdef.store()
|
||||
|
||||
resp = app.get(formdef.get_url())
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert 'There were errors processing the form' not in resp
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f234"]').attr.style == 'display: none'
|
||||
assert resp.pyquery('[data-widget-name="f1$element1$f234"]').attr.style == 'display: none'
|
||||
resp.form['f1$element0$f123'] = 'test'
|
||||
live_resp = app.post(
|
||||
formdef.get_url() + 'live?modified_field_id=123&modified_block_id=1&modified_block_row=0',
|
||||
params=resp.form.submit_fields(),
|
||||
)
|
||||
assert live_resp.json['result']['1-123-0']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-0']['visible'] is True
|
||||
assert live_resp.json['result']['1-123-1']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-1']['visible'] is False
|
||||
|
||||
resp.form['f1$element0$f234'] = 'foo'
|
||||
resp.form['f1$element1$f123'] = 'xxx'
|
||||
|
||||
resp = resp.form.submit('submit') # validation
|
||||
resp = resp.form.submit('submit') # -> end page
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data == {
|
||||
'1': {
|
||||
'data': [{'123': 'test', '234': 'foo'}, {'123': 'xxx'}],
|
||||
'schema': {'123': 'string', '234': 'string'},
|
||||
},
|
||||
'1_display': 'foobar, foobar',
|
||||
}
|
||||
|
||||
|
||||
def test_block_with_block_counter_condition(pub):
|
||||
BlockDef.wipe()
|
||||
block = BlockDef()
|
||||
block.name = 'foobar'
|
||||
block.fields = [
|
||||
fields.StringField(id='123', required=True, label='One', type='string', varname='one'),
|
||||
fields.StringField(
|
||||
id='234',
|
||||
required=True,
|
||||
label='Two',
|
||||
type='string',
|
||||
condition={'type': 'django', 'value': 'block_counter.index == 1'},
|
||||
),
|
||||
]
|
||||
block.store()
|
||||
|
||||
FormDef.wipe()
|
||||
formdef = FormDef()
|
||||
formdef.name = 'form title'
|
||||
formdef.fields = [
|
||||
fields.BlockField(id='1', label='test', type='block:foobar', max_items=3),
|
||||
]
|
||||
formdef.store()
|
||||
formdef.data_class().wipe()
|
||||
|
||||
app = get_app(pub)
|
||||
resp = app.get(formdef.get_url())
|
||||
assert resp.pyquery('[data-widget-name="f1$element0$f234"]').attr.style is None
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.pyquery('[data-widget-name="f1$element1$f234"]').attr.style == 'display: none'
|
||||
resp = resp.form.submit('f1$add_element')
|
||||
assert resp.pyquery('[data-widget-name="f1$element2$f234"]').attr.style == 'display: none'
|
||||
|
||||
resp.form['f1$element0$f123'] = 'foo'
|
||||
resp.form['f1$element0$f234'] = 'foo'
|
||||
resp.form['f1$element1$f123'] = 'bar'
|
||||
live_resp = app.post(
|
||||
formdef.get_url() + 'live',
|
||||
params=resp.form.submit_fields(),
|
||||
)
|
||||
assert live_resp.json['result']['1-123-0']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-0']['visible'] is True
|
||||
assert live_resp.json['result']['1-123-1']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-1']['visible'] is False
|
||||
assert live_resp.json['result']['1-123-2']['visible'] is True
|
||||
assert live_resp.json['result']['1-234-2']['visible'] is False
|
||||
resp = resp.form.submit('submit') # validation
|
||||
resp = resp.form.submit('submit') # -> end page
|
||||
formdata = formdef.data_class().select()[0]
|
||||
assert formdata.data == {
|
||||
'1': {
|
||||
'data': [{'123': 'foo', '234': 'foo'}, {'123': 'bar'}],
|
||||
'schema': {'123': 'string', '234': 'string'},
|
||||
},
|
||||
'1_display': 'foobar, foobar',
|
||||
}
|
||||
formdef.data_class().wipe()
|
||||
|
|
|
@ -1087,7 +1087,14 @@ def test_field_live_block_string_prefill(pub, http_requests):
|
|||
assert live_resp.json['result'] == {
|
||||
'1': {'visible': True},
|
||||
'2': {'visible': True},
|
||||
'2-123': {'block_id': '2', 'content': 'hello', 'field_id': '123', 'visible': True},
|
||||
'2-123-0': {
|
||||
'block_id': '2',
|
||||
'block_row': 'element0',
|
||||
'row': 0,
|
||||
'content': 'hello',
|
||||
'field_id': '123',
|
||||
'visible': True,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -31,8 +31,6 @@ from wcs.qommon.form import FileWidget, Form, HtmlWidget, SingleSelectWidget, Sl
|
|||
|
||||
|
||||
class BlockFieldDefPage(FieldDefPage):
|
||||
blacklisted_attributes = ['condition']
|
||||
|
||||
def redirect_field_anchor(self, field):
|
||||
anchor = '#itemId_%s' % field.id if field else ''
|
||||
return redirect('../%s' % anchor)
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
import itertools
|
||||
import uuid
|
||||
import xml.etree.ElementTree as ET
|
||||
from contextlib import contextmanager
|
||||
|
||||
from quixote import get_publisher, get_request
|
||||
from quixote.html import htmltag, htmltext
|
||||
|
@ -43,6 +44,7 @@ class BlockDef(StorableObject):
|
|||
xml_root_node = 'block'
|
||||
verbose_name = _('Field block')
|
||||
verbose_name_plural = _('Field blocks')
|
||||
var_prefixes = ['block']
|
||||
|
||||
name = None
|
||||
slug = None
|
||||
|
@ -230,6 +232,15 @@ class BlockDef(StorableObject):
|
|||
def is_used(self):
|
||||
return any(self.get_usage_formdefs())
|
||||
|
||||
@contextmanager
|
||||
def visibility_context(self, value, row_index):
|
||||
from .variables import LazyBlockDataVar
|
||||
|
||||
context = self.get_substitution_counter_variables(row_index)
|
||||
context['block_var'] = LazyBlockDataVar(self.fields, value)
|
||||
with get_publisher().substitutions.temporary_feed(context):
|
||||
yield
|
||||
|
||||
|
||||
class BlockSubWidget(CompositeWidget):
|
||||
template_name = 'qommon/forms/widgets/block_sub.html'
|
||||
|
@ -246,24 +257,49 @@ class BlockSubWidget(CompositeWidget):
|
|||
field_value = None
|
||||
if value is not None:
|
||||
field_value = value.get(field.id)
|
||||
field.add_to_view_form(form=self, value=field_value)
|
||||
return field.add_to_view_form(form=self, value=field_value)
|
||||
else:
|
||||
field.add_to_form(form=self)
|
||||
widget = self.get_widget('f%s' % field.id)
|
||||
if widget:
|
||||
widget.div_id = None
|
||||
widget.prefill_attributes = field.get_prefill_attributes()
|
||||
return widget
|
||||
|
||||
self.fields = {}
|
||||
|
||||
live_sources = []
|
||||
for field in self.block.fields:
|
||||
context = self.block.get_substitution_counter_variables(self.index)
|
||||
if field.type in ['title', 'subtitle', 'comment']:
|
||||
with get_publisher().substitutions.temporary_feed(context):
|
||||
add_to_form(field)
|
||||
widget = add_to_form(field)
|
||||
else:
|
||||
add_to_form(field)
|
||||
widget = add_to_form(field)
|
||||
|
||||
if field.condition:
|
||||
live_sources.extend(field.get_condition_varnames(formdef=self.block))
|
||||
field.widget = widget
|
||||
self.fields[field.id] = widget
|
||||
|
||||
for field in self.block.fields:
|
||||
if field.varname in live_sources:
|
||||
field.widget.live_condition_source = True
|
||||
|
||||
if value:
|
||||
self.set_value(value)
|
||||
|
||||
self.set_visibility(value)
|
||||
|
||||
def set_visibility(self, value):
|
||||
with self.block.visibility_context(value, self.index):
|
||||
for field in self.block.fields:
|
||||
widget = self.fields.get(field.id)
|
||||
if not widget:
|
||||
continue
|
||||
visible = field.is_visible({}, formdef=None)
|
||||
widget.is_hidden = not (visible)
|
||||
|
||||
def set_value(self, value):
|
||||
self.value = value
|
||||
for widget in self.get_widgets():
|
||||
|
@ -278,8 +314,13 @@ class BlockSubWidget(CompositeWidget):
|
|||
def _parse(self, request):
|
||||
value = {}
|
||||
empty = True
|
||||
|
||||
for widget in self.get_widgets():
|
||||
widget_value = self.get_field_data(widget.field, widget)
|
||||
with self.block.visibility_context(value, self.index):
|
||||
if not widget.field.is_visible({}, formdef=None):
|
||||
widget.clear_error()
|
||||
continue
|
||||
value.update(widget_value)
|
||||
if widget_value.get(widget.field.id) is not None:
|
||||
empty = False
|
||||
|
|
|
@ -880,6 +880,7 @@ class WidgetField(Field):
|
|||
widget.extra_css_class = '%s %s' % (widget.extra_css_class, self.extra_css_class)
|
||||
else:
|
||||
widget.extra_css_class = self.extra_css_class
|
||||
return widget
|
||||
|
||||
def get_display_locations_options(self):
|
||||
options = [
|
||||
|
@ -2733,7 +2734,7 @@ class PageCondition(Condition):
|
|||
|
||||
live_data = {}
|
||||
form_live_data = {}
|
||||
if dict_vars is not None:
|
||||
if dict_vars is not None and formdef:
|
||||
live_data = get_dict_with_varnames(formdef.fields, dict_vars)
|
||||
form_live_data = {'form_' + x: y for x, y in live_data.items()}
|
||||
|
||||
|
|
|
@ -804,19 +804,30 @@ class FormStatusPage(Directory, FormTemplateMixin):
|
|||
for widget in form.widgets:
|
||||
if not getattr(widget, 'field', None):
|
||||
continue
|
||||
yield (None, widget.field, widget)
|
||||
yield (None, None, widget.field, widget)
|
||||
if isinstance(widget, BlockWidget):
|
||||
block_row = 0
|
||||
for subwidget in widget.widgets:
|
||||
if isinstance(subwidget, BlockSubWidget):
|
||||
for field_widget in subwidget.widgets:
|
||||
yield (widget.field, field_widget.field, field_widget)
|
||||
yield (widget.field, block_row, field_widget.field, field_widget)
|
||||
block_row += 1
|
||||
|
||||
for block, field, widget in get_all_field_widgets(form):
|
||||
for block, block_row, field, widget in get_all_field_widgets(form):
|
||||
if block:
|
||||
entry = {'visible': True}
|
||||
result['%s-%s' % (block.id, field.id)] = entry
|
||||
try:
|
||||
block_data = formdata.data.get(block.id)['data'][block_row]
|
||||
except (IndexError, TypeError):
|
||||
block_data = {}
|
||||
|
||||
with block.block.visibility_context(block_data, block_row):
|
||||
is_visible = field.is_visible({}, formdef=None)
|
||||
entry = {'visible': is_visible, 'row': block_row, 'field_id': field.id, 'block_id': block.id}
|
||||
result['%s-%s-%s' % (block.id, field.id, block_row)] = entry
|
||||
entry['block_id'] = block.id
|
||||
entry['field_id'] = field.id
|
||||
entry['block_row'] = 'element%s' % block_row
|
||||
|
||||
else:
|
||||
entry = result[field.id]
|
||||
if field.key == 'comment':
|
||||
|
|
|
@ -452,6 +452,8 @@ $(function() {
|
|||
var new_data = $(this).serialize();
|
||||
if (data && data.modified_field) {
|
||||
new_data += '&modified_field_id=' + data.modified_field;
|
||||
if (data.modified_block) new_data += '&modified_block_id=' + data.modified_block;
|
||||
if (data.modified_block_row) new_data += '&modified_block_row=' + data.modified_block_row;
|
||||
}
|
||||
$('.widget-prefilled').each(function(idx, elem) {
|
||||
new_data += '&prefilled_' + $(elem).data('field-id') + '=true';
|
||||
|
@ -465,7 +467,9 @@ $(function() {
|
|||
headers: {'accept': 'application/json'},
|
||||
success: function(json) {
|
||||
$.each(json.result, function(key, value) {
|
||||
if (value.block_id) {
|
||||
if (value.block_id && value.block_row) {
|
||||
var $widget = $('[data-field-id="' + value.block_id + '"] [data-block-row="' + value.block_row + '"] [data-field-id="' + value.field_id + '"]');
|
||||
} else if (value.block_id) {
|
||||
var $widget = $('[data-field-id="' + value.block_id + '"] [data-field-id="' + value.field_id + '"]');
|
||||
} else {
|
||||
var $widget = $('[data-field-id="' + key + '"]');
|
||||
|
@ -567,8 +571,13 @@ $(function() {
|
|||
$('form').on('change input paste wcs:change',
|
||||
'div[data-live-source] input:not([type=file]), div[data-live-source] select, div[data-live-source] textarea',
|
||||
function(ev) {
|
||||
var modified_field = $(this).parents('[data-field-id]').data('field-id');
|
||||
$(this).parents('form').trigger('wcs:change', {modified_field: modified_field});
|
||||
var params = {};
|
||||
params.modified_field = $(this).closest('[data-field-id]').data('field-id');
|
||||
if ($(this).parents('.BlockWidget').length) {
|
||||
params.modified_block = $(this).closest('.BlockWidget').data('field-id');
|
||||
params.modified_block_row = $(this).closest('.BlockSubWidget').data('block-row');
|
||||
}
|
||||
$(this).parents('form').trigger('wcs:change', params);
|
||||
});
|
||||
}
|
||||
$('form div[data-live-source]').parents('form').trigger('wcs:change', {modified_field: 'init'});
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
{% if widget.get_error %}widget-with-error{% endif %}
|
||||
{% if widget.is_required %}widget-required{% else %}widget-optional{% endif %}
|
||||
{% if widget.is_prefilled %}widget-prefilled{% endif %}{% endblock %}"
|
||||
{% block widget-attrs %}
|
||||
{% if widget.is_hidden %}style="display: none"{% endif %}
|
||||
{% if widget.field %}data-field-id="{{ widget.field.id }}"{% endif %}
|
||||
data-widget-name="{{ widget.name }}"
|
||||
|
@ -22,6 +23,7 @@
|
|||
data-dynamic-display-value-in="{{widget.attrs|get:"data-dynamic-display-value-in"}}"
|
||||
{% endif %}
|
||||
{% if widget.live_condition_source %}data-live-source="true"{% endif %}
|
||||
{% endblock %}
|
||||
>
|
||||
{% block widget-title %}
|
||||
{{widget.rendered_title}}
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
{% extends "qommon/forms/widget.html" %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block widget-attrs %}
|
||||
{{ block.super }} data-block-row="element{{ widget.index }}"
|
||||
{% endblock %}
|
||||
|
||||
{% block widget-content %}
|
||||
{% for subwidget in widget.get_widgets %}
|
||||
{% if widget.readonly and not subwidget.field.include_in_validation_page %}<div style="display: none">{% endif %}
|
||||
|
|
Loading…
Reference in New Issue