Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 32 additions & 6 deletions pilo/fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,29 @@ def field1(self, value):
]


class FormError(ValueError):

def __init__(self, *field_errors):
self.field_errors = field_errors
super(FormError, self).__init__(self.message)

def __str__(self):
return self.message

def __repr__(self):
return 'FormError({0})'.format(
', '.join(exc.__class__.__name__ for exc in self.field_errors)
)

@property
def message(self):
msg = '\n* '.join(
'{0}: {1}'.format(exc.__class__.__name__, str(exc))
for exc in self.field_errors
)
return '\n* {0}\n'.format(msg)


class FieldError(ValueError):

def __init__(self, message, field):
Expand Down Expand Up @@ -118,9 +141,12 @@ def invalid(self, violation):

class RaiseErrors(list, Errors):

def __call__(self, *ex):
self.extend(ex)
raise ex[0]
def __call__(self, *excs):
self.extend(excs)
field_errors = [e for e in self if isinstance(e, FieldError)]
if field_errors:
raise FormError(*field_errors)
raise self[0]


class CollectErrors(list, Errors):
Expand All @@ -137,7 +163,7 @@ class CreatedCountMixin(object):

_created_count = 0

def __init__(self):
def __init__(self):
CreatedCountMixin._created_count += 1
self._count = CreatedCountMixin._created_count

Expand Down Expand Up @@ -1541,7 +1567,7 @@ def __init__(self, *args, **kwargs):
if src:
errors = self.map(src)
if errors:
raise errors[0]
RaiseErrors()(*errors)

def _map_source(self, obj):
return DefaultSource(obj)
Expand Down Expand Up @@ -1638,7 +1664,7 @@ def map(self, src=None, tags=None, reset=False, unmapped='ignore', error='collec
if error == 'collect':
return errors
if errors:
raise errors[0]
RaiseErrors()(*errors)
return self

def has(self, field):
Expand Down
141 changes: 141 additions & 0 deletions tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import re

import pilo
from pilo.fields import Integer, List, String, SubForm

from tests import TestCase

Expand Down Expand Up @@ -374,3 +375,143 @@ def send_to_zoo(self):
]:
obj = Animal.type.cast(desc)(desc)
self.assertIsInstance(obj, cls)


class TestFormExceptions(TestCase):

def test_exceptions(self):

class DatingProfile(pilo.Form):

genders = ['male', 'female', 'neutral']

name = String()
email = String()
postal_code = String(length=5)
blurb = String(max_length=100)
gender = String(choices=genders)
sexual_preferences = List(String(choices=genders))
likes = List(String())

profile_with_one_error = dict(
name='William Henry Cavendish III',
email='whc@example.org',
postal_code='9021', # Invalid length
blurb='I am a test fixture',
gender='male',
sexual_preferences=['female', 'neutral'],
likes=['croquet', 'muesli', 'ruses', 'umbrellas', 'wenches'],
)
profile_with_two_errors = dict(
name='William Henry Cavendish III',
email='whc@example.org',
postal_code='9021', # Invalid postal code
blurb='I am a test fixture',
gender='male',
sexual_preferences=['female', 'neutral'],
# Likes parameter missing
)
profile_with_three_errors = dict(
name='William Henry Cavendish III',
email='whc@example.org',
postal_code='9021', # Invalid length
blurb='I am a test fixture',
gender='male',
sexual_preferences=['alien'], # Invalid preference
# likes is missing
)

with self.assertRaises(pilo.fields.FormError) as ctx:
DatingProfile(profile_with_one_error)

self.assertEquals(
ctx.exception.message,
'\n'
'* Invalid: postal_code - "9021" must have length >= 5'
'\n'
)
with self.assertRaises(pilo.fields.FormError) as ctx:
DatingProfile(profile_with_two_errors)

self.assertEquals(
ctx.exception.message,
'\n'
'* Invalid: postal_code - "9021" must have length >= 5'
'\n'
'* Missing: likes - missing'
'\n'
)
with self.assertRaises(pilo.fields.FormError) as ctx:
DatingProfile(profile_with_three_errors)

self.assertEquals(
ctx.exception.message,
'\n'
'* Invalid: postal_code - "9021" must have length >= 5'
'\n'
'* Invalid: sexual_preferences[0] - "alien" is not one of "male", '
'"female", "neutral"'
'\n'
'* Missing: likes - missing'
'\n'
)

def test_exceptions_in_nested_forms(self):

class DatingProfile(pilo.Form):

genders = ['male', 'female', 'neutral']

name = String()
email = String()
postal_code = String(length=5)
blurb = String(max_length=100)
gender = String(choices=genders)
sexual_preferences = List(String(choices=genders))
likes = List(String())

class Matches(pilo.Form):

similarity = Integer()
candidates = List(SubForm(DatingProfile))

with self.assertRaises(pilo.fields.FormError) as ctx:
Matches(
similarity="Not an integer",
candidates=[
dict(
name='William Henry Cavendish III',
email='whc@example.org',
postal_code='9021', # Invalid postal code
blurb='I am a test fixture',
gender='male',
sexual_preferences=['female', 'neutral'],
# Likes parameter missing
),
dict(),
]
)
self.assertEquals(
ctx.exception.message,
'\n'
'* Invalid: similarity - "Not an integer" is not an integer'
'\n'
'* Invalid: candidates[0].postal_code - "9021" must have length >= 5'
'\n'
'* Missing: candidates[0].likes - missing'
'\n'
'* Missing: candidates[1].name - missing'
'\n'
'* Missing: candidates[1].email - missing'
'\n'
'* Missing: candidates[1].postal_code - missing'
'\n'
'* Missing: candidates[1].blurb - missing'
'\n'
'* Missing: candidates[1].gender - missing'
'\n'
'* Missing: candidates[1].sexual_preferences - missing'
'\n'
'* Missing: candidates[1].likes - missing'
'\n'
)