Projet

Général

Profil

0001-fields-make-it-possible-to-include-disabled-items-in.patch

Frédéric Péters, 31 août 2016 16:38

Télécharger (17,7 ko)

Voir les différences:

Subject: [PATCH] fields: make it possible to include disabled items in
 datasources (#12967)

This only works with JSON data sources and the Item field type.
 tests/test_datasource.py | 50 ++++++++++++++++++++++----------
 tests/test_form_pages.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++
 wcs/data_sources.py      |  9 ++++--
 wcs/fields.py            | 10 +++++--
 wcs/qommon/form.py       | 62 ++++++++++++++++++++++++++--------------
 5 files changed, 165 insertions(+), 40 deletions(-)
tests/test_datasource.py
58 58
def test_python_datasource():
59 59
    plain_list = [('1', 'foo'), ('2', 'bar')]
60 60
    datasource = {'type': 'formula', 'value': repr(plain_list)}
61
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
61
    assert data_sources.get_items(datasource) == [
62
            ('1', 'foo', '1', {'id': '1', 'text': 'foo'}),
63
            ('2', 'bar', '2', {'id': '2', 'text': 'bar'})]
62 64
    assert data_sources.get_structured_items(datasource) == [
63 65
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
64 66

  
......
73 75
    # three-item tuples
74 76
    plain_list = [('1', 'foo', 'a'), ('2', 'bar', 'b')]
75 77
    datasource = {'type': 'formula', 'value': repr(plain_list)}
76
    assert data_sources.get_items(datasource) == [('1', 'foo', 'a'), ('2', 'bar', 'b')]
78
    assert data_sources.get_items(datasource) == [
79
            ('1', 'foo', 'a', {'id': '1', 'key': 'a', 'text': 'foo'}),
80
            ('2', 'bar', 'b', {'id': '2', 'key': 'b', 'text': 'bar'})]
77 81

  
78 82
    # single-item tuples
79 83
    plain_list = [('foo', ), ('bar', )]
80 84
    datasource = {'type': 'formula', 'value': repr(plain_list)}
81
    assert data_sources.get_items(datasource) == [('foo', 'foo', 'foo'), ('bar', 'bar', 'bar')]
85
    assert data_sources.get_items(datasource) == [
86
            ('foo', 'foo', 'foo', {'id': 'foo', 'text': 'foo'}),
87
            ('bar', 'bar', 'bar', {'id': 'bar', 'text': 'bar'})]
82 88

  
83 89
    # list of strings
84 90
    plain_list = ['foo', 'bar']
85 91
    datasource = {'type': 'formula', 'value': repr(plain_list)}
86
    assert data_sources.get_items(datasource) == [('foo', 'foo', 'foo'), ('bar', 'bar', 'bar')]
92
    assert data_sources.get_items(datasource) == [
93
            ('foo', 'foo', 'foo', {'id': 'foo', 'text': 'foo'}),
94
            ('bar', 'bar', 'bar', {'id': 'bar', 'text': 'bar'})]
87 95

  
88 96
def test_json_datasource():
89 97
    datasource = {'type': 'json', 'value': ''}
......
122 130
    json_file = open(json_file_path, 'w')
123 131
    json.dump({'data': [{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]}, json_file)
124 132
    json_file.close()
125
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
133
    assert data_sources.get_items(datasource) == [
134
            ('1', 'foo', '1', {'id': '1', 'text': 'foo'}),
135
            ('2', 'bar', '2', {'id': '2', 'text': 'bar'})]
126 136
    assert data_sources.get_structured_items(datasource) == [
127 137
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
128 138

  
......
131 141
    json.dump({'data': [{'id': '1', 'text': 'foo', 'more': 'xxx'},
132 142
                        {'id': '2', 'text': 'bar', 'more': 'yyy'}]}, json_file)
133 143
    json_file.close()
134
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
144
    assert data_sources.get_items(datasource) == [
145
            ('1', 'foo', '1', {'id': '1', 'text': 'foo', 'more': 'xxx'}),
146
            ('2', 'bar', '2', {'id': '2', 'text': 'bar', 'more': 'yyy'})]
135 147
    assert data_sources.get_structured_items(datasource) == [
136 148
            {'id': '1', 'text': 'foo', 'more': 'xxx'},
137 149
            {'id': '2', 'text': 'bar', 'more': 'yyy'}]
......
142 154
            return {'json_url': 'file://%s' % json_file_path}
143 155
    pub.substitutions.feed(JsonUrlPath())
144 156
    datasource = {'type': 'json', 'value': '[json_url]'}
145
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
157
    assert data_sources.get_items(datasource) == [
158
            ('1', 'foo', '1', {'id': '1', 'text': 'foo', 'more': 'xxx'}),
159
            ('2', 'bar', '2', {'id': '2', 'text': 'bar', 'more': 'yyy'})]
146 160

  
147 161
    # a json file with integer as 'id'
148 162
    json_file = open(json_file_path, 'w')
149 163
    json.dump({'data': [{'id': 1, 'text': 'foo'}, {'id': 2, 'text': 'bar'}]}, json_file)
150 164
    json_file.close()
151
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
165
    assert data_sources.get_items(datasource) == [
166
            ('1', 'foo', '1', {'id': 1, 'text': 'foo'}),
167
            ('2', 'bar', '2', {'id': 2, 'text': 'bar'})]
152 168
    assert data_sources.get_structured_items(datasource) == [
153 169
            {'id': 1, 'text': 'foo'}, {'id': 2, 'text': 'bar'}]
154 170

  
......
156 172
    json_file = open(json_file_path, 'w')
157 173
    json.dump({'data': [{'id': '1', 'text': ''}, {'id': '2'}]}, json_file)
158 174
    json_file.close()
159
    assert data_sources.get_items(datasource) == [('1', '', '1'), ('2', '2', '2')]
175
    assert data_sources.get_items(datasource) == [
176
            ('1', '', '1', {'id': '1', 'text': ''}),
177
            ('2', '2', '2', {'id': '2', 'text': '2'})]
160 178
    assert data_sources.get_structured_items(datasource) == [
161 179
            {'id': '1', 'text': ''},
162 180
            {'id': '2', 'text': '2'}]
......
193 211
    register_data_source_function(xxx)
194 212

  
195 213
    datasource = {'type': 'formula', 'value': 'xxx()'}
196
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
214
    assert data_sources.get_items(datasource) == [
215
            ('1', 'foo', '1', {'id': '1', 'text': 'foo'}),
216
            ('2', 'bar', '2', {'id': '2', 'text': 'bar'})]
197 217
    assert data_sources.get_structured_items(datasource) == [
198 218
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
199 219

  
......
243 263
    data_source2 = NamedDataSource.select()[0]
244 264
    assert data_source2.data_source == data_source.data_source
245 265
    assert data_sources.get_items({'type': 'foobar'}) == [
246
        ('uné',) * 3,
247
        ('deux',) * 3,
266
        ('uné', 'uné', 'uné', {'id': 'uné', 'text': 'uné'}),
267
        ('deux', 'deux', 'deux', {'id': 'deux', 'text': 'deux'}),
248 268
    ]
249 269

  
250 270
    NamedDataSource.wipe()
......
258 278
        urllib2.urlopen.return_value.read.return_value = \
259 279
                '{"data": [{"id": 0, "text": "zéro"}, {"id": 1, "text": "uné"}, {"id": 2, "text": "deux"}]}'
260 280
        assert data_sources.get_items({'type': 'foobar'}) == [
261
            ('0', 'zéro', '0'),
262
            ('1', 'uné', '1'),
263
            ('2', 'deux', '2'),
281
            ('0', 'zéro', '0', {"id": 0, "text": "zéro"}),
282
            ('1', 'uné', '1', {"id": 1, "text": "uné"}),
283
            ('2', 'deux', '2', {"id": 2, "text": "deux"}),
264 284
        ]
tests/test_form_pages.py
3261 3261
    assert 'readonly' in resp.form['f0'].attrs
3262 3262
    assert not 'Check values then click submit.' in resp.body
3263 3263
    assert resp.form['f0'].value == 'foo@localhost'
3264

  
3265
def test_item_field_with_disabled_items(pub):
3266
    user = create_user(pub)
3267
    formdef = create_formdef()
3268
    formdef.data_class().wipe()
3269
    ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
3270
    formdef.fields = [fields.ItemField(id='0', label='string', data_source=ds)]
3271
    formdef.store()
3272

  
3273
    with mock.patch('urllib2.urlopen') as urlopen:
3274
        data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]}
3275
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3276
        resp = get_app(pub).get('/test/')
3277
        resp.form['f0'] = '1'
3278
        resp.form['f0'] = '2'
3279
        resp = resp.form.submit('submit') # -> validation page
3280
        resp = resp.form.submit('submit') # -> submit
3281
        assert formdef.data_class().select()[0].data['0'] == '2'
3282
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3283

  
3284
    formdef.data_class().wipe()
3285

  
3286
    with mock.patch('urllib2.urlopen') as urlopen:
3287
        data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]}
3288
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3289
        resp = get_app(pub).get('/test/')
3290
        assert '<option disabled="disabled" value="1">hello</option>' in resp.body
3291
        resp.form['f0'] = '1'
3292
        resp.form['f0'] = '2'
3293
        resp = resp.form.submit('submit') # -> validation page
3294
        resp = resp.form.submit('submit') # -> submit
3295
        assert formdef.data_class().select()[0].data['0'] == '2'
3296
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3297

  
3298
        resp = get_app(pub).get('/test/')
3299
        assert '<option disabled="disabled" value="1">hello</option>' in resp.body
3300
        resp.form['f0'] = '1'
3301
        resp = resp.form.submit('submit') # -> validation page
3302
        assert 'There were errors processing the form' in resp.body
3303

  
3304
    formdef.data_class().wipe()
3305
    formdef.fields = [fields.ItemField(id='0', label='string', data_source=ds, show_as_radio=True)]
3306
    formdef.store()
3307

  
3308
    with mock.patch('urllib2.urlopen') as urlopen:
3309
        data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]}
3310
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3311
        resp = get_app(pub).get('/test/')
3312
        resp.form['f0'] = '1'
3313
        resp.form['f0'] = '2'
3314
        resp = resp.form.submit('submit') # -> validation page
3315
        resp = resp.form.submit('submit') # -> submit
3316
        assert formdef.data_class().select()[0].data['0'] == '2'
3317
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3318

  
3319
    formdef.data_class().wipe()
3320

  
3321
    with mock.patch('urllib2.urlopen') as urlopen:
3322
        data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]}
3323
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3324
        resp = get_app(pub).get('/test/')
3325
        assert '<input disabled="disabled" type="radio" name="f0" value="1" />' in resp.body
3326
        resp.form['f0'] = '1'
3327
        resp.form['f0'] = '2'
3328
        resp = resp.form.submit('submit') # -> validation page
3329
        resp = resp.form.submit('submit') # -> submit
3330
        assert formdef.data_class().select()[0].data['0'] == '2'
3331
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3332

  
3333
        resp = get_app(pub).get('/test/')
3334
        assert '<input disabled="disabled" type="radio" name="f0" value="1" />' in resp.body
3335
        resp.form['f0'] = '1'
3336
        resp = resp.form.submit('submit') # -> validation page
3337
        assert 'There were errors processing the form' in resp.body
wcs/data_sources.py
84 84
        return r.getvalue()
85 85

  
86 86

  
87
def get_items(data_source):
87
def get_items(data_source, include_disabled=False):
88 88
    structured_items = get_structured_items(data_source)
89 89
    tupled_items = []
90 90
    for item in structured_items:
91
        tupled_items.append((str(item['id']), str(item['text']), str(item.get('key', item['id']))))
91
        if item.get('disabled') and not include_disabled:
92
            continue
93
        tupled_items.append((str(item['id']),
94
                             str(item['text']),
95
                             str(item.get('key', item['id'])),
96
                             item))
92 97
    return tupled_items
93 98

  
94 99

  
wcs/fields.py
1056 1056

  
1057 1057
    def get_options(self):
1058 1058
        if self.data_source:
1059
            return data_sources.get_items(self.data_source)
1059
            return [x[:3] for x in data_sources.get_items(self.data_source)]
1060 1060
        if self.items:
1061 1061
            return self.items[:]
1062 1062
        return []
......
1066 1066
        if real_data_source and real_data_source.get('type') == 'jsonp':
1067 1067
            kwargs['url'] = real_data_source.get('value')
1068 1068
            self.widget_class = JsonpSingleSelectWidget
1069
        elif self.items:
1070
            kwargs['options'] = self.items[:]
1071
        elif self.data_source:
1072
            items = data_sources.get_items(self.data_source, include_disabled=True)
1073
            kwargs['options'] = [x[:3] for x in items if not x[-1].get('disabled')]
1074
            kwargs['options_with_disabled'] = items[:]
1069 1075
        else:
1070 1076
            kwargs['options'] = self.get_options()
1071 1077
        if not kwargs.get('options'):
......
1239 1245
        if self.data_source:
1240 1246
            if self._cached_data_source:
1241 1247
                return self._cached_data_source
1242
            self._cached_data_source = data_sources.get_items(self.data_source)
1248
            self._cached_data_source = [x[:3] for x in data_sources.get_items(self.data_source)]
1243 1249
            return self._cached_data_source[:]
1244 1250
        elif self.items:
1245 1251
            return self.items[:]
wcs/qommon/form.py
200 200
SubmitWidget.render_content = submit_render_content
201 201

  
202 202

  
203
def radiobuttons_render_content(self):
204
    tags = []
205
    for object, description, key in self.options:
206
        if self.is_selected(object):
207
            checked = 'checked'
208
        else:
209
            checked = None
210
        r = htmltag("input", xml_end=True,
211
                    type="radio",
212
                    name=self.name,
213
                    value=key,
214
                    checked=checked,
215
                    **self.attrs)
216
        tags.append(htmltext('<label>') + r + htmlescape(description) + htmltext('</label>'))
217
    return htmlescape(self.delim).join(tags)
218
RadiobuttonsWidget.render_content = radiobuttons_render_content
203
class RadiobuttonsWidget(quixote.form.RadiobuttonsWidget):
204
    def __init__(self, name, value=None, **kwargs):
205
        self.options_with_disabled = kwargs.pop('options_with_disabled', None)
206
        super(RadiobuttonsWidget, self).__init__(name, value=value, **kwargs)
219 207

  
208
    def render_content(self):
209
        include_disabled = False
210
        options = self.options[:]
211
        if self.options_with_disabled:
212
            options = self.options_with_disabled
213
            include_disabled = True
214

  
215
        tags = []
216
        for option in options:
217
            object, description, key = option[:3]
218
            html_attrs = self.attrs.copy()
219
            if self.is_selected(object):
220
                html_attrs['checked'] = 'checked'
221
            if self.options_with_disabled and option[-1].get('disabled'):
222
                html_attrs['disabled'] = 'disabled'
223
            r = htmltag("input", xml_end=True,
224
                        type="radio",
225
                        name=self.name,
226
                        value=key,
227
                        **html_attrs)
228
            tags.append(htmltext('<label>') + r + htmlescape(description) + htmltext('</label>'))
229
        return htmlescape(self.delim).join(tags)
220 230

  
221 231
def checkbox_render_content(self):
222 232
    attrs = {'id': 'form_' + self.name}
......
1523 1533

  
1524 1534

  
1525 1535
class SingleSelectHintWidget(SingleSelectWidget):
1536
    def __init__(self, name, value=None, **kwargs):
1537
        self.options_with_disabled = kwargs.pop('options_with_disabled', None)
1538
        super(SingleSelectHintWidget, self).__init__(name, value=value, **kwargs)
1526 1539

  
1527 1540
    def separate_hint(self):
1528 1541
        return (self.hint and len(self.hint) > 80)
......
1533 1546
            attrs.update(self.attrs)
1534 1547
        tags = [htmltag('select', name=self.name, **attrs)]
1535 1548
        options = self.options[:]
1549
        include_disabled = False
1550
        if self.options_with_disabled:
1551
            options = self.options_with_disabled
1552
            include_disabled = True
1536 1553
        if not self.separate_hint() and self.hint:
1537 1554
            r = htmltag('option', value='', selected=None)
1538 1555
            tags.append(r + htmlescape(self.hint) + htmltext('</option>'))
......
1540 1557
                # hint has been put as first element, skip the default empty
1541 1558
                # value.
1542 1559
                options = self.options[1:]
1543
        for object, description, key in options:
1560
        for option in options:
1561
            object, description, key = option[:3]
1562
            html_attrs = {}
1563
            html_attrs['value'] = key
1544 1564
            if self.is_selected(object):
1545
                selected = 'selected'
1546
            else:
1547
                selected = None
1565
                html_attrs['selected'] = 'selected'
1566
            if self.options_with_disabled and option[-1].get('disabled'):
1567
                html_attrs['disabled'] = 'disabled'
1548 1568
            if description is None:
1549 1569
                description = ''
1550
            r = htmltag('option', value=key, selected=selected)
1570
            r = htmltag('option', **html_attrs)
1551 1571
            tags.append(r + htmlescape(description) + htmltext('</option>'))
1552 1572
        tags.append(htmltext('</select>'))
1553 1573
        return htmltext('\n').join(tags)
1554
-