0001-forms-add-support-for-live-list-contents-27173.patch
tests/test_form_pages.py | ||
---|---|---|
5480 | 5480 |
assert 'name="f2"' in resp.body |
5481 | 5481 |
assert 'name="f4"' in resp.body |
5482 | 5482 |
resp = resp.form.submit('submit') |
5483 | ||
5484 |
def test_field_live_select_content(pub, http_requests): |
|
5485 |
FormDef.wipe() |
|
5486 |
formdef = FormDef() |
|
5487 |
formdef.name = 'Foo' |
|
5488 |
formdef.fields = [ |
|
5489 |
fields.StringField(type='string', id='1', label='Bar', size='40', |
|
5490 |
required=True, varname='bar'), |
|
5491 |
fields.StringField(type='string', id='2', label='Bar2', size='40', |
|
5492 |
required=True, varname='bar2'), |
|
5493 |
fields.ItemField(type='item', id='3', label='Foo', |
|
5494 |
data_source={ |
|
5495 |
'type': 'json', |
|
5496 |
'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}' |
|
5497 |
}), |
|
5498 |
] |
|
5499 |
formdef.store() |
|
5500 | ||
5501 |
app = get_app(pub) |
|
5502 |
resp = app.get('/foo/') |
|
5503 |
assert 'f1' in resp.form.fields |
|
5504 |
assert 'f2' in resp.form.fields |
|
5505 |
assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true' |
|
5506 |
assert resp.html.find('div', {'data-field-id': '3'}).find('select') |
|
5507 |
resp.form['f1'] = 'hello' |
|
5508 |
live_resp = app.post('/foo/live', params=resp.form.submit_fields()) |
|
5509 |
assert live_resp.json['result']['1']['visible'] |
|
5510 |
assert live_resp.json['result']['2']['visible'] |
|
5511 |
assert live_resp.json['result']['3']['visible'] |
|
5512 |
assert not 'items' in live_resp.json['result']['3'] |
|
5513 |
resp.form['f2'] = 'plop' |
|
5514 |
live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields()) |
|
5515 |
assert live_resp.json['result']['1']['visible'] |
|
5516 |
assert live_resp.json['result']['2']['visible'] |
|
5517 |
assert live_resp.json['result']['3']['visible'] |
|
5518 |
assert 'items' in live_resp.json['result']['3'] |
|
5519 |
resp.form['f3'].options = [] |
|
5520 |
for item in live_resp.json['result']['3']['items']: |
|
5521 |
# simulate javascript filling the <select> |
|
5522 |
resp.form['f3'].options.append((item['id'], False, item['text'])) |
|
5523 |
resp.form['f3'] = 'a' |
|
5524 |
resp = resp.form.submit('submit') |
|
5525 |
assert 'Check values then click submit.' in resp.body |
|
5526 |
assert 'name="f1"' in resp.body |
|
5527 |
assert 'name="f2"' in resp.body |
|
5528 |
assert 'name="f3"' in resp.body |
|
5529 |
resp = resp.form.submit('submit') |
|
5530 |
resp = resp.follow() |
|
5531 |
formdata = formdef.data_class().select()[0] |
|
5532 |
assert formdata.data['1'] == 'hello' |
|
5533 |
assert formdata.data['2'] == 'plop' |
|
5534 |
assert formdata.data['3'] == 'a' |
|
5535 |
assert formdata.data['3_display'] == 'b' |
tests/utilities.py | ||
---|---|---|
327 | 327 |
'http://remote.example.net/404-json': (404, '{"err": 1}', None), |
328 | 328 |
'http://remote.example.net/500': (500, 'internal server error', None), |
329 | 329 |
'http://remote.example.net/json': (200, '{"foo": "bar"}', None), |
330 |
'http://remote.example.net/json-list': (200, '{"data": [{"id": "a", "text": "b"}]}', None), |
|
330 | 331 |
'http://remote.example.net/json-err0': (200, '{"data": "foo", "err": 0}', None), |
331 | 332 |
'http://remote.example.net/json-err1': (200, '{"data": "", "err": 1}', None), |
332 | 333 |
'http://remote.example.net/json-errstr': (200, '{"data": "", "err": "bug"}', None), |
wcs/data_sources.py | ||
---|---|---|
95 | 95 |
return r.getvalue() |
96 | 96 | |
97 | 97 | |
98 |
def get_items(data_source, include_disabled=False): |
|
99 |
structured_items = get_structured_items(data_source) |
|
98 |
def get_items(data_source, include_disabled=False, mode=None):
|
|
99 |
structured_items = get_structured_items(data_source, mode=mode)
|
|
100 | 100 |
tupled_items = [] |
101 | 101 |
for item in structured_items: |
102 | 102 |
if item.get('disabled') and not include_disabled: |
... | ... | |
108 | 108 |
return tupled_items |
109 | 109 | |
110 | 110 | |
111 |
def get_structured_items(data_source): |
|
111 |
def get_structured_items(data_source, mode=None):
|
|
112 | 112 |
cache_duration = 0 |
113 | 113 |
if data_source.get('type') not in ('json', 'jsonp', 'formula'): |
114 | 114 |
# named data source |
... | ... | |
126 | 126 |
# - three elements, (id, text, key) |
127 | 127 |
# - two elements, (id, text) |
128 | 128 |
# - a single element, (id,) |
129 |
variables = get_publisher().substitutions.get_context_variables() |
|
129 |
variables = get_publisher().substitutions.get_context_variables(mode=mode)
|
|
130 | 130 |
global_eval_dict = get_publisher().get_global_eval_dict() |
131 | 131 |
global_eval_dict.update(data_source_functions) |
132 | 132 |
try: |
... | ... | |
162 | 162 |
return [] |
163 | 163 |
url = url.strip() |
164 | 164 |
if Template.is_template_string(url): |
165 |
vars = get_publisher().substitutions.get_context_variables() |
|
165 |
vars = get_publisher().substitutions.get_context_variables(mode=mode)
|
|
166 | 166 |
url = get_variadic_url(url, vars) |
167 | 167 | |
168 | 168 |
request = get_request() |
wcs/fields.py | ||
---|---|---|
1183 | 1183 |
self.items = [] |
1184 | 1184 |
WidgetField.__init__(self, **kwargs) |
1185 | 1185 | |
1186 |
def get_options(self): |
|
1186 |
def get_options(self, mode=None):
|
|
1187 | 1187 |
if self.data_source: |
1188 |
return [x[:3] for x in data_sources.get_items(self.data_source)] |
|
1188 |
return [x[:3] for x in data_sources.get_items(self.data_source, mode=mode)]
|
|
1189 | 1189 |
if self.items: |
1190 | 1190 |
return [(x, x) for x in self.items] |
1191 | 1191 |
return [] |
wcs/formdef.py | ||
---|---|---|
512 | 512 |
def get_display_id_format(self): |
513 | 513 |
return '[formdef_id]-[form_number_raw]' |
514 | 514 | |
515 |
def create_form(self, page=None, displayed_fields=None): |
|
515 |
def create_form(self, page=None, displayed_fields=None, transient_formdata=None):
|
|
516 | 516 |
form = Form(enctype="multipart/form-data", use_tokens=False) |
517 | 517 |
if self.appearance_keywords: |
518 | 518 |
form.attrs['class'] = 'quixote %s' % self.appearance_keywords |
... | ... | |
521 | 521 |
form.ERROR_NOTICE = _('There were errors processing the form and ' |
522 | 522 |
'you cannot go to the next page. Do ' |
523 | 523 |
'check below that you filled all fields correctly.') |
524 |
self.add_fields_to_form(form, page=page, displayed_fields=displayed_fields) |
|
524 |
self.add_fields_to_form(form, |
|
525 |
page=page, |
|
526 |
displayed_fields=displayed_fields, |
|
527 |
transient_formdata=transient_formdata) |
|
525 | 528 |
return form |
526 | 529 | |
527 |
def add_fields_to_form(self, form, page=None, displayed_fields=None, form_data=None): |
|
530 |
def add_fields_to_form(self, |
|
531 |
form, |
|
532 |
page=None, |
|
533 |
displayed_fields=None, |
|
534 |
form_data=None, # a dictionary, to fill fields |
|
535 |
transient_formdata=None): # a FormData |
|
528 | 536 |
current_page = 0 |
529 | 537 |
on_page = (page is None) |
530 | 538 |
for field in self.fields: |
... | ... | |
549 | 557 |
widget = field.add_to_form(form, value) |
550 | 558 |
widget.is_hidden = not(visible) |
551 | 559 |
widget.field = field |
560 |
if transient_formdata and not widget.is_hidden: |
|
561 |
transient_formdata.data.update(self.get_field_data(field, widget)) |
|
562 |
widget._parsed = False |
|
563 |
widget.error = None |
|
552 | 564 | |
553 | 565 |
def get_page(self, page_no): |
554 | 566 |
return [x for x in self.fields if x.type == 'page'][page_no] |
... | ... | |
611 | 623 | |
612 | 624 |
return form |
613 | 625 | |
626 |
def get_field_data(self, field, widget): |
|
627 |
d = {} |
|
628 |
d[field.id] = widget.parse() |
|
629 |
if d.get(field.id) and field.convert_value_from_str: |
|
630 |
d[field.id] = field.convert_value_from_str(d[field.id]) |
|
631 |
if d.get(field.id) and field.store_display_value: |
|
632 |
display_value = field.store_display_value(d, field.id) |
|
633 |
if display_value: |
|
634 |
d['%s_display' % field.id] = display_value |
|
635 |
elif d.has_key('%s_display' % field.id): |
|
636 |
del d['%s_display' % field.id] |
|
637 |
if d.get(field.id) and field.store_structured_value: |
|
638 |
structured_value = field.store_structured_value(d, field.id) |
|
639 |
if structured_value: |
|
640 |
d['%s_structured' % field.id] = structured_value |
|
641 |
elif '%s_structured' % field.id in d: |
|
642 |
del d['%s_structured' % field.id] |
|
643 |
if getattr(widget, 'cleanup', None): |
|
644 |
widget.cleanup() |
|
645 |
return d |
|
646 | ||
614 | 647 |
def get_data(self, form): |
615 | 648 |
d = {} |
616 | 649 |
for field in self.fields: |
617 | 650 |
widget = form.get_widget('f%s' % field.id) |
618 | 651 |
if widget: |
619 |
d[field.id] = widget.parse() |
|
620 |
if d.get(field.id) and field.convert_value_from_str: |
|
621 |
d[field.id] = field.convert_value_from_str(d[field.id]) |
|
622 |
if d.get(field.id) and field.store_display_value: |
|
623 |
display_value = field.store_display_value(d, field.id) |
|
624 |
if display_value: |
|
625 |
d['%s_display' % field.id] = display_value |
|
626 |
elif d.has_key('%s_display' % field.id): |
|
627 |
del d['%s_display' % field.id] |
|
628 |
if d.get(field.id) and field.store_structured_value: |
|
629 |
structured_value = field.store_structured_value(d, field.id) |
|
630 |
if structured_value: |
|
631 |
d['%s_structured' % field.id] = structured_value |
|
632 |
elif '%s_structured' % field.id in d: |
|
633 |
del d['%s_structured' % field.id] |
|
634 |
if widget and widget.cleanup: |
|
635 |
widget.cleanup() |
|
636 | ||
652 |
d.update(self.get_field_data(field, widget)) |
|
637 | 653 |
return d |
638 | 654 | |
639 | 655 |
def export_to_json(self, include_id=False, indent=None): |
wcs/forms/root.py | ||
---|---|---|
45 | 45 |
from qommon.logger import BotFilter |
46 | 46 |
from qommon import emails |
47 | 47 | |
48 |
from wcs import data_sources |
|
48 | 49 |
from wcs.categories import Category |
49 | 50 |
from wcs.formdef import FormDef |
50 | 51 |
from wcs.formdata import FormData |
... | ... | |
384 | 385 |
if not varname in live_condition_fields: |
385 | 386 |
live_condition_fields[varname] = [] |
386 | 387 |
live_condition_fields[varname].append(field) |
388 |
if field.key == 'item' and field.data_source: |
|
389 |
real_data_source = data_sources.get_real(field.data_source) |
|
390 |
if real_data_source.get('type') != 'json': |
|
391 |
continue |
|
392 |
varnames = re.findall(r'\bform[_\.]var[_\.]([a-zA-Z0-9_]+?)(?:_raw|\b)', |
|
393 |
real_data_source.get('value')) |
|
394 |
for varname in varnames: |
|
395 |
if not varname in live_condition_fields: |
|
396 |
live_condition_fields[varname] = [] |
|
397 |
live_condition_fields[varname].append(field) |
|
387 | 398 | |
388 | 399 |
for field in displayed_fields: |
389 | 400 |
if field.varname in live_condition_fields: |
... | ... | |
699 | 710 |
self.feed_current_data(magictoken) |
700 | 711 | |
701 | 712 |
submitted_fields = [] |
702 |
form = self.create_form(page=page, displayed_fields=submitted_fields) |
|
713 |
transient_formdata = self.get_transient_formdata() |
|
714 |
with get_publisher().substitutions.temporary_feed( |
|
715 |
transient_formdata, force_mode='lazy'): |
|
716 |
form = self.create_form(page=page, |
|
717 |
displayed_fields=submitted_fields, |
|
718 |
transient_formdata=transient_formdata) |
|
703 | 719 |
form.add_submit('previous') |
704 | 720 |
if self.has_draft_support(): |
705 | 721 |
form.add_submit('removedraft') |
... | ... | |
1005 | 1021 |
if not session: |
1006 | 1022 |
return result_error('missing session') |
1007 | 1023 | |
1008 |
formdata = self.get_transient_formdata() |
|
1009 |
get_publisher().substitutions.feed(formdata) |
|
1010 | ||
1011 | 1024 |
page_id = get_request().form.get('page_id') |
1012 | 1025 |
if page_id: |
1013 | 1026 |
for field in self.formdef.fields: |
... | ... | |
1017 | 1030 |
else: |
1018 | 1031 |
page = None |
1019 | 1032 | |
1033 |
formdata = self.get_transient_formdata() |
|
1034 |
get_publisher().substitutions.feed(formdata) |
|
1020 | 1035 |
displayed_fields = [] |
1021 |
form = self.create_form(page=page, displayed_fields=displayed_fields) |
|
1036 |
with get_publisher().substitutions.temporary_feed(formdata, force_mode='lazy'): |
|
1037 |
form = self.create_form( |
|
1038 |
page=page, |
|
1039 |
displayed_fields=displayed_fields, |
|
1040 |
transient_formdata=formdata) |
|
1022 | 1041 |
formdata.data.update(self.formdef.get_data(form)) |
1023 | 1042 | |
1024 | 1043 |
result = {} |
1025 | 1044 |
for field in displayed_fields: |
1026 | 1045 |
result[field.id] = {'visible': field.is_visible(formdata.data, self.formdef)} |
1027 | 1046 | |
1047 |
modified_field_varname = None |
|
1048 |
for field in displayed_fields: |
|
1049 |
if field.id == get_request().form.get('modified_field_id'): |
|
1050 |
modified_field_varname = field.varname |
|
1051 | ||
1052 |
for field in displayed_fields: |
|
1053 |
if field.key == 'item' and field.data_source: |
|
1054 |
real_data_source = data_sources.get_real(field.data_source) |
|
1055 |
if real_data_source.get('type') != 'json': |
|
1056 |
continue |
|
1057 |
varnames = re.findall(r'\bform[_\.]var[_\.]([a-zA-Z0-9_]+?)(?:_raw|\b)', |
|
1058 |
real_data_source.get('value')) |
|
1059 |
if modified_field_varname in varnames: |
|
1060 |
result[field.id]['items'] = [ |
|
1061 |
{'id': x[2], 'text': x[1]} for x in field.get_options(mode='lazy')] |
|
1062 | ||
1028 | 1063 |
return json.dumps({'result': result}) |
1029 | 1064 | |
1030 | 1065 |
def submitted(self, form, existing_formdata = None): |
wcs/qommon/static/js/qommon.forms.js | ||
---|---|---|
91 | 91 |
} else { |
92 | 92 |
$widget.hide(); |
93 | 93 |
} |
94 |
if (value.items) { |
|
95 |
// replace <select> contents |
|
96 |
var $select = $widget.find('select'); |
|
97 |
var current_value = $select.val(); |
|
98 |
$select.empty(); |
|
99 |
for (var i=0; i<value.items.length; i++) { |
|
100 |
var $option = $('<option></option>', {value: value.items[i].id, text: value.items[i].text}); |
|
101 |
if (value.items[i].id == current_value) { |
|
102 |
$option.attr('selected', 'selected'); |
|
103 |
} |
|
104 |
$option.appendTo($select); |
|
105 |
} |
|
106 |
} |
|
94 | 107 |
}); |
95 | 108 |
} |
96 | 109 |
}); |
wcs/qommon/substitution.py | ||
---|---|---|
32 | 32 |
class Substitutions(object): |
33 | 33 |
substitutions_dict = {} |
34 | 34 |
dynamic_sources = [] |
35 | ||
36 | 35 |
sources = None |
37 | 36 | |
37 |
_forced_mode = None |
|
38 | ||
38 | 39 |
def __init__(self): |
39 | 40 |
self.sources = [] |
40 | 41 | |
... | ... | |
77 | 78 |
self.invalidate_cache() |
78 | 79 | |
79 | 80 |
@contextmanager |
80 |
def temporary_feed(self, source): |
|
81 |
def temporary_feed(self, source, force_mode=None):
|
|
81 | 82 |
if source is None or source in self.sources: |
82 | 83 |
yield |
83 | 84 |
return |
... | ... | |
85 | 86 |
orig_sources, self.sources = self.sources, self.sources[:] |
86 | 87 |
self.sources.append(source) |
87 | 88 |
self.invalidate_cache() |
89 |
old_mode, self._forced_mode = self._forced_mode, force_mode |
|
88 | 90 |
yield |
91 |
self._forced_mode = old_mode |
|
89 | 92 |
self.sources = orig_sources |
90 | 93 |
self.invalidate_cache() |
91 | 94 | |
... | ... | |
95 | 98 |
delattr(self, '_cache_context_variables%r' % value) |
96 | 99 | |
97 | 100 |
def get_context_variables(self, mode=None): |
101 |
if self._forced_mode: |
|
102 |
mode = self._forced_mode |
|
98 | 103 |
lazy = mode in get_publisher().get_lazy_variables_modes() if mode else False |
99 | 104 |
d = getattr(self, '_cache_context_variables%r' % lazy, None) |
100 | 105 |
if d is not None: |
101 |
- |