Projet

Général

Profil

0001-forms-add-support-for-live-list-contents-27173.patch

Frédéric Péters, 02 novembre 2018 09:55

Télécharger (17,3 ko)

Voir les différences:

Subject: [PATCH] forms: add support for live list contents (#27173)

 tests/test_form_pages.py             | 53 +++++++++++++++++++++++++
 tests/utilities.py                   |  1 +
 wcs/data_sources.py                  | 10 ++---
 wcs/fields.py                        |  4 +-
 wcs/formdef.py                       | 58 ++++++++++++++++++----------
 wcs/forms/root.py                    | 45 ++++++++++++++++++---
 wcs/qommon/static/js/qommon.forms.js | 13 +++++++
 wcs/qommon/substitution.py           |  9 ++++-
 8 files changed, 158 insertions(+), 35 deletions(-)
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
-