Skip to content

Commit

Permalink
Merge a5368d0 into 82d6e6b
Browse files Browse the repository at this point in the history
  • Loading branch information
jace committed Jun 21, 2019
2 parents 82d6e6b + a5368d0 commit fc8bed0
Show file tree
Hide file tree
Showing 5 changed files with 103 additions and 3 deletions.
16 changes: 16 additions & 0 deletions baseframe/forms/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

from coaster.utils import unicode_extended_whitespace

__all__ = ['lower', 'upper', 'strip', 'lstrip', 'rstrip', 'strip_each', 'none_if_empty']


def lower():
"""
Expand Down Expand Up @@ -72,6 +74,20 @@ def rstrip_inner(value):
return rstrip_inner


def strip_each(chars=unicode_extended_whitespace):
"""
Strip whitespace and remove blank elements from each element in an iterable.
Falsy values are returned unprocessed
:param chars: If specified, strip these characters instead of whitespace
"""
def strip_each_inner(value):
if value:
return [sline for sline in [line.strip(chars) for line in value] if sline]
return value
return strip_each_inner


def none_if_empty():
"""
If data is empty or evalues to boolean false, replace with None
Expand Down
20 changes: 18 additions & 2 deletions baseframe/forms/validators.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# -*- coding: utf-8 -*-

from collections import namedtuple
from decimal import Decimal
from fractions import Fraction
import datetime
Expand All @@ -22,7 +23,7 @@

__local = ['AllUrlsValid', 'IsNotPublicEmailDomain', 'IsPublicEmailDomain', 'NoObfuscatedEmail',
'AllowedIf', 'OptionalIf', 'RequiredIf', 'ValidCoordinates', 'ValidEmail',
'ValidEmailDomain', 'ValidName', 'ValidUrl']
'ValidEmailDomain', 'ValidName', 'ValidUrl', 'ForEach']
__imported = [ # WTForms validators
'DataRequired', 'EqualTo', 'InputRequired', 'Length', 'NumberRange', 'Optional',
'StopValidation', 'URL', 'ValidationError']
Expand All @@ -31,7 +32,6 @@

EMAIL_RE = re.compile(r'\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,63}\b', re.I)


_zero_values = (0, 0.0, Decimal('0'), 0j, Fraction(0, 1), datetime.time(0, 0, 0))


Expand All @@ -53,6 +53,22 @@ def is_empty(value):
return value not in _zero_values and not value


FakeField = namedtuple('FakeField', ['data', 'gettext', 'ngettext'])


class ForEach(object):
"""
Runs specified validators on each element of an iterable value
"""
def __init__(self, validators):
self.validators = validators

def __call__(self, form, field):
for v in self.validators:
for element in field.data:
v(form, FakeField(element, field.gettext, field.ngettext))


class AllowedIf(object):
"""
Validator that allows a value only if another field also has a value.
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[flake8]
ignore = I100, I201, E501, E128, E124, E402, W503
hang-closing = True
19 changes: 19 additions & 0 deletions tests/test_filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,25 @@ def test_rstrip(self):
self.assertEqual(rstrip_func('a test '), 'a test')
self.assertEqual(rstrip_func(' '), '')

def test_strip_each(self):
strip_each_func = forms.strip_each()
self.assertEqual(strip_each_func(None), None)
self.assertEqual(strip_each_func([]), [])
self.assertEqual(strip_each_func(
[
' Left strip',
'Right strip ',
' Full strip ',
'',
'No strip',
''
]), [
'Left strip',
'Right strip',
'Full strip',
'No strip',
])

def test_none_if_empty(self):
none_if_empty_func = forms.none_if_empty()
self.assertEqual(none_if_empty_func('Test'), 'Test')
Expand Down
50 changes: 49 additions & 1 deletion tests/test_validators.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import warnings
import urllib3
from werkzeug.datastructures import MultiDict
from mxsniff import MXLookupException
from baseframe.utils import is_public_email_domain
from baseframe import forms
from mxsniff import MXLookupException
from .fixtures import (TestCaseBaseframe, UrlFormTest, AllUrlsFormTest, PublicEmailDomainFormTest)


Expand Down Expand Up @@ -142,6 +143,53 @@ def tearDown(self):
self.ctx.pop()


class TestForEach(TestFormBase):
class Form(forms.Form):
textlist = forms.TextListField(
validators=[forms.ForEach([forms.URL()])]
)

def test_passes_single(self):
self.form.process(formdata=MultiDict({'textlist': "http://www.example.com/"}))
assert self.form.validate() is True

def test_passes_list(self):
self.form.process(formdata=MultiDict({'textlist': "http://www.example.com\r\nhttp://www.example.org/"}))
assert self.form.validate() is True

def test_fails_single(self):
self.form.process(formdata=MultiDict({'textlist': "example"}))
assert self.form.validate() is False

def test_fails_list(self):
self.form.process(formdata=MultiDict({'textlist': "www.example.com\r\nwww.example.org"}))
assert self.form.validate() is False

def test_fails_mixed1(self):
self.form.process(formdata=MultiDict({'textlist': "http://www.example.com/\r\nwww.example.org"}))
assert self.form.validate() is False

def test_fails_mixed2(self):
self.form.process(formdata=MultiDict({'textlist': "www.example.com\r\nhttp://www.example.org/"}))
assert self.form.validate() is False

def test_fails_blanklines(self):
self.form.process(formdata=MultiDict({'textlist': "http://www.example.com\r\n"}))
assert self.form.validate() is False


class TestForEachFiltered(TestFormBase):
class Form(forms.Form):
textlist = forms.TextListField(
validators=[forms.ForEach([forms.URL()])],
filters=[forms.strip_each()]
)

def test_passes_blanklines(self):
self.form.process(formdata=MultiDict({'textlist': "http://www.example.com\r\n"}))
assert self.form.validate() is True


class TestAllowedIf(TestFormBase):
class Form(forms.Form):
other = forms.StringField("Other")
Expand Down

0 comments on commit fc8bed0

Please sign in to comment.