Compare commits

..

8 Commits

11 changed files with 365 additions and 25 deletions

View File

@ -2652,6 +2652,92 @@ def test_block_prefill_full_block_email(pub):
}
def test_block_prefill_full_block_card_item(pub):
FormDef.wipe()
BlockDef.wipe()
CardDef.wipe()
create_user(pub)
carddef = CardDef()
carddef.name = 'Test'
carddef.fields = [
fields.StringField(id='0', label='blah', varname='blah'),
]
carddef.digest_templates = {'default': '{{ form_var_blah|upper }}'}
carddef.store()
carddef.data_class().wipe()
carddata1 = carddef.data_class()()
carddata1.data = {'0': 'bar'}
carddata1.just_created()
carddata1.store()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.ItemField(
id='123',
required=False,
hint='-----',
label='Test',
varname='plop',
data_source={'type': 'carddef:test'},
),
]
block.digest_template = '{{block_var_plop}}'
block.store()
formdef = FormDef()
formdef.name = 'form title'
formdef.fields = [
fields.PageField(id='0', label='1st page'),
fields.PageField(id='2', label='2nd page'),
fields.BlockField(
id='1',
label='test',
block_slug='foobar',
max_items=5,
prefill={
'type': 'string',
'value': '{% block_value plop="1" %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
login(app, username='foo', password='foo')
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit') # -> page 2
assert resp.form['f1$element0$f123'].value == '1'
resp = resp.form.submit('submit') # validation
resp = resp.form.submit('submit') # done
assert formdef.data_class().select()[0].data == {
'1': {
'data': [
{'123': '1', '123_display': 'BAR', '123_structured': {'blah': 'bar', 'id': 1, 'text': 'BAR'}}
],
'schema': {'123': 'item'},
},
'1_display': 'BAR',
}
# prefill with unknown value
pub.loggederror_class.wipe()
formdef.fields[2].prefill['value'] = '{% block_value plop="123" %}'
formdef.store()
formdef.data_class().wipe()
resp = app.get(formdef.get_url())
resp = resp.form.submit('submit') # -> page 2
assert not resp.form['f1$element0$f123'].value
assert pub.loggederror_class.count() == 1
assert (
pub.loggederror_class.select()[0].summary
== 'invalid value when creating block: unknown card value (\'123\')'
)
def test_block_titles_and_empty_block_on_summary_page(pub, emails):
FormDef.wipe()
BlockDef.wipe()

View File

@ -631,6 +631,107 @@ def test_form_page_item_with_variable_data_source_prefill(pub):
assert not resp.pyquery('#form_error_f2').text()
def test_form_page_item_with_card_with_custom_id_prefill(pub):
create_user(pub)
CardDef.wipe()
carddef = CardDef()
carddef.name = 'Test'
carddef.fields = [
fields.StringField(id='0', label='blah', varname='blah'),
]
carddef.digest_templates = {'default': '{{ form_var_blah|upper }}'}
carddef.id_template = '{{ form_var_blah }}'
carddef.store()
carddef.data_class().wipe()
carddata1 = carddef.data_class()()
carddata1.data = {'0': 'bar'}
carddata1.just_created()
carddata1.store()
carddata2 = carddef.data_class()()
carddata2.data = {'0': 'foo'}
carddata2.just_created()
carddata2.store()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.ItemField(
id='2',
label='item',
varname='item',
required=False,
data_source={'type': 'carddef:test'},
prefill={'type': 'string', 'value': 'foo'},
),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert [x.attrib['value'] for x in resp.pyquery('#form_f2 option')] == ['bar', 'foo']
assert resp.form['f2'].value == 'foo'
assert not resp.pyquery('#form_error_f2').text()
def test_form_page_block_with_item_with_card_with_custom_id_prefill(pub):
create_user(pub)
CardDef.wipe()
carddef = CardDef()
carddef.name = 'Test'
carddef.fields = [
fields.StringField(id='0', label='blah', varname='blah'),
]
carddef.digest_templates = {'default': 'card {{ form_var_blah }}'}
carddef.id_template = '{{ form_var_blah }}'
carddef.store()
carddef.data_class().wipe()
carddata1 = carddef.data_class()()
carddata1.data = {'0': 'bar'}
carddata1.just_created()
carddata1.store()
carddata2 = carddef.data_class()()
carddata2.data = {'0': 'foo'}
carddata2.just_created()
carddata2.store()
BlockDef.wipe()
block = BlockDef()
block.name = 'foobar'
block.fields = [
fields.ItemField(
id='123',
label='item',
varname='item',
required=False,
data_source={'type': 'carddef:test'},
),
]
block.store()
formdef = create_formdef()
formdef.data_class().wipe()
formdef.fields = [
fields.BlockField(
id='2',
label='test',
block_slug='foobar',
varname='foobar',
prefill={'type': 'string', 'value': '{% block_value item="foo" %}'},
),
]
formdef.store()
resp = get_app(pub).get('/test/')
assert [x.attrib['value'] for x in resp.pyquery('#form_f2__element0__f123 option')] == ['bar', 'foo']
assert resp.form['f2$element0$f123'].value == 'foo'
assert not resp.pyquery('.widget-with-error')
def test_form_page_item_with_computed_field_variable_data_source_prefill(pub):
create_user(pub)
formdef = create_formdef()

View File

@ -62,3 +62,24 @@ def test_import_blockdef_multiple_errors(pub):
assert excinfo.value.details == (
'Unknown datasources: carddef:foo:unknown, carddef:unknown, foobar; Unknown field types: foobaz'
)
def test_import_blockdef_post_conditions(pub):
BlockDef.wipe()
carddef = CardDef()
carddef.name = 'foo'
carddef.fields = []
carddef.store()
blockdef = BlockDef()
blockdef.name = 'foo'
blockdef.fields = []
blockdef.post_conditions = [
{'condition': {'type': 'django', 'value': 'blah1'}, 'error_message': 'bar1'},
{'condition': {'type': 'django', 'value': 'blah2'}, 'error_message': 'bar2'},
]
export = ET.tostring(export_to_indented_xml(blockdef))
blockdef2 = BlockDef.import_from_xml(io.BytesIO(export))
assert blockdef.post_conditions == blockdef2.post_conditions

View File

@ -1119,6 +1119,117 @@ def test_edit_carddata_targeting_itself(pub):
assert carddata.status == 'wf-%s' % st2.id
def test_edit_carddata_auto_targeting_custom_id(pub):
CardDef.wipe()
carddef = CardDef()
carddef.name = 'Foo Card'
carddef.fields = [
StringField(id='0', label='foo', varname='foo'),
StringField(id='1', label='slug', varname='slug'),
]
carddef.id_template = 'card_{{form_var_slug}}'
carddef.store()
carddef.data_class().wipe()
carddata = carddef.data_class()()
carddata.data = {'0': 'foo', '1': 'foo'}
carddata.store()
carddata.just_created()
carddata.store()
assert carddata.identifier == 'card_foo'
carddef2 = CardDef()
carddef2.name = 'Bar Card'
carddef2.fields = [
ItemField(id='1', label='card', varname='card', data_source={'type': 'carddef:%s' % carddef.url_name})
]
carddef2.store()
card_wf = Workflow(name='Card workflow')
st1 = card_wf.add_status('Status1')
st2 = card_wf.add_status('Status2')
edit = st1.add_action('edit_carddata', id='_edit')
edit.formdef_slug = carddef.url_name
edit.target_mode = 'all'
edit.mappings = [Mapping(field_id='0', expression='bar')]
jump = st1.add_action('jump', '_jump')
jump.status = st2.id
card_wf.store()
carddef2.workflow = card_wf
carddef2.store()
carddata2 = carddef2.data_class()()
carddata2.data = {
'1': 'card_foo',
}
carddata2.store()
carddata2.just_created()
carddata2.store()
carddata2.perform_workflow()
carddata.refresh_from_storage()
assert carddata.data['0'] == 'bar'
def test_edit_carddata_manual_targeting_custom_id(pub):
CardDef.wipe()
carddef = CardDef()
carddef.name = 'Foo Card'
carddef.fields = [
StringField(id='0', label='foo', varname='foo'),
StringField(id='1', label='slug', varname='slug'),
]
carddef.id_template = 'card_{{form_var_slug}}'
carddef.store()
carddef.data_class().wipe()
carddata = carddef.data_class()()
carddata.data = {'0': 'foo', '1': 'foo'}
carddata.store()
carddata.just_created()
carddata.store()
assert carddata.identifier == 'card_foo'
carddef2 = CardDef()
carddef2.name = 'Bar Card'
carddef2.fields = []
carddef2.store()
card_wf = Workflow(name='Card workflow')
st1 = card_wf.add_status('Status1')
st2 = card_wf.add_status('Status2')
edit = st1.add_action('edit_carddata', id='_edit')
edit.formdef_slug = carddef.url_name
edit.target_mode = 'manual'
edit.target_id = 'card_foo'
edit.mappings = [Mapping(field_id='0', expression='bar')]
jump = st1.add_action('jump', '_jump')
jump.status = st2.id
card_wf.store()
carddef2.workflow = card_wf
carddef2.store()
carddata2 = carddef2.data_class()()
carddata2.data = {}
carddata2.store()
carddata2.just_created()
carddata2.store()
carddata2.perform_workflow()
carddata.refresh_from_storage()
assert carddata.data['0'] == 'bar'
def test_edit_carddata_from_created_object(pub):
FormDef.wipe()
CardDef.wipe()

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
@ -435,6 +435,21 @@ 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')
error_message = post_condition.get('error_message')
try:
if not conditions.Condition(condition, record_errors=False).evaluate():
error_messages.append(error_message)
except RuntimeError:
error_messages.append(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
@ -528,6 +543,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:
# do not mark individual rows as required
if widget.error == self.REQUIRED_ERROR:
widget.clear_error()
return self.value
def add_media(self):

View File

@ -19,7 +19,7 @@ from quixote import get_publisher, get_request, get_session
from wcs.formdata import FormData
from .qommon import _
from .sql_criterias import Equal, Null, StrictNotEqual
from .sql_criterias import Equal
class CardData(FormData):
@ -39,20 +39,6 @@ class CardData(FormData):
formdef = property(get_formdef)
@classmethod
def get_by_id(cls, value):
try:
return cls.select(
[
StrictNotEqual('status', 'draft'),
Null('anonymised'),
cls._formdef.get_by_id_criteria(value),
],
limit=1,
)[0]
except IndexError:
raise KeyError(value)
def get_data_source_structured_item(
self, digest_key='default', group_by=None, with_related_urls=False, with_files_urls=False
):

View File

@ -28,6 +28,7 @@ from wcs.qommon.ods import NS as OD_NS
from wcs.qommon.ods import clean_text as od_clean_text
from .base import SetValueError, WidgetField
from .item import UnknownCardValueError
class MissingBlockFieldError(Exception):
@ -67,7 +68,11 @@ class BlockRowValue:
sub_field.set_value(row_data, sub_value)
return row_data
row_data = make_row_data(self.attributes)
try:
row_data = make_row_data(self.attributes)
except UnknownCardValueError as e:
get_publisher().record_error(_('invalid value when creating block: %s') % str(e), exception=e)
return None
current_block_value = data.get(field.id)
if not self.check_current_value(current_block_value):

View File

@ -32,7 +32,7 @@ from quixote.errors import RequestError
from quixote.html import htmltext
from quixote.http_request import Upload
from wcs.sql_criterias import And, Contains, Equal, Intersects
from wcs.sql_criterias import And, Contains, Equal, Intersects, Null, StrictNotEqual
from .qommon import _, misc
from .qommon.evalutils import make_datetime
@ -365,7 +365,10 @@ class FormData(StorableObject):
def get_by_id(cls, value):
if cls._formdef.id_template:
try:
return cls.select([Equal('id_display', str(value))], limit=1)[0]
return cls.select(
[StrictNotEqual('status', 'draft'), Null('anonymised'), Equal('id_display', str(value))],
limit=1,
)[0]
except IndexError:
raise KeyError(value)
return cls.get(value)
@ -1932,7 +1935,7 @@ class FormData(StorableObject):
if object_type:
# workflow action
try:
yield objectdef.data_class().get(target_id)
yield objectdef.data_class().get_by_id(target_id)
except KeyError:
# linked object may be missing
pass
@ -1952,7 +1955,7 @@ class FormData(StorableObject):
_('%s - not found') % origin,
)
else:
yield (_objectdef.data_class().get(target_id), origin)
yield (_objectdef.data_class().get_by_id(target_id), origin)
except ValueError:
pass
except KeyError:

View File

@ -4,8 +4,8 @@ msgid ""
msgstr ""
"Project-Id-Version: wcs 0\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-03-08 11:14+0100\n"
"PO-Revision-Date: 2024-03-08 11:14+0100\n"
"POT-Creation-Date: 2024-03-12 09:34+0100\n"
"PO-Revision-Date: 2024-03-12 09:34+0100\n"
"Last-Translator: Thomas Noël <tnoel@entrouvert.com>\n"
"Language-Team: french\n"
"Language: fr\n"
@ -2570,7 +2570,9 @@ msgstr "Importer les données depuis une demande"
#: admin/tests.py
msgid "Import data from form (and initialise workflow tests)"
msgstr "Importer les données depuis une demande (et initialiser les tests de workflow)"
msgstr ""
"Importer les données depuis une demande (et initialiser les tests de "
"workflow)"
#: admin/tests.py
msgid ""
@ -5446,6 +5448,11 @@ msgstr "La valeur doit contenir un ou plusieurs noms valides."
msgid "Missing block field: %s"
msgstr "Bloc de champ manquant : %s"
#: fields/block.py
#, python-format
msgid "invalid value when creating block: %s"
msgstr "valeur invalide pour la création du bloc : %s"
#: fields/block.py
#, python-format
msgid "Field Block (%s)"

View File

@ -6,6 +6,7 @@
{% endblock %}
{% block widget-content %}
{% if not widget.readonly and widget.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 }}

View File

@ -267,7 +267,7 @@ class ExternalWorkflowGlobalAction(WorkflowStatusItem):
return
try:
yield objectdef.data_class().get(target_id)
yield objectdef.data_class().get_by_id(target_id)
except KeyError as e:
# use custom error message depending on target type
get_publisher().record_error(