try: from html import escape except ImportError: from cgi import escape # from wtforms import widgets from wtforms.widgets import html_params from wtforms.fields.core import Field from wtforms.compat import text_type, izip from markupsafe import Markup class MySelect(object): """ Renders a select field. If `multiple` is True, then the `size` property should be specified on rendering to make the field useful. The field must provide an `iter_choices()` method which the widget will call on rendering; this method must yield tuples of `(value, label, selected)`. """ def __init__(self, multiple=False): self.multiple = multiple def __call__(self, field, **kwargs): kwargs.setdefault('id', field.id) if self.multiple: kwargs['multiple'] = True html = ['') return Markup(''.join(html)) @classmethod def render_option(cls, value, label, selected, **kwargs): if value is True: # Handle the special case of a 'True' value. value = text_type(value) options = dict(kwargs, value=value) if selected: options['selected'] = True return Markup('' % (html_params(**options), escape(text_type(label), quote=False))) @classmethod def render_optgroup(cls, previous_label, label, **kwargs): options = dict(kwargs) if previous_label is None: return Markup( '' % (html_params(**options), escape(text_type(label), quote=False))) elif label is None: return Markup('') else: return Markup( '' % (html_params(**options), escape(text_type(label), quote=False))) class MyOption(object): """ Renders the individual option from a select field. This is just a convenience for various custom rendering situations, and an option by itself does not constitute an entire field. """ def __call__(self, field, **kwargs): return MySelect.render_option(field._value(), field.label.text, field.checked, **kwargs) class MySelectFieldBase(Field): # option_widget = widgets.Option() option_widget = MyOption() """ Base class for fields which can be iterated to produce options. This isn't a field, but an abstract base class for fields which want to provide this functionality. """ def __init__(self, label=None, validators=None, option_widget=None, **kwargs): super(MySelectFieldBase, self).__init__(label, validators, **kwargs) if option_widget is not None: self.option_widget = option_widget def iter_choices(self): """ Provides data for choice widget rendering. Must return a sequence or iterable of (value, label, selected) tuples. """ raise NotImplementedError() def __iter__(self): opts = dict(widget=self.option_widget, _name=self.name, _form=None, _meta=self.meta) for i, (value, label, checked) in enumerate(self.iter_choices()): opt = self._Option(label=label, id='%s-%d' % (self.id, i), **opts) opt.process(None, value) opt.checked = checked yield opt class _Option(Field): checked = False def _value(self): return text_type(self.data) class MySelectField(MySelectFieldBase): # widget = widgets.Select() widget = MySelect() def __init__(self, label=None, validators=None, coerce=text_type, choices=None, **kwargs): super(MySelectField, self).__init__(label, validators, **kwargs) self.coerce = coerce self.choices = choices def iter_choices(self): for choiceA, choiceB in self.choices: if isinstance(choiceB, (tuple, list)): # We should consider choiceA as an optgroup label group_label = choiceA for value, label in choiceB: yield group_label, value, label, self.coerce(value) == self.data else: value, label = choiceA, choiceB # Not an optgroup, let's fallback to classic usage yield None, value, label, self.coerce(value) == self.data def process_data(self, value): try: self.data = self.coerce(value) except (ValueError, TypeError): self.data = None def process_formdata(self, valuelist): if valuelist: try: self.data = self.coerce(valuelist[0]) except ValueError: raise ValueError(self.gettext('Invalid Choice: could not coerce')) def pre_validate(self, form): for choiceA, choiceB in self.choices: if isinstance(choiceB, (tuple, list)): for value, label in choiceB: if self.data == value: break if self.data == value: break else: value, label = choiceA, choiceB if self.data == value: break else: raise ValueError(self.gettext('Not a valid choice'))