From 58343476aaebc6dc66db78d16f61e3ea00efdbca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fr=C3=A9d=C3=A9ric=20P=C3=A9ters?= Date: Wed, 31 Aug 2016 16:35:30 +0200 Subject: [PATCH] fields: make it possible to include disabled items in datasources (#12967) --- tests/test_datasource.py | 59 +++++++++++----- tests/test_fields.py | 24 +++++-- tests/test_form_pages.py | 142 +++++++++++++++++++++++++++++++++++++++ wcs/admin/forms.py | 4 +- wcs/admin/settings.py | 8 +-- wcs/backoffice/management.py | 9 +-- wcs/data_sources.py | 9 ++- wcs/fields.py | 42 +++++++++--- wcs/qommon/form.py | 102 +++++++++++++++++----------- wcs/qommon/static/css/qommon.css | 4 ++ 10 files changed, 322 insertions(+), 81 deletions(-) diff --git a/tests/test_datasource.py b/tests/test_datasource.py index 0e837a4..e6a94b7 100644 --- a/tests/test_datasource.py +++ b/tests/test_datasource.py @@ -58,7 +58,9 @@ def test_item_field_python_datasource(): def test_python_datasource(): plain_list = [('1', 'foo'), ('2', 'bar')] datasource = {'type': 'formula', 'value': repr(plain_list)} - assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', '1', {'id': '1', 'text': 'foo'}), + ('2', 'bar', '2', {'id': '2', 'text': 'bar'})] assert data_sources.get_structured_items(datasource) == [ {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}] @@ -73,17 +75,32 @@ def test_python_datasource(): # three-item tuples plain_list = [('1', 'foo', 'a'), ('2', 'bar', 'b')] datasource = {'type': 'formula', 'value': repr(plain_list)} - assert data_sources.get_items(datasource) == [('1', 'foo', 'a'), ('2', 'bar', 'b')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', 'a', {'id': '1', 'key': 'a', 'text': 'foo'}), + ('2', 'bar', 'b', {'id': '2', 'key': 'b', 'text': 'bar'})] # single-item tuples plain_list = [('foo', ), ('bar', )] datasource = {'type': 'formula', 'value': repr(plain_list)} - assert data_sources.get_items(datasource) == [('foo', 'foo', 'foo'), ('bar', 'bar', 'bar')] + assert data_sources.get_items(datasource) == [ + ('foo', 'foo', 'foo', {'id': 'foo', 'text': 'foo'}), + ('bar', 'bar', 'bar', {'id': 'bar', 'text': 'bar'})] # list of strings plain_list = ['foo', 'bar'] datasource = {'type': 'formula', 'value': repr(plain_list)} - assert data_sources.get_items(datasource) == [('foo', 'foo', 'foo'), ('bar', 'bar', 'bar')] + assert data_sources.get_items(datasource) == [ + ('foo', 'foo', 'foo', {'id': 'foo', 'text': 'foo'}), + ('bar', 'bar', 'bar', {'id': 'bar', 'text': 'bar'})] + + # list of dicts + plain_list = [{'id': 'foo', 'text': 'Foo'}, {'id': 'bar', 'text': 'Bar', 'disabled': True}] + datasource = {'type': 'formula', 'value': repr(plain_list)} + assert data_sources.get_items(datasource) == [ + ('foo', 'Foo', 'foo', {'id': 'foo', 'text': 'Foo'})] + assert data_sources.get_items(datasource, include_disabled=True) == [ + ('foo', 'Foo', 'foo', {'id': 'foo', 'text': 'Foo'}), + ('bar', 'Bar', 'bar', {'id': 'bar', 'text': 'Bar', 'disabled': True})] def test_json_datasource(): datasource = {'type': 'json', 'value': ''} @@ -122,7 +139,9 @@ def test_json_datasource(): json_file = open(json_file_path, 'w') json.dump({'data': [{'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}]}, json_file) json_file.close() - assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', '1', {'id': '1', 'text': 'foo'}), + ('2', 'bar', '2', {'id': '2', 'text': 'bar'})] assert data_sources.get_structured_items(datasource) == [ {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}] @@ -131,7 +150,9 @@ def test_json_datasource(): json.dump({'data': [{'id': '1', 'text': 'foo', 'more': 'xxx'}, {'id': '2', 'text': 'bar', 'more': 'yyy'}]}, json_file) json_file.close() - assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', '1', {'id': '1', 'text': 'foo', 'more': 'xxx'}), + ('2', 'bar', '2', {'id': '2', 'text': 'bar', 'more': 'yyy'})] assert data_sources.get_structured_items(datasource) == [ {'id': '1', 'text': 'foo', 'more': 'xxx'}, {'id': '2', 'text': 'bar', 'more': 'yyy'}] @@ -142,13 +163,17 @@ def test_json_datasource(): return {'json_url': 'file://%s' % json_file_path} pub.substitutions.feed(JsonUrlPath()) datasource = {'type': 'json', 'value': '[json_url]'} - assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', '1', {'id': '1', 'text': 'foo', 'more': 'xxx'}), + ('2', 'bar', '2', {'id': '2', 'text': 'bar', 'more': 'yyy'})] # a json file with integer as 'id' json_file = open(json_file_path, 'w') json.dump({'data': [{'id': 1, 'text': 'foo'}, {'id': 2, 'text': 'bar'}]}, json_file) json_file.close() - assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', '1', {'id': 1, 'text': 'foo'}), + ('2', 'bar', '2', {'id': 2, 'text': 'bar'})] assert data_sources.get_structured_items(datasource) == [ {'id': 1, 'text': 'foo'}, {'id': 2, 'text': 'bar'}] @@ -156,7 +181,9 @@ def test_json_datasource(): json_file = open(json_file_path, 'w') json.dump({'data': [{'id': '1', 'text': ''}, {'id': '2'}]}, json_file) json_file.close() - assert data_sources.get_items(datasource) == [('1', '', '1'), ('2', '2', '2')] + assert data_sources.get_items(datasource) == [ + ('1', '', '1', {'id': '1', 'text': ''}), + ('2', '2', '2', {'id': '2', 'text': '2'})] assert data_sources.get_structured_items(datasource) == [ {'id': '1', 'text': ''}, {'id': '2', 'text': '2'}] @@ -193,7 +220,9 @@ def test_register_data_source_function(): register_data_source_function(xxx) datasource = {'type': 'formula', 'value': 'xxx()'} - assert data_sources.get_items(datasource) == [('1', 'foo', '1'), ('2', 'bar', '2')] + assert data_sources.get_items(datasource) == [ + ('1', 'foo', '1', {'id': '1', 'text': 'foo'}), + ('2', 'bar', '2', {'id': '2', 'text': 'bar'})] assert data_sources.get_structured_items(datasource) == [ {'id': '1', 'text': 'foo'}, {'id': '2', 'text': 'bar'}] @@ -243,8 +272,8 @@ def test_data_source_unicode(): data_source2 = NamedDataSource.select()[0] assert data_source2.data_source == data_source.data_source assert data_sources.get_items({'type': 'foobar'}) == [ - ('uné',) * 3, - ('deux',) * 3, + ('uné', 'uné', 'uné', {'id': 'uné', 'text': 'uné'}), + ('deux', 'deux', 'deux', {'id': 'deux', 'text': 'deux'}), ] NamedDataSource.wipe() @@ -258,7 +287,7 @@ def test_data_source_unicode(): urllib2.urlopen.return_value.read.return_value = \ '{"data": [{"id": 0, "text": "zéro"}, {"id": 1, "text": "uné"}, {"id": 2, "text": "deux"}]}' assert data_sources.get_items({'type': 'foobar'}) == [ - ('0', 'zéro', '0'), - ('1', 'uné', '1'), - ('2', 'deux', '2'), + ('0', 'zéro', '0', {"id": 0, "text": "zéro"}), + ('1', 'uné', '1', {"id": 1, "text": "uné"}), + ('2', 'deux', '2', {"id": 2, "text": "deux"}), ] diff --git a/tests/test_fields.py b/tests/test_fields.py index 768e12f..6bcf178 100644 --- a/tests/test_fields.py +++ b/tests/test_fields.py @@ -155,10 +155,10 @@ def test_map(): def test_item_render(): items_kwargs = [] - items_kwargs.append({'items': ['a', 'b', 'c']}) + items_kwargs.append({'items': ['aa', 'ab', 'ac']}) items_kwargs.append({'data_source': { 'type': 'formula', - 'value': '''['a', 'b', 'c']'''}}) + 'value': '''['aa', 'ab', 'ac']'''}}) for item_kwargs in items_kwargs: field = fields.ItemField(id='1', label='Foobar', **item_kwargs) @@ -220,10 +220,10 @@ def test_item_render(): def test_item_render_as_radio(): items_kwargs = [] - items_kwargs.append({'items': ['a', 'b', 'c']}) + items_kwargs.append({'items': ['aa', 'ab', 'ac']}) items_kwargs.append({'data_source': { 'type': 'formula', - 'value': '''['a', 'b', 'c']'''}}) + 'value': '''['aa', 'ab', 'ac']'''}}) for item_kwargs in items_kwargs: field = fields.ItemField(id='1', label='Foobar', show_as_radio=True, **item_kwargs) @@ -278,3 +278,19 @@ def test_item_render_as_radio(): form = Form() field.add_to_form(form) assert str(form.render()).count('"radio"') == 1 + +def test_items_render(): + items_kwargs = [] + items_kwargs.append({'items': ['aa', 'ab', 'ac']}) + items_kwargs.append({'data_source': { + 'type': 'formula', + 'value': '''['aa', 'ab', 'ac']'''}}) + + for item_kwargs in items_kwargs: + field = fields.ItemsField(id='1', label='Foobar', **item_kwargs) + form = Form() + field.add_to_form(form) + assert str(form.render()).count('type="checkbox"') == 3 + assert '>aa<' in str(form.render()) + assert '>ab<' in str(form.render()) + assert '>ac<' in str(form.render()) diff --git a/tests/test_form_pages.py b/tests/test_form_pages.py index 7866c8d..59f3f07 100644 --- a/tests/test_form_pages.py +++ b/tests/test_form_pages.py @@ -3335,3 +3335,145 @@ def test_form_page_profile_verified_prefill(pub): assert 'readonly' in resp.form['f0'].attrs assert not 'Check values then click submit.' in resp.body assert resp.form['f0'].value == 'foo@localhost' + +def test_item_field_with_disabled_items(pub): + user = create_user(pub) + formdef = create_formdef() + formdef.data_class().wipe() + ds = {'type': 'json', 'value': 'http://remote.example.net/json'} + formdef.fields = [fields.ItemField(id='0', label='string', type='item', + data_source=ds, display_disabled_items=True)] + formdef.store() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + resp.form['f0'] = '1' + resp.form['f0'] = '2' + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == '2' + assert formdef.data_class().select()[0].data['0_display'] == 'world' + + formdef.data_class().wipe() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + assert '' in resp.body + resp.form['f0'] = '1' + resp.form['f0'] = '2' + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == '2' + assert formdef.data_class().select()[0].data['0_display'] == 'world' + + resp = get_app(pub).get('/test/') + assert '' in resp.body + resp.form['f0'] = '1' + resp = resp.form.submit('submit') # -> validation page + assert 'There were errors processing the form' in resp.body + + formdef.data_class().wipe() + formdef.fields = [fields.ItemField(id='0', label='string', type='item', data_source=ds, + display_disabled_items=False)] + formdef.store() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + assert not '' in resp.body + resp.form['f0'] = '2' + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == '2' + assert formdef.data_class().select()[0].data['0_display'] == 'world' + + formdef.data_class().wipe() + formdef.fields = [fields.ItemField(id='0', label='string', type='item', + data_source=ds, show_as_radio=True, display_disabled_items=True)] + formdef.store() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + resp.form['f0'] = '1' + resp.form['f0'] = '2' + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == '2' + assert formdef.data_class().select()[0].data['0_display'] == 'world' + + formdef.data_class().wipe() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + assert '' in resp.body + resp.form['f0'] = '1' + resp.form['f0'] = '2' + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == '2' + assert formdef.data_class().select()[0].data['0_display'] == 'world' + + resp = get_app(pub).get('/test/') + assert '' in resp.body + resp.form['f0'] = '1' + resp = resp.form.submit('submit') # -> validation page + assert 'There were errors processing the form' in resp.body + +def test_items_field_with_disabled_items(pub): + user = create_user(pub) + formdef = create_formdef() + formdef.data_class().wipe() + ds = {'type': 'json', 'value': 'http://remote.example.net/json'} + formdef.fields = [fields.ItemsField(id='0', label='string', type='items', + data_source=ds, display_disabled_items=True)] + formdef.store() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello'}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + resp.form['f0$element1'].checked = True + resp.form['f0$element2'].checked = True + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == ['1', '2'] + assert formdef.data_class().select()[0].data['0_display'] == 'hello, world' + + formdef.data_class().wipe() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + assert resp.form['f0$element1'].attrs['disabled'] + resp.form['f0$element1'].checked = True + resp.form['f0$element2'].checked = True + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == ['2'] + assert formdef.data_class().select()[0].data['0_display'] == 'world' + + formdef.data_class().wipe() + formdef.fields = [fields.ItemsField(id='0', label='string', type='items', + data_source=ds, display_disabled_items=False)] + formdef.store() + + with mock.patch('urllib2.urlopen') as urlopen: + data = {'data': [{'id': '1', 'text': 'hello', 'disabled': True}, {'id': '2', 'text': 'world'}]} + urlopen.side_effect = lambda *args: StringIO.StringIO(json.dumps(data)) + resp = get_app(pub).get('/test/') + assert not 'f0$element1' in resp.form.fields + resp.form['f0$element2'].checked = True + resp = resp.form.submit('submit') # -> validation page + resp = resp.form.submit('submit') # -> submit + assert formdef.data_class().select()[0].data['0'] == ['2'] + assert formdef.data_class().select()[0].data['0_display'] == 'world' diff --git a/wcs/admin/forms.py b/wcs/admin/forms.py index 0cab890..19b635f 100644 --- a/wcs/admin/forms.py +++ b/wcs/admin/forms.py @@ -1178,7 +1178,7 @@ class FormDefPage(Directory): endpoints = [] for status in self.formdef.workflow.get_endpoint_status(): - endpoints.append((status.id, status.name)) + endpoints.append((str(status.id), status.name)) form = Form(enctype='multipart/form-data') form.add(DateWidget, 'before_request_date', @@ -1187,7 +1187,7 @@ class FormDefPage(Directory): required=True) form.add(CheckboxesWidget, 'endpoints', title=_('Status of the forms to anonymise'), value=[endpoint[0] for endpoint in endpoints], - elements=endpoints, + options=endpoints, inline=False, required=True) form.add_submit('submit', _('Submit')) diff --git a/wcs/admin/settings.py b/wcs/admin/settings.py index cd86b1e..21d72fe 100644 --- a/wcs/admin/settings.py +++ b/wcs/admin/settings.py @@ -72,10 +72,10 @@ class IdentificationDirectory(Directory): methods.insert(0, ('idp', _('Delegated to SAML identity provider'))) form.add(CheckboxesWidget, 'methods', title = _('Methods'), - value = identification_cfg.get('methods'), - elements = methods, - inline = False, - required = True) + value=identification_cfg.get('methods'), + options=methods, + inline=False, + required=True) form.add(CheckboxWidget, 'use_user_hash', title=_('One-way association between user and forms'), value=bool(identification_cfg.get('use_user_hash', False)), diff --git a/wcs/backoffice/management.py b/wcs/backoffice/management.py index 9890f47..f2dcd23 100644 --- a/wcs/backoffice/management.py +++ b/wcs/backoffice/management.py @@ -953,12 +953,9 @@ class FormPage(Directory): filter_field.required = False options = filter_field.get_options() if options: - if type(options[0]) in (tuple, list): - if len(options[0]) == 2: - options = [(x[0], x[1], x[0]) for x in options] - options.insert(0, (None, '', None)) - else: - options.insert(0, '') + if len(options[0]) == 2: + options = [(x[0], x[1], x[0]) for x in options] + options.insert(0, (None, '', '')) r += SingleSelectWidget(filter_field_key, title=filter_field.label, options=options, value=filter_field_value, render_br=False).render() diff --git a/wcs/data_sources.py b/wcs/data_sources.py index cf8811e..9991550 100644 --- a/wcs/data_sources.py +++ b/wcs/data_sources.py @@ -84,11 +84,16 @@ class DataSourceSelectionWidget(CompositeWidget): return r.getvalue() -def get_items(data_source): +def get_items(data_source, include_disabled=False): structured_items = get_structured_items(data_source) tupled_items = [] for item in structured_items: - tupled_items.append((str(item['id']), str(item['text']), str(item.get('key', item['id'])))) + if item.get('disabled') and not include_disabled: + continue + tupled_items.append((str(item['id']), + str(item['text']), + str(item.get('key', item['id'])), + item)) return tupled_items diff --git a/wcs/fields.py b/wcs/fields.py index f94f6cb..085acea 100644 --- a/wcs/fields.py +++ b/wcs/fields.py @@ -1049,6 +1049,7 @@ class ItemField(WidgetField): widget_class = SingleSelectHintWidget data_source = {} in_filters = False + display_disabled_items = False def __init__(self, **kwargs): self.items = [] @@ -1056,9 +1057,9 @@ class ItemField(WidgetField): def get_options(self): if self.data_source: - return data_sources.get_items(self.data_source) + return [x[:3] for x in data_sources.get_items(self.data_source)] if self.items: - return self.items[:] + return [(x, x) for x in self.items] return [] def perform_more_widget_changes(self, form, kwargs, edit = True): @@ -1066,6 +1067,13 @@ class ItemField(WidgetField): if real_data_source and real_data_source.get('type') == 'jsonp': kwargs['url'] = real_data_source.get('value') self.widget_class = JsonpSingleSelectWidget + elif self.items: + kwargs['options'] = self.get_options() + elif self.data_source: + items = data_sources.get_items(self.data_source, + include_disabled=self.display_disabled_items) + kwargs['options'] = [x[:3] for x in items if not x[-1].get('disabled')] + kwargs['options_with_attributes'] = items[:] else: kwargs['options'] = self.get_options() if not kwargs.get('options'): @@ -1179,10 +1187,15 @@ class ItemField(WidgetField): hint=_('This will get the available items from an external source.'), required=False, advanced=is_datasource_advanced(self.data_source)) + form.add(CheckboxWidget, 'display_disabled_items', + title=_('Display disabled items'), + value=self.display_disabled_items, + advanced=not(self.display_disabled_items)) def get_admin_attributes(self): return WidgetField.get_admin_attributes(self) + ['items', - 'show_as_radio', 'data_source', 'in_filters', 'anonymise'] + 'show_as_radio', 'data_source', 'in_filters', 'anonymise', + 'display_disabled_items'] def check_admin_form(self, form): items = form.get_widget('items').parse() @@ -1226,6 +1239,7 @@ class ItemsField(WidgetField): items = [] max_choices = 0 data_source = {} + display_disabled_items = False widget_class = CheckboxesWidget @@ -1239,18 +1253,23 @@ class ItemsField(WidgetField): if self.data_source: if self._cached_data_source: return self._cached_data_source - self._cached_data_source = data_sources.get_items(self.data_source) + self._cached_data_source = [x[:3] for x in data_sources.get_items(self.data_source)] return self._cached_data_source[:] elif self.items: - return self.items[:] + return [(x, x) for x in self.items] else: return [] def perform_more_widget_changes(self, form, kwargs, edit = True): - kwargs['elements'] = self.get_options() + kwargs['options'] = self.get_options() kwargs['max_choices'] = self.max_choices + if self.data_source: + items = data_sources.get_items(self.data_source, + include_disabled=self.display_disabled_items) + kwargs['options'] = [x[:3] for x in items if not x[-1].get('disabled')] + kwargs['options_with_attributes'] = items[:] - if len(self.get_options()) > 3: + if len(kwargs['options']) > 3: kwargs['inline'] = False def fill_admin_form(self, form): @@ -1268,10 +1287,15 @@ class ItemsField(WidgetField): hint=_('This will get the available items from an external source.'), required=False, advanced=is_datasource_advanced(self.data_source)) + form.add(CheckboxWidget, 'display_disabled_items', + title=_('Display disabled items'), + value=self.display_disabled_items, + advanced=not(self.display_disabled_items)) def get_admin_attributes(self): return WidgetField.get_admin_attributes(self) + ['items', - 'max_choices', 'data_source', 'anonymise'] + 'max_choices', 'data_source', 'anonymise', + 'display_disabled_items'] def check_admin_form(self, form): items = form.get_widget('items').parse() @@ -1973,7 +1997,7 @@ class PasswordField(WidgetField): ('sha1', _('SHA1')), ] form.add(CheckboxesWidget, 'formats', title=_('Storage formats'), - value=self.formats, elements=formats, inline=True) + value=self.formats, options=formats, inline=True) form.add(IntWidget, 'min_length', title=_('Minimum length'), value=self.min_length) form.add(IntWidget, 'max_length', title=_('Maximum password length'), diff --git a/wcs/qommon/form.py b/wcs/qommon/form.py index dba6992..51e047d 100644 --- a/wcs/qommon/form.py +++ b/wcs/qommon/form.py @@ -200,23 +200,33 @@ def submit_render_content(self): SubmitWidget.render_content = submit_render_content -def radiobuttons_render_content(self): - tags = [] - for object, description, key in self.options: - if self.is_selected(object): - checked = 'checked' - else: - checked = None - r = htmltag("input", xml_end=True, - type="radio", - name=self.name, - value=key, - checked=checked, - **self.attrs) - tags.append(htmltext('')) - return htmlescape(self.delim).join(tags) -RadiobuttonsWidget.render_content = radiobuttons_render_content +class RadiobuttonsWidget(quixote.form.RadiobuttonsWidget): + def __init__(self, name, value=None, **kwargs): + self.options_with_attributes = kwargs.pop('options_with_attributes', None) + super(RadiobuttonsWidget, self).__init__(name, value=value, **kwargs) + def render_content(self): + include_disabled = False + options = self.options[:] + if self.options_with_attributes: + options = self.options_with_attributes + include_disabled = True + + tags = [] + for option in options: + object, description, key = option[:3] + html_attrs = self.attrs.copy() + if self.is_selected(object): + html_attrs['checked'] = 'checked' + if self.options_with_attributes and option[-1].get('disabled'): + html_attrs['disabled'] = 'disabled' + r = htmltag("input", xml_end=True, + type="radio", + name=self.name, + value=key, + **html_attrs) + tags.append(htmltext('')) + return htmlescape(self.delim).join(tags) def checkbox_render_content(self): attrs = {'id': 'form_' + self.name} @@ -1100,7 +1110,7 @@ class RegexStringWidget(StringWidget): class CheckboxesWidget(CompositeWidget): readonly = False - def __init__(self, name, value = None, elements = None, **kwargs): + def __init__(self, name, value=None, options=None, **kwargs): CompositeWidget.__init__(self, name, value, **kwargs) self.element_names = {} @@ -1112,27 +1122,26 @@ class CheckboxesWidget(CompositeWidget): self.inline = kwargs.get('inline', True) self.max_choices = int(kwargs.get('max_choices', 0) or 0) - for v in elements: - if type(v) is tuple: - title = v[1] - key = v[0] - if type(key) is int: - name = 'element%d' % v[0] - elif type(key) in (str, htmltext): - name = str('element%s' % v[0]) - key = str(key) - else: - raise NotImplementedError() - else: - title = v - key = v - name = 'element%d' % len(self.element_names.keys()) + self.options_with_attributes = kwargs.pop('options_with_attributes', None) + self.disabled_options = [] + if self.options_with_attributes: + options = self.options_with_attributes + + for option in options: + key, title = option[:2] + key = str(key) + name = 'element%s' % key + + element_kwargs = kwargs.copy() + if self.options_with_attributes and option[-1].get('disabled'): + element_kwargs['disabled'] = 'disabled' + self.disabled_options.append(name) if value and key in value: checked = True else: checked = False - self.add(CheckboxWidget, name, title = title, value = checked, **kwargs) + self.add(CheckboxWidget, name, title=title, value=checked, **element_kwargs) self.element_names[name] = key def _parse(self, request): @@ -1140,6 +1149,8 @@ class CheckboxesWidget(CompositeWidget): return values = [] for name in self.element_names: + if name in self.disabled_options: + continue value = self.get(name) if value is True: values.append(self.element_names[name]) @@ -1168,7 +1179,10 @@ class CheckboxesWidget(CompositeWidget): else: r += htmltext('