123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219 |
- import itertools
- from wtforms import widgets
- from wtforms.fields.core import Field
- from wtforms.validators import ValidationError
- __all__ = (
- "SelectField",
- "SelectMultipleField",
- "RadioField",
- )
- class SelectFieldBase(Field):
- option_widget = widgets.Option()
- """
- 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().__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 has_groups(self):
- return False
- def iter_groups(self):
- raise NotImplementedError()
- def __iter__(self):
- opts = dict(
- widget=self.option_widget,
- validators=self.validators,
- name=self.name,
- render_kw=self.render_kw,
- _form=None,
- _meta=self.meta,
- )
- for i, (value, label, checked, render_kw) in enumerate(self.iter_choices()):
- opt = self._Option(
- label=label, id="%s-%d" % (self.id, i), **opts, **render_kw
- )
- opt.process(None, value)
- opt.checked = checked
- yield opt
- class _Option(Field):
- checked = False
- def _value(self):
- return str(self.data)
- class SelectField(SelectFieldBase):
- widget = widgets.Select()
- def __init__(
- self,
- label=None,
- validators=None,
- coerce=str,
- choices=None,
- validate_choice=True,
- **kwargs,
- ):
- super().__init__(label, validators, **kwargs)
- self.coerce = coerce
- if callable(choices):
- choices = choices()
- if choices is not None:
- self.choices = choices if isinstance(choices, dict) else list(choices)
- else:
- self.choices = None
- self.validate_choice = validate_choice
- def iter_choices(self):
- if not self.choices:
- choices = []
- elif isinstance(self.choices, dict):
- choices = list(itertools.chain.from_iterable(self.choices.values()))
- else:
- choices = self.choices
- return self._choices_generator(choices)
- def has_groups(self):
- return isinstance(self.choices, dict)
- def iter_groups(self):
- if isinstance(self.choices, dict):
- for label, choices in self.choices.items():
- yield (label, self._choices_generator(choices))
- def _choices_generator(self, choices):
- if not choices:
- _choices = []
- elif isinstance(choices[0], (list, tuple)):
- _choices = choices
- else:
- _choices = zip(choices, choices)
- for value, label, *other_args in _choices:
- render_kw = other_args[0] if len(other_args) else {}
- yield (value, label, self.coerce(value) == self.data, render_kw)
- def process_data(self, value):
- try:
- # If value is None, don't coerce to a value
- self.data = self.coerce(value) if value is not None else None
- except (ValueError, TypeError):
- self.data = None
- def process_formdata(self, valuelist):
- if not valuelist:
- return
- try:
- self.data = self.coerce(valuelist[0])
- except ValueError as exc:
- raise ValueError(self.gettext("Invalid Choice: could not coerce.")) from exc
- def pre_validate(self, form):
- if self.choices is None:
- raise TypeError(self.gettext("Choices cannot be None."))
- if not self.validate_choice:
- return
- for _, _, match, _ in self.iter_choices():
- if match:
- break
- else:
- raise ValidationError(self.gettext("Not a valid choice."))
- class SelectMultipleField(SelectField):
- """
- No different from a normal select field, except this one can take (and
- validate) multiple choices. You'll need to specify the HTML `size`
- attribute to the select field when rendering.
- """
- widget = widgets.Select(multiple=True)
- def _choices_generator(self, choices):
- if choices:
- if isinstance(choices[0], (list, tuple)):
- _choices = choices
- else:
- _choices = zip(choices, choices)
- else:
- _choices = []
- for value, label, *args in _choices:
- selected = self.data is not None and self.coerce(value) in self.data
- render_kw = args[0] if len(args) else {}
- yield (value, label, selected, render_kw)
- def process_data(self, value):
- try:
- self.data = list(self.coerce(v) for v in value)
- except (ValueError, TypeError):
- self.data = None
- def process_formdata(self, valuelist):
- try:
- self.data = list(self.coerce(x) for x in valuelist)
- except ValueError as exc:
- raise ValueError(
- self.gettext(
- "Invalid choice(s): one or more data inputs could not be coerced."
- )
- ) from exc
- def pre_validate(self, form):
- if self.choices is None:
- raise TypeError(self.gettext("Choices cannot be None."))
- if not self.validate_choice or not self.data:
- return
- acceptable = {c[0] for c in self.iter_choices()}
- if any(d not in acceptable for d in self.data):
- unacceptable = [str(d) for d in set(self.data) - acceptable]
- raise ValidationError(
- self.ngettext(
- "'%(value)s' is not a valid choice for this field.",
- "'%(value)s' are not valid choices for this field.",
- len(unacceptable),
- )
- % dict(value="', '".join(unacceptable))
- )
- class RadioField(SelectField):
- """
- Like a SelectField, except displays a list of radio buttons.
- Iterating the field will produce subfields (each containing a label as
- well) in order to allow custom rendering of the individual radio fields.
- """
- widget = widgets.ListWidget(prefix_label=False)
- option_widget = widgets.RadioInput()
|