misc: check selected item field value against updated list of options (#73982) #727
|
@ -479,6 +479,89 @@ def test_form_page_profile_prefill_list(pub):
|
|||
assert resp.forms[0]['f0'].value == ''
|
||||
|
||||
|
||||
def test_form_page_item_with_variable_data_source_prefill(pub):
|
||||
create_user(pub)
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [
|
||||
fields.StringField(
|
||||
id='1', label='string', varname='string', prefill={'type': 'string', 'value': 'foobar'}
|
||||
),
|
||||
fields.ItemField(
|
||||
id='2',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=False,
|
||||
data_source={'type': 'foobar'},
|
||||
prefill={'type': 'string', 'value': '4'},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {
|
||||
'type': 'json',
|
||||
'value': 'http://example.net/{{form_var_string}}',
|
||||
}
|
||||
data_source.store()
|
||||
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.get('http://example.net/None', json={'data': [{'id': '1', 'text': 'hello'}]})
|
||||
rsps.get(
|
||||
'http://example.net/foobar',
|
||||
json={'data': [{'id': '1', 'text': 'hello'}, {'id': '4', 'text': 'world'}]},
|
||||
)
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert len(rsps.calls) == 2
|
||||
assert rsps.calls[0].request.url == 'http://example.net/None'
|
||||
assert rsps.calls[1].request.url == 'http://example.net/foobar'
|
||||
assert [x.attrib['value'] for x in resp.pyquery('#form_f2 option')] == ['1', '4']
|
||||
assert resp.form['f2'].value == '4'
|
||||
assert not resp.pyquery('#form_error_f2').text()
|
||||
|
||||
|
||||
def test_form_page_item_with_computed_field_variable_data_source_prefill(pub):
|
||||
create_user(pub)
|
||||
formdef = create_formdef()
|
||||
formdef.data_class().wipe()
|
||||
formdef.fields = [
|
||||
fields.ComputedField(
|
||||
id='1',
|
||||
label='string',
|
||||
varname='string',
|
||||
value_template='foobar',
|
||||
),
|
||||
fields.ItemField(
|
||||
id='2',
|
||||
label='item',
|
||||
varname='item',
|
||||
required=False,
|
||||
data_source={'type': 'foobar'},
|
||||
prefill={'type': 'string', 'value': '4'},
|
||||
),
|
||||
]
|
||||
formdef.store()
|
||||
|
||||
NamedDataSource.wipe()
|
||||
data_source = NamedDataSource(name='foobar')
|
||||
data_source.data_source = {
|
||||
'type': 'json',
|
||||
'value': 'http://example.net/{{form_var_string}}',
|
||||
}
|
||||
data_source.store()
|
||||
|
||||
with responses.RequestsMock() as rsps:
|
||||
rsps.get(
|
||||
'http://example.net/foobar',
|
||||
json={'data': [{'id': '1', 'text': 'hello'}, {'id': '4', 'text': 'world'}]},
|
||||
)
|
||||
resp = get_app(pub).get('/test/')
|
||||
assert [x.attrib['value'] for x in resp.pyquery('#form_f2 option')] == ['1', '4']
|
||||
assert resp.form['f2'].value == '4'
|
||||
assert not resp.pyquery('#form_error_f2').text()
|
||||
|
||||
|
||||
def test_form_page_formula_prefill_items_field(pub):
|
||||
create_user(pub)
|
||||
formdef = create_formdef()
|
||||
|
|
|
@ -964,6 +964,9 @@ class FormDef(StorableObject):
|
|||
if widget:
|
||||
widget.live_condition_source = True
|
||||
widget.live_condition_fields = live_condition_fields[field.varname]
|
||||
elif field.key == 'computed':
|
||||
field.live_condition_source = True
|
||||
field.live_condition_fields = live_condition_fields[field.varname]
|
||||
|
||||
|
||||
@classmethod
|
||||
def get_field_data(cls, field, widget, raise_on_error=False):
|
||||
|
|
|
@ -522,13 +522,6 @@ class FormPage(Directory, TempfileDirectoryMixin, FormTemplateMixin):
|
|||
form.get_widget('f%s' % block.id).unparse()
|
||||
form.get_widget('f%s' % block.id).clear_error()
|
||||
|
||||
if field.key == 'item' and v:
|
||||
# mark field as invalid if the value is not allowed
|
||||
# (this is required by quixote>=3 as the value would
|
||||
# not be evaluated in the initial GET request of the
|
||||
# page).
|
||||
widget._parse(req)
|
||||
fpeters
commented
Ce code avait donc été ajouté pour quixote 3, c'est en forçant le passage dans _parse que l'erreur "choix invalide" était notée, parce qu'à ce stade du traitement les options possibles ne prennent pas encore en compte les données préremplies dans des champs précédents. Ce code avait donc été ajouté pour quixote 3, c'est en forçant le passage dans _parse que l'erreur "choix invalide" était notée, parce qu'à ce stade du traitement les options possibles ne prennent pas encore en compte les données préremplies dans des champs précédents.
|
||||
|
||||
had_prefill = True
|
||||
return had_prefill
|
||||
|
||||
|
@ -577,9 +570,8 @@ class FormPage(Directory, TempfileDirectoryMixin, FormTemplateMixin):
|
|||
magictoken = randbytes(8)
|
||||
has_new_magictoken = True
|
||||
|
||||
computed_data = self.handle_computed_fields(
|
||||
magictoken, self.formdef.get_computed_fields_from_page(page)
|
||||
)
|
||||
computed_fields_on_page = list(self.formdef.get_computed_fields_from_page(page))
|
||||
computed_data = self.handle_computed_fields(magictoken, computed_fields_on_page)
|
||||
if computed_data:
|
||||
form_data.update(computed_data)
|
||||
self.feed_current_data(magictoken)
|
||||
|
@ -687,18 +679,24 @@ class FormPage(Directory, TempfileDirectoryMixin, FormTemplateMixin):
|
|||
|
||||
if had_prefill:
|
||||
# pass over prefilled fields that are used as live source of item
|
||||
# fields
|
||||
# fields, update matching list of options of matching fields,
|
||||
# and mark fields as invalid if the selected value is not available.
|
||||
fpeters
commented
On passait déjà sur les champs pour actualiser les options présentées, on y ajoute ici l'appel à widget._parse() qui a été retiré plus haut. On passait déjà sur les champs pour actualiser les options présentées, on y ajoute ici l'appel à widget._parse() qui a été retiré plus haut.
|
||||
fields_to_update = set()
|
||||
for field in computed_fields_on_page:
|
||||
if getattr(field, 'live_condition_source', False):
|
||||
fields_to_update.update(field.live_condition_fields)
|
||||
fpeters
commented
Mais on ne prenait pas en compte la dépendance aux champs calculés, donc voilà ici. Mais on ne prenait pas en compte la dépendance aux champs calculés, donc voilà ici.
|
||||
for field, field_key, widget, dummy, dummy in self.iter_with_block_fields(form, displayed_fields):
|
||||
if getattr(widget, 'prefilled', False) and getattr(widget, 'live_condition_source', False):
|
||||
fields_to_update.update(widget.live_condition_fields)
|
||||
elif field in fields_to_update and field.key == 'item':
|
||||
elif field.key == 'item':
|
||||
kwargs = {}
|
||||
with get_publisher().substitutions.temporary_feed(transient_formdata, force_mode='lazy'):
|
||||
field.perform_more_widget_changes(form, kwargs)
|
||||
if 'options' in kwargs and 'options_with_attributes' in kwargs:
|
||||
widget.options = kwargs['options']
|
||||
widget.options_with_attributes = kwargs['options_with_attributes']
|
||||
if field in fields_to_update:
|
||||
field.perform_more_widget_changes(form, kwargs)
|
||||
if 'options' in kwargs:
|
||||
widget.options = kwargs['options']
|
||||
widget.options_with_attributes = kwargs.get('options_with_attributes')
|
||||
widget._parse(req)
|
||||
fpeters
commented
Voilà l'appel au widget._parse. Voilà l'appel au widget._parse.
|
||||
|
||||
self.set_page_title()
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Les champs calculés n'ont pas de widget associé mais on a besoin d'enregistrer quelque part l'info sur les champs qui en dépendent. (je pense qu'on ne peut pas faire ça de manière systématique parce que les blocs de champs peuvent donner plusieurs widgets pour un seul field, mais je n'ai pas creusé).