Picture-Puzzle-website/venv/Lib/site-packages/wtforms/fields/choices.py

230 lines
6.7 KiB
Python

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, render_kw) 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, choice in enumerate(self.iter_choices()):
if len(choice) == 4:
value, label, checked, render_kw = choice
else:
value, label, checked = choice
render_kw = {}
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:
selected = self.coerce(value) == self.data
render_kw = other_args[0] if len(other_args) else {}
yield (value, label, selected, 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 not self.validate_choice:
return
if self.choices is None:
raise TypeError(self.gettext("Choices cannot be None."))
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 not choices:
_choices = []
elif isinstance(choices[0], (list, tuple)):
_choices = choices
else:
_choices = zip(choices, choices)
for value, label, *other_args in _choices:
selected = self.data is not None and self.coerce(value) in self.data
render_kw = other_args[0] if len(other_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 not self.validate_choice or not self.data:
return
if self.choices is None:
raise TypeError(self.gettext("Choices cannot be None."))
acceptable = [self.coerce(choice[0]) for choice in self.iter_choices()]
if any(data not in acceptable for data in self.data):
unacceptable = [
str(data) for data in set(self.data) if data not in 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()