forms: add dynamic source support to items field (#53763)

This commit is contained in:
Frédéric Péters 2022-07-02 12:20:44 +02:00
parent f278716206
commit 40e934a92c
6 changed files with 128 additions and 15 deletions

View File

@ -5,7 +5,7 @@ import json
from unittest import mock
import pytest
from webtest import Hidden
from webtest import Checkbox, Hidden
from wcs import fields
from wcs.blocks import BlockDef
@ -591,6 +591,89 @@ def test_field_live_select(pub, http_requests):
assert len(live_resp.json['result']['3']['items']) == 1
def test_field_live_items_checkboxes(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemsField(
type='items',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('ul')
assert not resp.html.find('div', {'data-field-id': '3'}).find('ul li')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 1
# simulate js, add relevant checkboxes
for option in live_resp.json['result']['3']['items']:
checkbox_name = '%s$element%s' % (
resp.html.find('div', {'data-field-id': '3'}).attrs['data-widget-name'],
option['id'],
)
resp.form.fields[checkbox_name] = Checkbox(
form=resp.form, name=checkbox_name, tag='input', value='yes', pos=10
)
resp.form.field_order.append((checkbox_name, resp.form.fields[checkbox_name]))
resp.form.fields[checkbox_name].checked = True
resp = resp.form.submit('submit') # -> validation
assert resp.pyquery('.CheckboxesWidget li label').text() == 'b'
resp = resp.form.submit('submit') # -> submitted
assert formdef.data_class().select()[0].data['3'] == ['a']
assert formdef.data_class().select()[0].data['3_display'] == 'b'
def test_field_live_items_select_multiple(pub, http_requests):
FormDef.wipe()
formdef = FormDef()
formdef.name = 'Foo'
formdef.fields = [
fields.StringField(type='string', id='2', label='Bar2', size='40', required=True, varname='bar2'),
fields.ItemsField(
type='items',
display_mode='autocomplete',
id='3',
label='Foo',
data_source={
'type': 'json',
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}',
},
),
]
formdef.store()
formdef.data_class().wipe()
app = get_app(pub)
resp = app.get('/foo/')
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
assert resp.html.find('div', {'data-field-id': '3'}).find('select')
assert not resp.html.find('div', {'data-field-id': '3'}).find('select option')
resp.form['f2'] = 'plop'
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
assert len(live_resp.json['result']['3']['items']) == 1
# simulate js, add relevant options
resp.form['f3[]'].options = [(x['id'], False, x['text']) for x in live_resp.json['result']['3']['items']]
resp.form['f3[]'].select_multiple(['a'])
resp = resp.form.submit('submit') # -> validation
assert resp.pyquery('select option[selected]').text() == 'b'
resp = resp.form.submit('submit') # -> submitted
assert formdef.data_class().select()[0].data['3'] == ['a']
assert formdef.data_class().select()[0].data['3_display'] == 'b'
def test_field_live_template_content(pub, http_requests):
FormDef.wipe()
formdef = FormDef()

View File

@ -2068,6 +2068,15 @@ class ItemFieldMixin:
except KeyError:
return None
def get_extended_options(self):
if self.data_source:
return data_sources.get_structured_items(
self.data_source, mode='lazy', include_disabled=self.display_disabled_items
)
if self.items:
return [{'id': x, 'text': x} for x in self.items]
return []
class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin):
key = 'item'
@ -2128,15 +2137,6 @@ class ItemField(WidgetField, MapOptionsMixin, ItemFieldMixin):
return data_sources.get_id_by_option_text(self.data_source, text_value)
return text_value
def get_extended_options(self):
if self.data_source:
return data_sources.get_structured_items(
self.data_source, mode='lazy', include_disabled=self.display_disabled_items
)
if self.items:
return [{'id': x, 'text': x} for x in self.items]
return []
def get_display_mode(self, data_source=None):
if not data_source:
data_source = data_sources.get_object(self.data_source)

View File

@ -967,7 +967,7 @@ class FormDef(StorableObject):
if varname not in live_condition_fields:
live_condition_fields[varname] = []
live_condition_fields[varname].append(field)
if field.key == 'item' and field.data_source:
if field.key in ('item', 'items') and field.data_source:
data_source = data_sources.get_object(field.data_source)
if data_source.type not in ('json', 'geojson') and not data_source.type.startswith(
'carddef:'

View File

@ -785,7 +785,7 @@ class FormStatusPage(Directory, FormTemplateMixin):
break
for field in displayed_fields:
if field.key == 'item' and field.data_source:
if field.key in ('item', 'items') and field.data_source:
data_source = data_sources.get_object(field.data_source)
if data_source.type not in ('json', 'geojson') and not data_source.type.startswith(
'carddef:'
@ -793,7 +793,7 @@ class FormStatusPage(Directory, FormTemplateMixin):
continue
varnames = data_source.get_referenced_varnames(field.formdef)
if (not modified_field_varnames or modified_field_varnames.intersection(varnames)) and (
field.display_mode == 'autocomplete' and data_source.can_jsonp()
field.display_mode == 'autocomplete' and data_source.can_jsonp() and field.type != 'items'
):
# computed earlier, in perform_more_widget_changes, when the field
# was added to the form

View File

@ -514,6 +514,35 @@ $(function() {
$label.appendTo($content);
}
$hint.appendTo($content);
} else if (value.items && $widget.is('.CheckboxesWidget')) {
var widget_name = $widget.data('widget-name');
var $ul = $widget.find('ul');
var current_value = $ul.find('input[type=checkbox]'
).filter(function() {return this.checked}
).map(function() {return this.name;}
).toArray();
var base_for_name = $ul.data('base-for-name');
var input_name = $widget.data('widget-name');
$ul.empty();
for (var i=0; i<value.items.length; i++) {
var $li = $('<li>');
var $label = $('<label>', {'for': base_for_name + i});
var $input = $('<input>', {
type: 'checkbox', 'id': base_for_name + i,
value: 'yes', name: widget_name + '$element' + value.items[i].id});
if (current_value.indexOf(widget_name + '$element' + value.items[i].id) != -1) {
$input.attr('checked', 'checked');
}
if (value.items[i].disabled) {
$input.prop('disabled', true);
$li.addClass('disabled');
}
var $span = $('<span>', {text: value.items[i].text});
$input.appendTo($label);
$span.appendTo($label);
$label.appendTo($li);
$li.appendTo($ul);
}
} else if (value.items) {
// replace <select> contents
var $select = $widget.find('select');
@ -527,7 +556,8 @@ $(function() {
}
for (var i=0; i<value.items.length; i++) {
var $option = $('<option></option>', {value: value.items[i].id, text: value.items[i].text});
if (value.items[i].id == current_value) {
if ((Array.isArray(current_value) && current_value.indexOf(value.items[i].id.toString()) != -1) ||
value.items[i].id == current_value) {
$option.attr('selected', 'selected');
value.items[i].selected = true;
}

View File

@ -1,7 +1,7 @@
{% extends "qommon/forms/widget.html" %}
{% block widget-control %}
<ul {% if widget.inline %}class="inline"{% endif %}>
<ul {% if widget.inline %}class="inline"{% endif %} data-base-for-name="{{ widget.get_name_for_id }}_op_">
{% for option in widget.get_options %}
<li {% if option.disabled %}class="disabled"{% endif %}><label for="{{ widget.get_name_for_id }}_op_{{ forloop.counter0 }}"><input
id="{{ widget.get_name_for_id }}_op_{{ forloop.counter0 }}"