Projet

Général

Profil

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

Frédéric Péters, 10 octobre 2018 17:02

Télécharger (17,1 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                    | 41 +++++++++++++++++---
 wcs/qommon/static/js/qommon.forms.js | 13 +++++++
 wcs/qommon/substitution.py           |  9 ++++-
 8 files changed, 154 insertions(+), 35 deletions(-)
tests/test_form_pages.py
5345 5345
    assert 'name="f2"' in resp.body
5346 5346
    assert 'name="f4"' in resp.body
5347 5347
    resp = resp.form.submit('submit')
5348

  
5349
def test_field_live_select_content(pub, http_requests):
5350
    FormDef.wipe()
5351
    formdef = FormDef()
5352
    formdef.name = 'Foo'
5353
    formdef.fields = [
5354
        fields.StringField(type='string', id='1', label='Bar', size='40',
5355
            required=True, varname='bar'),
5356
        fields.StringField(type='string', id='2', label='Bar2', size='40',
5357
            required=True, varname='bar2'),
5358
        fields.ItemField(type='item', id='3', label='Foo',
5359
            data_source={
5360
                'type': 'json',
5361
                'value': '{% if form_var_bar2 %}http://remote.example.net/json-list?plop={{form_var_bar2}}{% endif %}'
5362
            }),
5363
    ]
5364
    formdef.store()
5365

  
5366
    app = get_app(pub)
5367
    resp = app.get('/foo/')
5368
    assert 'f1' in resp.form.fields
5369
    assert 'f2' in resp.form.fields
5370
    assert resp.html.find('div', {'data-field-id': '2'}).attrs['data-live-source'] == 'true'
5371
    assert resp.html.find('div', {'data-field-id': '3'}).find('select')
5372
    resp.form['f1'] = 'hello'
5373
    live_resp = app.post('/foo/live', params=resp.form.submit_fields())
5374
    assert live_resp.json['result']['1']['visible']
5375
    assert live_resp.json['result']['2']['visible']
5376
    assert live_resp.json['result']['3']['visible']
5377
    assert not 'items' in live_resp.json['result']['3']
5378
    resp.form['f2'] = 'plop'
5379
    live_resp = app.post('/foo/live?modified_field_id=2', params=resp.form.submit_fields())
5380
    assert live_resp.json['result']['1']['visible']
5381
    assert live_resp.json['result']['2']['visible']
5382
    assert live_resp.json['result']['3']['visible']
5383
    assert 'items' in live_resp.json['result']['3']
5384
    resp.form['f3'].options = []
5385
    for item in live_resp.json['result']['3']['items']:
5386
        # simulate javascript filling the <select>
5387
        resp.form['f3'].options.append((item['id'], False, item['text']))
5388
    resp.form['f3'] = 'a'
5389
    resp = resp.form.submit('submit')
5390
    assert 'Check values then click submit.' in resp.body
5391
    assert 'name="f1"' in resp.body
5392
    assert 'name="f2"' in resp.body
5393
    assert 'name="f3"' in resp.body
5394
    resp = resp.form.submit('submit')
5395
    resp = resp.follow()
5396
    formdata = formdef.data_class().select()[0]
5397
    assert formdata.data['1'] == 'hello'
5398
    assert formdata.data['2'] == 'plop'
5399
    assert formdata.data['3'] == 'a'
5400
    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]
......
604 616

  
605 617
        return form
606 618

  
619
    def get_field_data(self, field, widget):
620
        d = {}
621
        d[field.id] = widget.parse()
622
        if d.get(field.id) and field.convert_value_from_str:
623
            d[field.id] = field.convert_value_from_str(d[field.id])
624
        if d.get(field.id) and field.store_display_value:
625
            display_value = field.store_display_value(d, field.id)
626
            if display_value:
627
                d['%s_display' % field.id] = display_value
628
            elif d.has_key('%s_display' % field.id):
629
                del d['%s_display' % field.id]
630
        if d.get(field.id) and field.store_structured_value:
631
            structured_value = field.store_structured_value(d, field.id)
632
            if structured_value:
633
                d['%s_structured' % field.id] = structured_value
634
            elif '%s_structured' % field.id in d:
635
                del d['%s_structured' % field.id]
636
        if getattr(widget, 'cleanup', None):
637
            widget.cleanup()
638
        return d
639

  
607 640
    def get_data(self, form):
608 641
        d = {}
609 642
        for field in self.fields:
610 643
            widget = form.get_widget('f%s' % field.id)
611 644
            if widget:
612
                d[field.id] = widget.parse()
613
            if d.get(field.id) and field.convert_value_from_str:
614
                d[field.id] = field.convert_value_from_str(d[field.id])
615
            if d.get(field.id) and field.store_display_value:
616
                display_value = field.store_display_value(d, field.id)
617
                if display_value:
618
                    d['%s_display' % field.id] = display_value
619
                elif d.has_key('%s_display' % field.id):
620
                    del d['%s_display' % field.id]
621
            if d.get(field.id) and field.store_structured_value:
622
                structured_value = field.store_structured_value(d, field.id)
623
                if structured_value:
624
                    d['%s_structured' % field.id] = structured_value
625
                elif '%s_structured' % field.id in d:
626
                    del d['%s_structured' % field.id]
627
            if widget and widget.cleanup:
628
                widget.cleanup()
629

  
645
                d.update(self.get_field_data(field, widget))
630 646
        return d
631 647

  
632 648
    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
......
376 377
                    if not varname in live_condition_fields:
377 378
                        live_condition_fields[varname] = []
378 379
                    live_condition_fields[varname].append(field)
380
            if field.key == 'item' and field.data_source:
381
                real_data_source = data_sources.get_real(field.data_source)
382
                varnames = re.findall(r'\bform[_\.]var[_\.]([a-zA-Z0-9_]+?)(?:_raw|\b)',
383
                        real_data_source.get('value'))
384
                for varname in varnames:
385
                    if not varname in live_condition_fields:
386
                        live_condition_fields[varname] = []
387
                    live_condition_fields[varname].append(field)
379 388

  
380 389
        for field in displayed_fields:
381 390
            if field.varname in live_condition_fields:
......
700 709
            self.feed_current_data(magictoken)
701 710

  
702 711
            submitted_fields = []
703
            form = self.create_form(page=page, displayed_fields=submitted_fields)
712
            transient_formdata = self.get_transient_formdata()
713
            with get_publisher().substitutions.temporary_feed(
714
                    transient_formdata, force_mode='lazy'):
715
                form = self.create_form(page=page,
716
                        displayed_fields=submitted_fields,
717
                        transient_formdata=transient_formdata)
704 718
            form.add_submit('previous')
705 719
            if self.formdef.enable_tracking_codes:
706 720
                form.add_submit('removedraft')
......
1008 1022
        if not session:
1009 1023
            return result_error('missing session')
1010 1024

  
1011
        formdata = self.get_transient_formdata()
1012
        get_publisher().substitutions.feed(formdata)
1013

  
1014 1025
        page_id = get_request().form.get('page_id')
1015 1026
        if page_id:
1016 1027
            for field in self.formdef.fields:
......
1020 1031
        else:
1021 1032
            page = None
1022 1033

  
1034
        formdata = self.get_transient_formdata()
1035
        get_publisher().substitutions.feed(formdata)
1023 1036
        displayed_fields = []
1024
        form = self.create_form(page=page, displayed_fields=displayed_fields)
1037
        with get_publisher().substitutions.temporary_feed(formdata, force_mode='lazy'):
1038
            form = self.create_form(
1039
                    page=page,
1040
                    displayed_fields=displayed_fields,
1041
                    transient_formdata=formdata)
1025 1042
        formdata.data.update(self.formdef.get_data(form))
1026 1043

  
1027 1044
        result = {}
1028 1045
        for field in displayed_fields:
1029 1046
            result[field.id] = {'visible': field.is_visible(formdata.data, self.formdef)}
1030 1047

  
1048
        modified_field_varname = None
1049
        for field in displayed_fields:
1050
            if field.id == get_request().form.get('modified_field_id'):
1051
                modified_field_varname = field.varname
1052

  
1053
        for field in displayed_fields:
1054
            if field.key == 'item' and field.data_source:
1055
                real_data_source = data_sources.get_real(field.data_source)
1056
                varnames = re.findall(r'\bform[_\.]var[_\.]([a-zA-Z0-9_]+?)(?:_raw|\b)',
1057
                        real_data_source.get('value'))
1058
                if modified_field_varname in varnames:
1059
                    result[field.id]['items'] = [
1060
                            {'id': x[2], 'text': x[1]} for x in field.get_options(mode='lazy')]
1061

  
1031 1062
        return json.dumps({'result': result})
1032 1063

  
1033 1064
    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

  
......
69 70
            self.invalidate_cache()
70 71

  
71 72
    @contextmanager
72
    def temporary_feed(self, source):
73
    def temporary_feed(self, source, force_mode=None):
73 74
        if source is None or source in self.sources:
74 75
            yield
75 76
            return
......
77 78
        orig_sources, self.sources = self.sources, self.sources[:]
78 79
        self.sources.append(source)
79 80
        self.invalidate_cache()
81
        old_mode, self._forced_mode = self._forced_mode, force_mode
80 82
        yield
83
        self._forced_mode = old_mode
81 84
        self.sources = orig_sources
82 85
        self.invalidate_cache()
83 86

  
......
89 92
                delattr(request, '_cache_context_variables%r' % value)
90 93

  
91 94
    def get_context_variables(self, mode=None):
95
        if self._forced_mode:
96
            mode = self._forced_mode
92 97
        lazy = mode in get_publisher().get_lazy_variables_modes() if mode else False
93 98
        request = get_request()
94 99
        d = getattr(request, '_cache_context_variables%r' % lazy, None)
95
-