-
-
Notifications
You must be signed in to change notification settings - Fork 395
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #710 from azmeuk/split-fields
Field files split
- Loading branch information
Showing
9 changed files
with
932 additions
and
901 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |
Oops, something went wrong.