Projet

Général

Profil

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

Frédéric Péters, 11 septembre 2016 15:54

Télécharger (26,3 ko)

Voir les différences:

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

 tests/test_datasource.py         |  59 +++++++++++++++------
 tests/test_form_pages.py         | 107 +++++++++++++++++++++++++++++++++++++++
 wcs/admin/forms.py               |   4 +-
 wcs/admin/settings.py            |   8 +--
 wcs/data_sources.py              |   9 +++-
 wcs/fields.py                    |  20 ++++++--
 wcs/qommon/form.py               | 102 +++++++++++++++++++++++--------------
 wcs/qommon/static/css/qommon.css |   4 ++
 8 files changed, 246 insertions(+), 67 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'})]
95

  
96
    # list of dicts
97
    plain_list = [{'id': 'foo', 'text': 'Foo'}, {'id': 'bar', 'text': 'Bar', 'disabled': True}]
98
    datasource = {'type': 'formula', 'value': repr(plain_list)}
99
    assert data_sources.get_items(datasource) == [
100
            ('foo', 'Foo', 'foo', {'id': 'foo', 'text': 'Foo'})]
101
    assert data_sources.get_items(datasource, include_disabled=True) == [
102
            ('foo', 'Foo', 'foo', {'id': 'foo', 'text': 'Foo'}),
103
            ('bar', 'Bar', 'bar', {'id': 'bar', 'text': 'Bar', 'disabled': True})]
87 104

  
88 105
def test_json_datasource():
89 106
    datasource = {'type': 'json', 'value': ''}
......
122 139
    json_file = open(json_file_path, 'w')
123 140
    json.dump({'data': [{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]}, json_file)
124 141
    json_file.close()
125
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
142
    assert data_sources.get_items(datasource) == [
143
            ('1', 'foo', '1', {'id': '1', 'text': 'foo'}),
144
            ('2', 'bar', '2', {'id': '2', 'text': 'bar'})]
126 145
    assert data_sources.get_structured_items(datasource) == [
127 146
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
128 147

  
......
131 150
    json.dump({'data': [{'id': '1', 'text': 'foo', 'more': 'xxx'},
132 151
                        {'id': '2', 'text': 'bar', 'more': 'yyy'}]}, json_file)
133 152
    json_file.close()
134
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
153
    assert data_sources.get_items(datasource) == [
154
            ('1', 'foo', '1', {'id': '1', 'text': 'foo', 'more': 'xxx'}),
155
            ('2', 'bar', '2', {'id': '2', 'text': 'bar', 'more': 'yyy'})]
135 156
    assert data_sources.get_structured_items(datasource) == [
136 157
            {'id': '1', 'text': 'foo', 'more': 'xxx'},
137 158
            {'id': '2', 'text': 'bar', 'more': 'yyy'}]
......
142 163
            return {'json_url': 'file://%s' % json_file_path}
143 164
    pub.substitutions.feed(JsonUrlPath())
144 165
    datasource = {'type': 'json', 'value': '[json_url]'}
145
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
166
    assert data_sources.get_items(datasource) == [
167
            ('1', 'foo', '1', {'id': '1', 'text': 'foo', 'more': 'xxx'}),
168
            ('2', 'bar', '2', {'id': '2', 'text': 'bar', 'more': 'yyy'})]
146 169

  
147 170
    # a json file with integer as 'id'
148 171
    json_file = open(json_file_path, 'w')
149 172
    json.dump({'data': [{'id': 1, 'text': 'foo'}, {'id': 2, 'text': 'bar'}]}, json_file)
150 173
    json_file.close()
151
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
174
    assert data_sources.get_items(datasource) == [
175
            ('1', 'foo', '1', {'id': 1, 'text': 'foo'}),
176
            ('2', 'bar', '2', {'id': 2, 'text': 'bar'})]
152 177
    assert data_sources.get_structured_items(datasource) == [
153 178
            {'id': 1, 'text': 'foo'}, {'id': 2, 'text': 'bar'}]
154 179

  
......
156 181
    json_file = open(json_file_path, 'w')
157 182
    json.dump({'data': [{'id': '1', 'text': ''}, {'id': '2'}]}, json_file)
158 183
    json_file.close()
159
    assert data_sources.get_items(datasource) == [('1', '', '1'), ('2', '2', '2')]
184
    assert data_sources.get_items(datasource) == [
185
            ('1', '', '1', {'id': '1', 'text': ''}),
186
            ('2', '2', '2', {'id': '2', 'text': '2'})]
160 187
    assert data_sources.get_structured_items(datasource) == [
161 188
            {'id': '1', 'text': ''},
162 189
            {'id': '2', 'text': '2'}]
......
193 220
    register_data_source_function(xxx)
194 221

  
195 222
    datasource = {'type': 'formula', 'value': 'xxx()'}
196
    assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')]
223
    assert data_sources.get_items(datasource) == [
224
            ('1', 'foo', '1', {'id': '1', 'text': 'foo'}),
225
            ('2', 'bar', '2', {'id': '2', 'text': 'bar'})]
197 226
    assert data_sources.get_structured_items(datasource) == [
198 227
            {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]
199 228

  
......
243 272
    data_source2 = NamedDataSource.select()[0]
244 273
    assert data_source2.data_source == data_source.data_source
245 274
    assert data_sources.get_items({'type': 'foobar'}) == [
246
        ('uné',) * 3,
247
        ('deux',) * 3,
275
        ('uné', 'uné', 'uné', {'id': 'uné', 'text': 'uné'}),
276
        ('deux', 'deux', 'deux', {'id': 'deux', 'text': 'deux'}),
248 277
    ]
249 278

  
250 279
    NamedDataSource.wipe()
......
258 287
        urllib2.urlopen.return_value.read.return_value = \
259 288
                '{"data": [{"id": 0, "text": "zéro"}, {"id": 1, "text": "uné"}, {"id": 2, "text": "deux"}]}'
260 289
        assert data_sources.get_items({'type': 'foobar'}) == [
261
            ('0', 'zéro', '0'),
262
            ('1', 'uné', '1'),
263
            ('2', 'deux', '2'),
290
            ('0', 'zéro', '0', {"id": 0, "text": "zéro"}),
291
            ('1', 'uné', '1', {"id": 1, "text": "uné"}),
292
            ('2', 'deux', '2', {"id": 2, "text": "deux"}),
264 293
        ]
tests/test_form_pages.py
3335 3335
    assert 'readonly' in resp.form['f0'].attrs
3336 3336
    assert not 'Check values then click submit.' in resp.body
3337 3337
    assert resp.form['f0'].value == 'foo@localhost'
3338

  
3339
def test_item_field_with_disabled_items(pub):
3340
    user = create_user(pub)
3341
    formdef = create_formdef()
3342
    formdef.data_class().wipe()
3343
    ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
3344
    formdef.fields = [fields.ItemField(id='0', label='string', type='item', data_source=ds)]
3345
    formdef.store()
3346

  
3347
    with mock.patch('urllib2.urlopen') as urlopen:
3348
        data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]}
3349
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3350
        resp = get_app(pub).get('/test/')
3351
        resp.form['f0'] = '1'
3352
        resp.form['f0'] = '2'
3353
        resp = resp.form.submit('submit') # -> validation page
3354
        resp = resp.form.submit('submit') # -> submit
3355
        assert formdef.data_class().select()[0].data['0'] == '2'
3356
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3357

  
3358
    formdef.data_class().wipe()
3359

  
3360
    with mock.patch('urllib2.urlopen') as urlopen:
3361
        data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]}
3362
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3363
        resp = get_app(pub).get('/test/')
3364
        assert '<option disabled="disabled" value="1">hello</option>' in resp.body
3365
        resp.form['f0'] = '1'
3366
        resp.form['f0'] = '2'
3367
        resp = resp.form.submit('submit') # -> validation page
3368
        resp = resp.form.submit('submit') # -> submit
3369
        assert formdef.data_class().select()[0].data['0'] == '2'
3370
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3371

  
3372
        resp = get_app(pub).get('/test/')
3373
        assert '<option disabled="disabled" value="1">hello</option>' in resp.body
3374
        resp.form['f0'] = '1'
3375
        resp = resp.form.submit('submit') # -> validation page
3376
        assert 'There were errors processing the form' in resp.body
3377

  
3378
    formdef.data_class().wipe()
3379
    formdef.fields = [fields.ItemField(id='0', label='string', type='item', data_source=ds, show_as_radio=True)]
3380
    formdef.store()
3381

  
3382
    with mock.patch('urllib2.urlopen') as urlopen:
3383
        data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]}
3384
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3385
        resp = get_app(pub).get('/test/')
3386
        resp.form['f0'] = '1'
3387
        resp.form['f0'] = '2'
3388
        resp = resp.form.submit('submit') # -> validation page
3389
        resp = resp.form.submit('submit') # -> submit
3390
        assert formdef.data_class().select()[0].data['0'] == '2'
3391
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3392

  
3393
    formdef.data_class().wipe()
3394

  
3395
    with mock.patch('urllib2.urlopen') as urlopen:
3396
        data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]}
3397
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3398
        resp = get_app(pub).get('/test/')
3399
        assert '<input disabled="disabled" type="radio" name="f0" value="1" />' in resp.body
3400
        resp.form['f0'] = '1'
3401
        resp.form['f0'] = '2'
3402
        resp = resp.form.submit('submit') # -> validation page
3403
        resp = resp.form.submit('submit') # -> submit
3404
        assert formdef.data_class().select()[0].data['0'] == '2'
3405
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
3406

  
3407
        resp = get_app(pub).get('/test/')
3408
        assert '<input disabled="disabled" type="radio" name="f0" value="1" />' in resp.body
3409
        resp.form['f0'] = '1'
3410
        resp = resp.form.submit('submit') # -> validation page
3411
        assert 'There were errors processing the form' in resp.body
3412

  
3413
def test_items_field_with_disabled_items(pub):
3414
    user = create_user(pub)
3415
    formdef = create_formdef()
3416
    formdef.data_class().wipe()
3417
    ds = {'type': 'json', 'value': 'http://remote.example.net/json'}
3418
    formdef.fields = [fields.ItemsField(id='0', label='string', type='items', data_source=ds)]
3419
    formdef.store()
3420

  
3421
    with mock.patch('urllib2.urlopen') as urlopen:
3422
        data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]}
3423
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3424
        resp = get_app(pub).get('/test/')
3425
        resp.form['f0$element1'].checked = True
3426
        resp.form['f0$element2'].checked = True
3427
        resp = resp.form.submit('submit') # -> validation page
3428
        resp = resp.form.submit('submit') # -> submit
3429
        assert formdef.data_class().select()[0].data['0'] == ['1', '2']
3430
        assert formdef.data_class().select()[0].data['0_display'] == 'hello, world'
3431

  
3432
    formdef.data_class().wipe()
3433

  
3434
    with mock.patch('urllib2.urlopen') as urlopen:
3435
        data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]}
3436
        urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data))
3437
        resp = get_app(pub).get('/test/')
3438
        assert resp.form['f0$element1'].attrs['disabled']
3439
        resp.form['f0$element1'].checked = True
3440
        resp.form['f0$element2'].checked = True
3441
        resp = resp.form.submit('submit') # -> validation page
3442
        resp = resp.form.submit('submit') # -> submit
3443
        assert formdef.data_class().select()[0].data['0'] == ['2']
3444
        assert formdef.data_class().select()[0].data['0_display'] == 'world'
wcs/admin/forms.py
1178 1178

  
1179 1179
        endpoints = []
1180 1180
        for status in self.formdef.workflow.get_endpoint_status():
1181
            endpoints.append((status.id, status.name))
1181
            endpoints.append((str(status.id), status.name))
1182 1182

  
1183 1183
        form = Form(enctype='multipart/form-data')
1184 1184
        form.add(DateWidget, 'before_request_date',
......
1187 1187
                required=True)
1188 1188
        form.add(CheckboxesWidget, 'endpoints', title=_('Status of the forms to anonymise'),
1189 1189
                value=[endpoint[0] for endpoint in endpoints],
1190
                elements=endpoints,
1190
                options=endpoints,
1191 1191
                inline=False,
1192 1192
                required=True)
1193 1193
        form.add_submit('submit', _('Submit'))
wcs/admin/settings.py
72 72
            methods.insert(0,
73 73
                    ('idp', _('Delegated to SAML identity provider')))
74 74
        form.add(CheckboxesWidget, 'methods', title = _('Methods'),
75
                value = identification_cfg.get('methods'),
76
                elements = methods,
77
                inline = False,
78
                required = True)
75
                value=identification_cfg.get('methods'),
76
                options=methods,
77
                inline=False,
78
                required=True)
79 79

  
80 80
        form.add(CheckboxWidget, 'use_user_hash', title=_('One-way association between user and forms'),
81 81
                value=bool(identification_cfg.get('use_user_hash', False)),
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_attributes'] = 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[:]
......
1247 1253
            return []
1248 1254

  
1249 1255
    def perform_more_widget_changes(self, form, kwargs, edit = True):
1250
        kwargs['elements'] = self.get_options()
1256
        kwargs['options'] = self.get_options()
1251 1257
        kwargs['max_choices'] = self.max_choices
1258
        if self.data_source:
1259
            items = data_sources.get_items(self.data_source, include_disabled=True)
1260
            kwargs['options'] = [x[:3] for x in items if not x[-1].get('disabled')]
1261
            kwargs['options_with_attributes'] = items[:]
1252 1262

  
1253
        if len(self.get_options()) > 3:
1263
        if len(kwargs['options']) > 3:
1254 1264
            kwargs['inline'] = False
1255 1265

  
1256 1266
    def fill_admin_form(self, form):
......
1973 1983
            ('sha1', _('SHA1')),
1974 1984
            ]
1975 1985
        form.add(CheckboxesWidget, 'formats', title=_('Storage formats'),
1976
                value=self.formats, elements=formats, inline=True)
1986
                value=self.formats, options=formats, inline=True)
1977 1987
        form.add(IntWidget, 'min_length', title=_('Minimum length'),
1978 1988
                value=self.min_length)
1979 1989
        form.add(IntWidget, 'max_length', title=_('Maximum password length'),
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_attributes = kwargs.pop('options_with_attributes', 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_attributes:
212
            options = self.options_with_attributes
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_attributes 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}
......
1100 1110
class CheckboxesWidget(CompositeWidget):
1101 1111
    readonly = False
1102 1112

  
1103
    def __init__(self, name, value = None, elements = None, **kwargs):
1113
    def __init__(self, name, value=None, options=None, **kwargs):
1104 1114
        CompositeWidget.__init__(self, name, value, **kwargs)
1105 1115
        self.element_names = {}
1106 1116

  
......
1112 1122
        self.inline = kwargs.get('inline', True)
1113 1123
        self.max_choices = int(kwargs.get('max_choices', 0) or 0)
1114 1124

  
1115
        for v in elements:
1116
            if type(v) is tuple:
1117
                title = v[1]
1118
                key = v[0]
1119
                if type(key) is int:
1120
                    name = 'element%d' % v[0]
1121
                elif type(key) in (str, htmltext):
1122
                    name = str('element%s' % v[0])
1123
                    key = str(key)
1124
                else:
1125
                    raise NotImplementedError()
1126
            else:
1127
                title = v
1128
                key = v
1129
                name = 'element%d' % len(self.element_names.keys())
1125
        self.options_with_attributes = kwargs.pop('options_with_attributes', None)
1126
        self.disabled_options = []
1127
        if self.options_with_attributes:
1128
            options = self.options_with_attributes
1129

  
1130
        for option in options:
1131
            key, title = option[:2]
1132
            key = str(key)
1133
            name = 'element%s' % key
1134

  
1135
            element_kwargs = kwargs.copy()
1136
            if self.options_with_attributes and option[-1].get('disabled'):
1137
                element_kwargs['disabled'] = 'disabled'
1138
                self.disabled_options.append(name)
1130 1139

  
1131 1140
            if value and key in value:
1132 1141
                checked = True
1133 1142
            else:
1134 1143
                checked = False
1135
            self.add(CheckboxWidget, name, title = title, value = checked, **kwargs)
1144
            self.add(CheckboxWidget, name, title=title, value=checked, **element_kwargs)
1136 1145
            self.element_names[name] = key
1137 1146

  
1138 1147
    def _parse(self, request):
......
1140 1149
            return
1141 1150
        values = []
1142 1151
        for name in self.element_names:
1152
            if name in self.disabled_options:
1153
                continue
1143 1154
            value = self.get(name)
1144 1155
            if value is True:
1145 1156
                values.append(self.element_names[name])
......
1168 1179
        else:
1169 1180
            r += htmltext('<ul>')
1170 1181
        for widget in self.get_widgets():
1171
            r += htmltext('<li><label>')
1182
            if widget.attrs and 'disabled' in widget.attrs:
1183
                r += htmltext('<li class="disabled"><label>')
1184
            else:
1185
                r += htmltext('<li><label>')
1172 1186
            if self.readonly:
1173 1187
                widget.attrs['disabled'] = 'disabled'
1174 1188
                if widget.value:
......
1533 1547

  
1534 1548

  
1535 1549
class SingleSelectHintWidget(SingleSelectWidget):
1550
    def __init__(self, name, value=None, **kwargs):
1551
        self.options_with_attributes = kwargs.pop('options_with_attributes', None)
1552
        super(SingleSelectHintWidget, self).__init__(name, value=value, **kwargs)
1536 1553

  
1537 1554
    def separate_hint(self):
1538 1555
        return (self.hint and len(self.hint) > 80)
......
1543 1560
            attrs.update(self.attrs)
1544 1561
        tags = [htmltag('select', name=self.name, **attrs)]
1545 1562
        options = self.options[:]
1563
        include_disabled = False
1564
        if self.options_with_attributes:
1565
            options = self.options_with_attributes
1566
            include_disabled = True
1546 1567
        if not self.separate_hint() and self.hint:
1547 1568
            r = htmltag('option', value='', selected=None)
1548 1569
            tags.append(r + htmlescape(self.hint) + htmltext('</option>'))
......
1550 1571
                # hint has been put as first element, skip the default empty
1551 1572
                # value.
1552 1573
                options = self.options[1:]
1553
        for object, description, key in options:
1574
        for option in options:
1575
            object, description, key = option[:3]
1576
            html_attrs = {}
1577
            html_attrs['value'] = key
1554 1578
            if self.is_selected(object):
1555
                selected = 'selected'
1556
            else:
1557
                selected = None
1579
                html_attrs['selected'] = 'selected'
1580
            if self.options_with_attributes and option[-1].get('disabled'):
1581
                html_attrs['disabled'] = 'disabled'
1558 1582
            if description is None:
1559 1583
                description = ''
1560
            r = htmltag('option', value=key, selected=selected)
1584
            r = htmltag('option', **html_attrs)
1561 1585
            tags.append(r + htmlescape(description) + htmltext('</option>'))
1562 1586
        tags.append(htmltext('</select>'))
1563 1587
        return htmltext('\n').join(tags)
wcs/qommon/static/css/qommon.css
269 269
	clear: both;
270 270
}
271 271

  
272
div.CheckboxesWidget div.content ul li.disabled {
273
	color: #aaa;
274
}
275

  
272 276
div.CheckboxesWidget div.content ul {
273 277
	list-style: none;
274 278
	padding: 0;
275
-