Skip to content

Commit

Permalink
Merge pull request #710 from azmeuk/split-fields
Browse files Browse the repository at this point in the history
Field files split
  • Loading branch information
azmeuk authored Nov 2, 2021
2 parents 368d9c6 + a287589 commit 563b781
Show file tree
Hide file tree
Showing 9 changed files with 932 additions and 901 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Unreleased
- :class:`~fields.DateTimeField`, :class:`~fields.DateField` and
:class:`~fields.TimeField` support time formats that removes leading
zeros. :pr:`703`
- Refactoring: split `fields/core.py` and `fields/simple.py` :pr:`710`

Version 3.0.0a1
---------------
Expand Down
8 changes: 6 additions & 2 deletions src/wtforms/fields/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
from wtforms.fields.core import *
from wtforms.fields.choices import *
from wtforms.fields.choices import SelectFieldBase
from wtforms.fields.core import Field
from wtforms.fields.core import Flags
from wtforms.fields.core import Label
from wtforms.fields.core import SelectFieldBase
from wtforms.fields.datetime import *
from wtforms.fields.form import *
from wtforms.fields.list import *
from wtforms.fields.numeric import *
from wtforms.fields.simple import *
from wtforms.utils import unset_value as _unset_value
215 changes: 215 additions & 0 deletions src/wtforms/fields/choices.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,215 @@
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) 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 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 in _choices:
yield (value, label, self.coerce(value) == self.data)

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 in _choices:
selected = self.data is not None and self.coerce(value) in self.data
yield (value, label, selected)

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()
Loading

0 comments on commit 563b781

Please sign in to comment.