Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Conv accept #115

Merged
merged 4 commits into from

3 participants

@Harut
Owner
  • Removed hack with wrapping to_python method
  • Explicit call of to_python when error handling and required check is not needed
  • No more mess with accept and to_python methods, Converter.convert method is added: to_python just converts value and may raise ValidationError, convert catches validation errors, checks for required and applies filters

minuses:

  • we have to check everywhere in the code, whether to use convert or accept methods
@ods

accept() method seems OK, but convert() is certainly a bad name.

Owner

Any ideas for the name?

@ods

Why you didn't make this in main branch? It's not related to the rest changes.

@Harut

Essentially, the content of try: block is a part of to_python flow, not accept. If we will move it there, the code becomes more clear. But I don't know how to implement it without any wrappings or other ugly staff.

@riffm
Owner

:+1:

@ods
Owner

Good:

  • we now can fix long standing problems
  • simple and obvious interface of to_python for most cases and more flexible interface of accept for rare occasions when we need to fill errors for several fields

Bad:

  • no symetry: we have 2 interface methods for one direction and 1 for opposite.
@Harut
Owner

simple and obvious interface of to_python

The thing still unsettling me about these changes is that to make the interface really obvious and consistent, as I wrote before, we have to force the content of try: block to be a part of to_python. I.e. required check and call of filters and validators.

How to implement it?

  • Wrap to_python as it was before? Or do some stuff with metaclasses as equal? Looks like hack
  • Implement this checks in Converter.to_python and force call Converter.to_python(self, value) every time the method is redefined? It's annoying and fragile.

May be anyone knows straight way?

@Harut
Owner

I mean, if we write for exmaple:

    conv = Char(striptags, limit(3,250))
    # we expect that:
    conv.to_python('<p>Text</p>') == 'Text'
    conv.to_python('a') # raises ValidationError by length

In current (in the branch) implementation we will get

    conv.to_python('<p>Text</p>') == '<p>Text</p>'
    conv.to_python('a') == 'a'

    conv.accept('<p>Text</p>') == 'Text'
    conv.accept('a') == None # form.errors = {'name': 'blabla the string is too short'}
@Harut Harut merged commit 73f4a9b into from
@Harut Harut deleted the branch
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 27, 2013
  1. @Harut
  2. @Harut

    Converter.convert method

    Harut authored
  3. @Harut
Commits on Mar 29, 2013
  1. @Harut

    removed unused Field.to_python

    Harut authored
This page is out of date. Refresh to see the latest.
Showing with 58 additions and 62 deletions.
  1. +23 −30 iktomi/forms/convs.py
  2. +4 −7 iktomi/forms/fields.py
  3. +31 −25 tests/forms/convs.py
View
53 iktomi/forms/convs.py
@@ -72,7 +72,6 @@ def __init__(self, *args, **kwargs):
self._init_kwargs = kwargs
self.__dict__.update(kwargs)
self.validators_and_filters = args
- self.to_python = self._check(self.to_python)
# It is defined as read-only property to avoid setting it to True where
# converter doesn't support it.
@@ -91,24 +90,22 @@ def env(self):
def _is_empty(self, value):
return value in ('', [], {}, None)
- def _check(self, method):
- def wrapper(value, **kwargs):
- field, form = self.field, self.field.form
- try:
- value = method(value, **kwargs)
- if self.required and self._is_empty(value):
- form.errors[self.field.input_name] = self.error_required
- return self._existing_value
- for v in self.validators_and_filters:
- value = v(self, value)
- except ValidationError, e:
+ def accept(self, value, silent=False):
+ try:
+ value = self.to_python(value)
+ if self.required and self._is_empty(value):
+ raise ValidationError(self.error_required)
+ for v in self.validators_and_filters:
+ value = v(self, value)
+ except ValidationError, e:
+ if not silent:
+ field, form = self.field, self.field.form
form.errors[field.input_name] = e.message
- #NOTE: by default value for field is in python_data,
- # but this is not true for FieldList where data
- # is dynamic, so we set value to None for absent value.
- value = self._existing_value
- return value
- return wrapper
+ #NOTE: by default value for field is in python_data,
+ # but this is not true for FieldList where data
+ # is dynamic, so we set value to None for absent value.
+ value = self._existing_value
+ return value
def to_python(self, value):
""" custom converters should override this """
@@ -134,7 +131,9 @@ def assert_(self, expression, msg):
@property
def _existing_value(self):
- return self.field.parent.python_data.get(self.field.name)
+ if self.field is not None:
+ return self.field.parent.python_data.get(self.field.name)
+ return [] if self.multiple else None
class validator(object):
@@ -307,21 +306,15 @@ def from_python(self, value):
return conv.from_python(value)
def _safe_to_python(self, value):
- conv = self.conv(field=self.field)
- try:
- value = conv.to_python(value)
- except ValidationError:
- return None
+ # XXX hack
+ value = self.conv.accept(value, silent=True)
if value not in dict(self.choices):
return None
return value
def to_python(self, value):
if value == '':
- #XXX: check for multiple?
- if self.multiple:
- return []
- return None
+ return [] if self.multiple else None
if self.multiple:
value = [item for item in map(self._safe_to_python, value or [])
if item is not None]
@@ -335,8 +328,8 @@ def __iter__(self):
yield conv.from_python(python_value), label
def get_label(self, value):
- conv = self.conv(field=self.field)
- return dict(self.choices).get(conv.to_python(value))
+ value = self.conv.accept(value, silent=True)
+ return dict(self.choices).get(value)
class BaseDatetime(Converter):
View
11 iktomi/forms/fields.py
@@ -91,9 +91,6 @@ def id(self):
# insure unique IDs.
return '%s-%s' % (self.form.id, self.input_name)
- def to_python(self, value):
- return self.conv.to_python(value)
-
def from_python(self, value):
return self.conv.from_python(value)
@@ -173,7 +170,7 @@ def accept(self):
value = self.raw_value
if not self._check_value_type(value):
value = [] if self.multiple else self._null_value
- return self.to_python(value)
+ return self.conv.accept(value)
class AggregateField(BaseField):
@@ -223,7 +220,7 @@ def get_field(self, name):
def get_initial(self):
result = dict((field.name, field.get_initial())
for field in self.fields)
- return self.to_python(result)
+ return self.conv.accept(result, silent=True)
def set_raw_value(self, raw_data, value):
# fills in raw_data multidict, resulting keys are field's absolute names
@@ -241,7 +238,7 @@ def accept(self):
# readonly field
field.set_raw_value(self.form.raw_data,
field.from_python(result[field.name]))
- return self.to_python(result)
+ return self.conv.accept(result)
def render(self):
return self.env.template.render(self.template, field=self)
@@ -314,7 +311,7 @@ def accept(self):
result[field.name] = old[field.name]
else:
result[field.name] = field.accept()
- return self.to_python(result)
+ return self.conv.accept(result)
def set_raw_value(self, raw_data, value):
indeces = []
View
56 tests/forms/convs.py
@@ -17,14 +17,14 @@ class ConverterTests(unittest.TestCase):
def test_accept(self):
'Accept method of converter'
conv = init_conv(convs.Converter)
- value = conv.to_python('value')
+ value = conv.accept('value')
self.assertEqual(value, 'value')
self.assertEqual(conv.field.form.errors, {})
def test_to_python(self):
'Converter to_python method'
conv = init_conv(convs.Converter)
- value = conv.to_python('value')
+ value = conv.accept('value')
self.assertEqual(value, 'value')
self.assertEqual(conv.field.form.errors, {})
@@ -39,20 +39,26 @@ def test_obsolete(self):
'Convertor accepting obsolete parameters'
self.assertRaises(DeprecationWarning, convs.Converter, null=True)
+ def test_filter(self):
+ 'Convertor with filters'
+ conv = convs.Converter(lambda conv, x: x+'-1', lambda conv, x: x+'-2')
+ value = conv.accept('value', silent=True)
+ self.assertEqual(value, 'value-1-2')
+
class IntConverterTests(unittest.TestCase):
def test_accept_valid(self):
'Accept method of Int converter'
conv = init_conv(convs.Int)
- value = conv.to_python('12')
+ value = conv.accept('12')
self.assertEqual(value, 12)
self.assertEqual(conv.field.form.errors, {})
def test_accept_null_value(self):
'Accept method of Int converter for None value'
conv = init_conv(convs.Int(required=False))
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, None)
self.assertEqual(conv.field.form.errors, {})
@@ -72,7 +78,7 @@ def test_accept_valid(self):
(1, 'result')
], conv=convs.Int()))
- value = conv.to_python('1')
+ value = conv.accept('1')
self.assertEqual(value, 1)
self.assertEqual(conv.field.form.errors, {})
@@ -82,7 +88,7 @@ def test_accept_null_value(self):
(1, 'result')
], conv=convs.Int(), required=False))
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, None)
self.assertEqual(conv.field.form.errors, {})
@@ -92,9 +98,9 @@ def test_accept_invalid_value(self):
(1, 'result')
], conv=convs.Int(), required=False))
- value = conv.to_python('unknown')
+ value = conv.accept('unknown')
self.assertEqual(value, None)
- self.assertEqual(conv.field.form.errors.keys(), [conv.field.name])
+ self.assertEqual(conv.field.form.errors.keys(), [])
def test_accept_missing_value(self):
'Accept method of EnumChoice converter for None value'
@@ -102,7 +108,7 @@ def test_accept_missing_value(self):
(1, 'result')
], conv=convs.Int(), required=False))
- value = conv.to_python('2')
+ value = conv.accept('2')
self.assertEqual(value, None)
self.assertEqual(conv.field.form.errors, {})
@@ -112,7 +118,7 @@ def test_decline_null_value(self):
(1, 'result')
], conv=convs.Int(), required=True))
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, None)
self.assertEqual(conv.field.form.errors.keys(),
[conv.field.name])
@@ -122,7 +128,7 @@ def test_decline_invalid_value(self):
conv = init_conv(convs.EnumChoice(choices=[
(1, 'result')
], conv=convs.Int(), required=True))
- value = conv.to_python('invalid')
+ value = conv.accept('invalid')
self.assertEqual(value, None)
self.assertEqual(conv.field.form.errors.keys(),
[conv.field.name])
@@ -132,7 +138,7 @@ def test_decline_missing_value(self):
conv = init_conv(convs.EnumChoice(choices=[
(1, 'result')
], conv=convs.Int(), required=True))
- value = conv.to_python('2')
+ value = conv.accept('2')
self.assertEqual(value, None)
self.assertEqual(conv.field.form.errors.keys(),
[conv.field.name])
@@ -153,21 +159,21 @@ class CharConverterTests(unittest.TestCase):
def test_accept_valid(self):
'Accept method of Char converter'
conv = init_conv(convs.Char)
- value = conv.to_python('12')
+ value = conv.accept('12')
self.assertEqual(value, u'12')
self.assertEqual(conv.field.form.errors, {})
def test_accept_null_value(self):
'Accept method of Char converter for None value'
conv = init_conv(convs.Char(required=False))
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, '')
self.assertEqual(conv.field.form.errors, {})
def test_accept_null_value_regex(self):
'Accept empty value by Char converter with non-empty regexp'
conv = init_conv(convs.Char(regex='.+', required=False))
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, '') # XXX
self.assertEqual(conv.field.form.errors, {})
@@ -175,9 +181,9 @@ def test_regex_error(self):
conv = init_conv(convs.Char(regex='ZZZ', required=True))
# XXX This should look like the following:
#with self.assertRaises(convs.ValidationError) as cm:
- # conv.to_python('AAA')
+ # conv.accept('AAA')
#self.assertIn(conv.regex, cm.exception.message)
- conv.to_python('AAA')
+ conv.accept('AAA')
field_name = conv.field.name
errors = conv.field.form.errors
self.assertEqual(conv.field.form.errors.keys(), [field_name])
@@ -193,19 +199,19 @@ def test_from_python(self):
def test_strip(self):
'convs.Char.strip tests'
conv = init_conv(convs.Char(regex="\d+"))
- value = conv.to_python(' 12')
+ value = conv.accept(' 12')
self.assertEqual(value, u'12')
self.assertEqual(conv.field.form.errors, {})
conv = init_conv(convs.Char(strip=False))
- value = conv.to_python(' 12')
+ value = conv.accept(' 12')
self.assertEqual(value, u' 12')
self.assertEqual(conv.field.form.errors, {})
def test_strip_required(self):
'convs.Char.strip tests for required'
conv = init_conv(convs.Char(required=True, strip=True))
- value = conv.to_python(' ')
+ value = conv.accept(' ')
self.assertEqual(value, None) # XXX
field_name = conv.field.name
self.assertEqual(conv.field.form.errors.keys(), [field_name])
@@ -215,19 +221,19 @@ class BoolConverterTests(unittest.TestCase):
def test_accept_true(self):
conv = init_conv(convs.Bool)
- value = conv.to_python('xx')
+ value = conv.accept('xx')
self.assertEqual(value, True)
self.assertEqual(conv.field.form.errors, {})
def test_accept_false(self):
conv = init_conv(convs.Bool)
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, False)
self.assertEqual(conv.field.form.errors, {})
def test_required(self):
conv = init_conv(convs.Bool(required=True))
- value = conv.to_python('')
+ value = conv.accept('')
self.assertEqual(value, False) # XXX is this right?
field_name = conv.field.name
self.assertEqual(conv.field.form.errors, {})
@@ -252,7 +258,7 @@ def test_accept_valid(self):
'''Date converter to_python method'''
from datetime import date
conv = init_conv(convs.Date(format="%d.%m.%Y"))
- self.assertEqual(conv.to_python('31.01.1999'), date(1999, 1, 31))
+ self.assertEqual(conv.accept('31.01.1999'), date(1999, 1, 31))
self.assertEqual(conv.field.form.errors, {})
def test_readable_format(self):
@@ -293,7 +299,7 @@ def test_to_python(self):
'''Time converter to_python method'''
from datetime import time
conv = init_conv(convs.Time)
- self.assertEqual(conv.to_python('12:30'), time(12, 30))
+ self.assertEqual(conv.accept('12:30'), time(12, 30))
self.assertEqual(conv.field.form.errors, {})
Something went wrong with that request. Please try again.