Permalink
Browse files

Make form_errors work with TW2, add test unit and refactor error mana…

…gement to be a bit less coupled to formencode
  • Loading branch information...
1 parent 7172173 commit 15e46663d6384d57ac2e01819f1c44f7494987ef @amol- amol- committed Mar 6, 2012
Showing with 83 additions and 36 deletions.
  1. +1 −0 setup.py
  2. +25 −1 tests/test_validation.py
  3. +48 −34 tg/controllers/decoratedcontroller.py
  4. +9 −1 tg/util.py
View
@@ -23,6 +23,7 @@
'repoze.tm2 >= 1.0a4',
'wsgiref',
'tw.forms',
+ 'tw2.forms',
'Kajiki>=0.2.2',
'Genshi >= 0.5.1',
'TurboKid >= 1.0.4',
@@ -4,7 +4,7 @@
import pylons
from formencode import validators, Schema
-from simplejson import loads
+from simplejson import loads, dumps
from tg.controllers import TGController
from tg.decorators import expose, validate, before_render
@@ -14,6 +14,14 @@
from tw.forms import TableForm, TextField
from tw.api import WidgetsList
+import tw2.core as tw2c
+import tw2.forms as tw2f
+
+class MovieForm(tw2f.TableForm):
+ title = tw2f.TextField(validator=tw2c.Required)
+ year = tw2f.TextField(size=4, validator=tw2c.IntValidator)
+movie_form = MovieForm(action='save_movie')
+
def setup():
setup_session_dir()
@@ -127,6 +135,15 @@ def send_to_error_handler(self, **kwargs):
return dict(kwargs)
@expose()
+ def tw2form_error_handler(self, **kwargs):
+ return dumps(dict(errors=pylons.tmpl_context.form_errors))
+
+ @expose('json')
+ @validate(form=movie_form, error_handler=tw2form_error_handler)
+ def send_tw2_to_error_handler(self, **kwargs):
+ return 'passed validation'
+
+ @expose()
def set_lang(self, lang=None):
pylons.session['tg_lang'] = lang
pylons.session.save()
@@ -238,6 +255,13 @@ def test_form_validation_redirect(self):
assert "Please enter an integer value" in values['errors']['year'], \
'Error message not found: %r' % values['errors']
+ def test_tw2form_validation(self):
+ form_values = {'title': 'Razer', 'year': "t007"}
+ resp = self.app.post('/send_tw2_to_error_handler', form_values)
+ values = loads(resp.body)
+ assert "Must be an integer" in values['errors']['year'],\
+ 'Error message not found: %r' % values['errors']
+
def test_form_validation_translation(self):
"""Test translation of form validation error messages"""
form_values = {'title': 'Razer', 'year': "t007"}
@@ -6,9 +6,9 @@
"""
from urllib import url2pathname
-import inspect
+import inspect, operator
-import formencode
+strip_string = operator.methodcaller('strip')
try:
from repoze.what.predicates import (
@@ -31,10 +31,29 @@ def not_anonymous():
from tg.jsonify import JsonEncodeError
from tg.render import render as tg_render
from tg.controllers.util import pylons_formencode_gettext
+from tg.util import _navigate_tw2form_children
# Load tw (ToscaWidets) only on demand
tw = None
+try:
+ from tw2.core import ValidationError as Tw2ValidationError
+except ImportError:
+ class Tw2ValidationError(Exception):
+ """ToscaWidgets2 Validation Error"""
+
+try:
+ from formencode.api import Invalid as FormEncodeValidationError
+ from formencode import Schema as FormEncodeSchema
+ from formencode.schema import format_compound_error
+except ImportError:
+ class FormEncodeValidationError(Exception):
+ """FormEncode Invalid"""
+ class FormEncodeSchema(object):
+ """FormEncode Schema"""
+ def format_compound_error(*arg, **kw):
+ """FormEncode format_compound_error"""
+
# @expose(content_type=CUSTOM_CONTENT_TYPE) won't
# override pylons.request.content_type
CUSTOM_CONTENT_TYPE = 'CUSTOM/LEAVE'
@@ -124,19 +143,9 @@ def _call(self, controller, params, remainder=None):
# call controller method
output = controller_callable(*remainder, **dict(params))
- except formencode.api.Invalid, inv:
+ except (FormEncodeValidationError, Tw2ValidationError) , inv:
controller, output = self._handle_validation_errors(
controller, remainder, params, inv)
- except Exception, e:
- if config.get('use_toscawidgets2'):
- from tw2.core import ValidationError
- if isinstance(e, ValidationError):
- controller, output = self._handle_validation_errors(
- controller, remainder, params, e)
- else:
- raise
- else:
- raise
#Be sure that we run hooks if the controller changed due to validation errors
tg_decoration = controller.decoration
@@ -197,7 +206,7 @@ def _perform_validate(self, controller, params):
new_params[field] = validator.to_python(params.get(field),
state)
# catch individual validation errors into the errors dictionary
- except formencode.api.Invalid, inv:
+ except FormEncodeValidationError, inv:
errors[field] = inv
# Parameters that don't have validators are returned verbatim
@@ -208,11 +217,10 @@ def _perform_validate(self, controller, params):
# If there are errors, create a compound validation error based on
# the errors dictionary, and raise it as an exception
if errors:
- raise formencode.api.Invalid(
- formencode.schema.format_compound_error(errors),
+ raise FormEncodeValidationError(format_compound_error(errors),
params, None, error_dict=errors)
- elif isinstance(validation.validators, formencode.Schema):
+ elif isinstance(validation.validators, FormEncodeSchema):
# A FormEncode Schema object - to_python converts the incoming
# parameters to sanitized Python values
new_params = validation.validators.to_python(params, state)
@@ -356,28 +364,34 @@ def _handle_validation_errors(self,
"""
- pylons.tmpl_context.validation_exception = exception
- pylons.tmpl_context.form_errors = {}
+ tmpl_context = pylons.tmpl_context
+ tmpl_context.validation_exception = exception
+ tmpl_context.form_errors = {}
- # Most Invalid objects come back with a list of errors in the format:
- #"fieldname1: error\nfieldname2: error"
+ if isinstance(exception, Tw2ValidationError):
+ #Fetch all the children and grandchildren of a widget
+ widget = exception.widget
+ widget_children = _navigate_tw2form_children(widget.child)
- error_list = exception.__str__().split('\n')
-
- for error in error_list:
- field_value = error.split(':', 1)
+ errors = [(child.id, child.error_msg) for child in widget_children]
+ tmpl_context.form_errors.update(errors)
+ tmpl_context.form_values = widget.child.value
+ else:
+ # Most Invalid objects come back with a list of errors in the format:
+ #"fieldname1: error\nfieldname2: error"
+ error_list = exception.__str__().split('\n')
+ for error in error_list:
+ field_value = map(strip_string, error.split(':', 1))
- #if the error has no field associated with it,
- #return the error as a global form error
- if len(field_value) == 1:
- pylons.tmpl_context.form_errors[
- '_the_form'] = field_value[0].strip()
- continue
+ #if the error has no field associated with it,
+ #return the error as a global form error
+ if len(field_value) == 1:
+ tmpl_context.form_errors['_the_form'] = field_value[0]
+ continue
- pylons.tmpl_context.form_errors[
- field_value[0]] = field_value[1].strip()
+ tmpl_context.form_errors[field_value[0]] = field_value[1]
- pylons.tmpl_context.form_values = getattr(exception, 'value', {})
+ tmpl_context.form_values = getattr(exception, 'value', {})
error_handler = controller.decoration.validation.error_handler
if error_handler is None:
View
@@ -235,4 +235,12 @@ def _f(*args, **kwargs):
warnings.simplefilter("ignore")
f(*args, **kwargs)
warnings.resetwarnings()
- return wrap(_f, f)
+ return wrap(_f, f)
+
+def _navigate_tw2form_children(w):
+ if getattr(w, 'id', None):
+ yield w
+ else:
+ for c in getattr(w, 'children', []):
+ for cc in _navigate_tw2form_children(c):
+ yield cc

0 comments on commit 15e4666

Please sign in to comment.