Skip to content
This repository has been archived by the owner on Sep 5, 2019. It is now read-only.

Commit

Permalink
Adds an Optional validator that does not ignore data and defaults to it
Browse files Browse the repository at this point in the history
  • Loading branch information
Denis Krienbühl committed Jan 24, 2018
1 parent 949b485 commit 4f7b7c1
Show file tree
Hide file tree
Showing 6 changed files with 94 additions and 47 deletions.
4 changes: 4 additions & 0 deletions HISTORY.rst
@@ -1,5 +1,9 @@
Changelog
---------

- Adds an Optional validator that does not ignore "data" and defaults to it.
[href]

0.30.0 (2018-01-17)
~~~~~~~~~~~~~~~~~~~

Expand Down
3 changes: 1 addition & 2 deletions onegov/form/__init__.py
Expand Up @@ -15,7 +15,6 @@
Form,
merge_forms,
move_fields,
with_options,
)
from onegov.form.display import render_field
from onegov.form.extensions import FormExtension, Extendable
Expand All @@ -32,7 +31,7 @@
from onegov.form.parser import parse_form
from onegov.form.parser import parse_formcode
from onegov.form.parser import WTFormsClassBuilder
from onegov.form.utils import decimal_range, as_internal_id
from onegov.form.utils import decimal_range, as_internal_id, with_options

__all__ = [
'as_internal_id',
Expand Down
42 changes: 3 additions & 39 deletions onegov/form/core.py
@@ -1,15 +1,15 @@
import inspect
import weakref

from collections import OrderedDict
from decimal import Decimal
from itertools import groupby
from onegov.form import utils
from onegov.form.validators import StrictOptional
from onegov.pay import Price
from operator import itemgetter
from wtforms import Form as BaseForm
from wtforms.fields.html5 import EmailField
from wtforms.validators import InputRequired, DataRequired, Optional
from wtforms.validators import InputRequired, DataRequired
from wtforms_components import If, Chain


Expand Down Expand Up @@ -216,7 +216,7 @@ def process_depends_on(self):
),
If(
field.depends_on.unfulfilled,
Optional()
StrictOptional()
),
)

Expand Down Expand Up @@ -534,42 +534,6 @@ def non_empty_fields(self):
(id, field) for id, field in self.fields.items() if field.data)


def with_options(widget, **render_options):
""" Takes a widget class or instance and returns a child-instance of the
widget class, with the given options set on the render call.
This makes it easy to use existing WTForms widgets with custom render
options:
field = StringField(widget=with_options(TextArea, class_="markdown"))
Note: With wtforms 2.1 this is no longer necssary. Instead use the
render_kw parameter of the field class. This function will be deprecated
in a future release.
"""

if inspect.isclass(widget):
class Widget(widget):

def __call__(self, *args, **kwargs):
render_options.update(kwargs)
return super().__call__(*args, **render_options)

return Widget()
else:
class Widget(widget.__class__):

def __init__(self):
self.__dict__.update(widget.__dict__)

def __call__(self, *args, **kwargs):
render_options.update(kwargs)
return widget.__call__(*args, **render_options)

return Widget()


class FieldDependency(object):
""" Defines a dependency to a field. The given field(s) must have the given
choice for this dependency to be fulfilled.
Expand Down
14 changes: 9 additions & 5 deletions onegov/form/parser/form.py
Expand Up @@ -2,14 +2,15 @@
from onegov.form import errors
from onegov.form.core import FieldDependency
from onegov.form.core import Form
from onegov.form.core import with_options
from onegov.form.fields import MultiCheckboxField
from onegov.form.fields import UploadField
from onegov.form.parser.core import parse_formcode
from onegov.form.utils import as_internal_id
from onegov.form.utils import with_options
from onegov.form.validators import ExpectedExtensions
from onegov.form.validators import FileSizeLimit
from onegov.form.validators import Stdnum
from onegov.form.validators import StrictOptional
from wtforms import PasswordField
from wtforms import RadioField
from wtforms import StringField
Expand All @@ -23,7 +24,6 @@
from wtforms.validators import DataRequired
from wtforms.validators import Length
from wtforms.validators import NumberRange
from wtforms.validators import Optional
from wtforms.validators import Regexp
from wtforms.validators import URL
from wtforms.widgets import TextArea
Expand Down Expand Up @@ -180,7 +180,9 @@ def handle_field(builder, field, dependency=None):
required=field.required,
choices=[(c.key, c.label) for c in field.choices],
default=next((c.key for c in field.choices if c.selected), None),
pricing=field.pricing
pricing=field.pricing,
# do not coerce None into 'None'
coerce=lambda v: str(v) if v is not None else v
)

elif field.type == 'checkbox':
Expand All @@ -192,7 +194,9 @@ def handle_field(builder, field, dependency=None):
required=field.required,
choices=[(c.key, c.label) for c in field.choices],
default=[c.key for c in field.choices if c.selected],
pricing=field.pricing
pricing=field.pricing,
# do not coerce None into 'None'
coerce=lambda v: str(v) if v is not None else v
)

elif field.type == 'integer_range':
Expand Down Expand Up @@ -287,7 +291,7 @@ def validators_add_dependency(self, validators, dependency):
validators.insert(0, validator)

def validators_add_optional(self, validators):
validators.insert(0, Optional())
validators.insert(0, StrictOptional())

def mark_as_dependent(self, field_id, dependency):
field = getattr(self.form_class, field_id)
Expand Down
37 changes: 37 additions & 0 deletions onegov/form/utils.py
@@ -1,3 +1,4 @@
import inspect
import re

from decimal import Decimal
Expand Down Expand Up @@ -86,3 +87,39 @@ def __next__(self):

def hash_definition(definition):
return md5(definition.encode('utf-8')).hexdigest()


def with_options(widget, **render_options):
""" Takes a widget class or instance and returns a child-instance of the
widget class, with the given options set on the render call.
This makes it easy to use existing WTForms widgets with custom render
options:
field = StringField(widget=with_options(TextArea, class_="markdown"))
Note: With wtforms 2.1 this is no longer necssary. Instead use the
render_kw parameter of the field class. This function will be deprecated
in a future release.
"""

if inspect.isclass(widget):
class Widget(widget):

def __call__(self, *args, **kwargs):
render_options.update(kwargs)
return super().__call__(*args, **render_options)

return Widget()
else:
class Widget(widget.__class__):

def __init__(self):
self.__dict__.update(widget.__dict__)

def __call__(self, *args, **kwargs):
render_options.update(kwargs)
return widget.__call__(*args, **render_options)

return Widget()
41 changes: 40 additions & 1 deletion onegov/form/validators.py
@@ -1,12 +1,16 @@
import importlib
import humanize

from cgi import FieldStorage
from mimetypes import types_map
from onegov.form import _
from onegov.form.core import with_options
from onegov.form.utils import with_options
from onegov.form.errors import InvalidFormSyntax, DuplicateLabelError
from stdnum.exceptions import ValidationError as StdnumValidationError
from wtforms import ValidationError
from wtforms.fields import SelectField
from wtforms.validators import StopValidation, Optional
from wtforms.compat import string_types


class Stdnum(object):
Expand Down Expand Up @@ -152,3 +156,38 @@ def __call__(self, form, field):
label=formfield.label.text
)
)


class StrictOptional(Optional):
""" A copy of wtform's Optional validator, but with a more strict approach
to optional validation checking.
See https://github.com/wtforms/wtforms/issues/350
"""

def is_missing(self, value):
if isinstance(value, FieldStorage):
return False

if not value:
return True

if isinstance(value, string_types):
return not self.string_check(value)

return False

def __call__(self, form, field):
raw = field.raw_data and field.raw_data[0]
val = field.data

# the selectfields have this annyoing habit of coercing all values
# that are added to them -> this includes the None, which is turned
# into 'None'
if isinstance(field, SelectField) and val == 'None':
val = None

if self.is_missing(raw) and self.is_missing(val):
field.errors[:] = []
raise StopValidation()

0 comments on commit 4f7b7c1

Please sign in to comment.