Skip to content

Commit

Permalink
Refactor validation status reporting, tmpl_context.form_errors and tm…
Browse files Browse the repository at this point in the history
…pl_context.form_values got removed in favor of request.validation which keeps more details
  • Loading branch information
amol- committed Oct 3, 2013
1 parent 9897251 commit 59f8ffc
Show file tree
Hide file tree
Showing 8 changed files with 103 additions and 62 deletions.
6 changes: 3 additions & 3 deletions tests/test_stack/rendering/controllers/root.py
@@ -1,7 +1,7 @@
"""Main Controller"""
import tg
from tg import expose, redirect, config, validate, override_template, response, render_template, tmpl_context
from tg import cache, i18n
from tg import cache, i18n, request
from tg.decorators import paginate, use_custom_format, with_trailing_slash, Decoration, before_render
from tg.controllers import TGController
from tg.validation import TGValidationError
Expand Down Expand Up @@ -159,7 +159,7 @@ def foreign(self):
@validate(form=base_movie_form)
def process_form_errors(self, **kwargs):
#add error messages to the kwargs dictionary and return it
kwargs['errors'] = tmpl_context.form_errors
kwargs['errors'] = request.validation['errors']
return dict(kwargs)

@expose()
Expand Down Expand Up @@ -433,4 +433,4 @@ def render_func(*args, **kw):
cached_template('index.html', render_func, **kwargs)
return _cache_options
finally:
tg.cache = old_cache
tg.cache = old_cache
14 changes: 13 additions & 1 deletion tests/test_util.py
Expand Up @@ -5,7 +5,7 @@
from nose.tools import eq_, raises
import os
from tg.controllers.util import *
from tg.wsgiapp import ContextObj
from tg.util import ContextObj, AttribSafeContextObj

import tg._compat
from tg._compat import u_
Expand Down Expand Up @@ -111,6 +111,18 @@ def test_lazy_string_format(self):
lf = l.format('HI')
assert lf == 'HI', lf

class TestAttribSafeContextObj(object):
def setup(self):
self.c = AttribSafeContextObj()

def test_attribute_default_value(self):
assert self.c.something == ''

self.c.something = 'HELLO'
assert self.c.something == 'HELLO'

assert self.c.more == ''

def test_tmpl_context_long_entry():
c = ContextObj()
c.something = '3'*300
Expand Down
29 changes: 20 additions & 9 deletions tests/test_validation.py
Expand Up @@ -123,7 +123,7 @@ class BasicTGController(TGController):
@expose()
@validate(ColonLessGenericValidator())
def validator_without_columns(self, **kw):
return tg.tmpl_context.form_errors['_the_form']
return tg.request.validation['errors']['_the_form']

@expose('json:')
@validate(validators={"some_int": validators.Int()})
Expand All @@ -146,8 +146,8 @@ def validate_controller_based_validator(self, *args, **kw):
@expose('json:')
@validate(validators={"a": validators.Int(), "someemail": validators.Email()})
def two_validators(self, a=None, someemail=None, *args):
errors = tg.tmpl_context.form_errors
values = tg.tmpl_context.form_values
errors = tg.request.validation['errors']
values = tg.request.validation['values']
return dict(a=a, someemail=someemail,
errors=str(errors), values=str(values))

Expand All @@ -163,7 +163,7 @@ def with_default_shadow(self, a, b=None ):
@expose('json:')
@validate(validators={"e": ColonValidator()})
def error_with_colon(self, e):
errors = tg.tmpl_context.form_errors
errors = tg.request.validation['errors']
return dict(errors=str(errors))

@expose('json:')
Expand All @@ -187,18 +187,18 @@ def display_form(self, **kwargs):
@expose('json:')
@validate(form=myform)
def process_form(self, **kwargs):
kwargs['errors'] = tg.tmpl_context.form_errors
kwargs['errors'] = tg.request.validation['errors']
return dict(kwargs)

@expose('json:')
@validate(form=myform, error_handler=process_form)
def send_to_error_handler(self, **kwargs):
kwargs['errors'] = tg.tmpl_context.form_errors
kwargs['errors'] = tg.request.validation['errors']
return dict(kwargs)

@expose()
def tw2form_error_handler(self, **kwargs):
return dumps(dict(errors=tg.tmpl_context.form_errors))
return dumps(dict(errors=tg.request.validation['errors']))

@expose('json:')
@validate(form=movie_form, error_handler=tw2form_error_handler)
Expand All @@ -208,7 +208,7 @@ def send_tw2_to_error_handler(self, **kwargs):
@expose()
@validate({'param':tw2c.IntValidator()})
def tw2_dict_validation(self, **kwargs):
return str(tg.tmpl_context.form_errors)
return str(tg.request.validation['errors'])

@expose()
def set_lang(self, lang=None):
Expand All @@ -220,7 +220,7 @@ def set_lang(self, lang=None):
@expose()
@validate(validators=Pwd())
def password(self, pwd1, pwd2):
if tg.tmpl_context.form_errors:
if tg.request.validation['errors']:
return "There was an error"
else:
return "Password ok!"
Expand All @@ -235,6 +235,12 @@ def hooked_error_handler(self, *args, **kw):
def with_hooked_error_handler(self, *args, **kw):
return dict(GOT_ERROR='NO ERROR')

@expose('json')
@validate({'v': validators.Int()})
def check_tmpl_context_compatibility(self, *args, **kw):
return dict(tmpl_errors=str(tg.tmpl_context.form_errors),
errors=str(tg.request.validation['errors']))

@expose()
def error_handler(self, *args, **kw):
return 'ERROR HANDLER!'
Expand Down Expand Up @@ -413,6 +419,11 @@ def test_hook_after_validation_error(self):
resp = self.app.post('/with_hooked_error_handler?v=a')
assert 'HOOKED' in resp, resp

def test_check_tmpl_context_compatibility(self):
resp = self.app.post('/check_tmpl_context_compatibility?v=a')
resp = resp.json
assert resp['errors'] == resp['tmpl_errors'], resp

def test_validation_error_has_message(self):
e = TGValidationError('This is a validation error')
assert str(e) == 'This is a validation error'
39 changes: 21 additions & 18 deletions tg/controllers/decoratedcontroller.py
Expand Up @@ -101,7 +101,7 @@ def _call(self, controller, params, remainder=None, tgl=None):
# Validate user input
params = self._perform_validate(controller, validate_params)

tgl.tmpl_context.form_values = params
tgl.request.validation['values'] = params

hooks.notify('before_call', args=(remainder, params),
controller=controller, context_config=context_config)
Expand Down Expand Up @@ -290,8 +290,7 @@ def _handle_validation_errors(self,
controller, remainder, params, exception):
"""Handle validation errors.
Sets up tg.tmpl_context.form_values
and tg.tmpl_context.form_errors
Sets up validation status and error tracking
to assist generating a form with given values
and the validation failure messages.
Expand All @@ -300,21 +299,23 @@ def _handle_validation_errors(self,
as the error handler instead.
"""
tmpl_context = tg.tmpl_context
tmpl_context.validation_exception = exception
tmpl_context.form_errors = {}
req = tg.request._current_obj()

#TODO: Those should be deprecated because they should not belong to the template context
validation_status = req.validation
validation_status['exception'] = exception

if isinstance(exception, _Tw2ValidationError):
#Fetch all the children and grandchildren of a widget
widget = exception.widget
widget_children = _navigate_tw2form_children(widget.child)

errors = [(child.key, child.error_msg) for child in widget_children]
tmpl_context.form_errors.update(errors)
tmpl_context.form_values = widget.child.value
errors = dict((child.key, child.error_msg) for child in widget_children)
validation_status['errors'] = errors
validation_status['values'] = widget.child.value
elif isinstance(exception, TGValidationError):
tmpl_context.form_errors = exception.error_dict
tmpl_context.form_values = exception.value
validation_status['errors'] = exception.error_dict
validation_status['values'] = exception.value
else:
# Most Invalid objects come back with a list of errors in the format:
#"fieldname1: error\nfieldname2: error"
Expand All @@ -325,25 +326,27 @@ def _handle_validation_errors(self,
#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]
validation_status['errors']['_the_form'] = field_value[0]
continue

tmpl_context.form_errors[field_value[0]] = field_value[1]
validation_status['errors'][field_value[0]] = field_value[1]

tmpl_context.form_values = getattr(exception, 'value', {})
validation_status['values'] = getattr(exception, 'value', {})

error_handler = controller.decoration.validation.error_handler
validation_status['error_handler'] = error_handler = controller.decoration.validation.error_handler
if error_handler is None:
error_handler = controller
validation_status['error_handler'] = error_handler = controller
output = error_handler(*remainder, **dict(params))
else:
output = error_handler(im_self(controller), *remainder, **dict(params))

return error_handler, output

def _initialize_validation_context(self, tgl):
tgl.tmpl_context.form_errors = {}
tgl.tmpl_context.form_values = {}
tgl.request.validation = {'errors': {},
'values': {},
'exception': None,
'error_handler': None}

def _check_security(self):
predicate = getattr(self, 'allow_only', None)
Expand Down
6 changes: 3 additions & 3 deletions tg/decorators.py
Expand Up @@ -22,7 +22,7 @@
from tg.flash import flash
from tg.caching import beaker_cache, cached_property
from tg.predicates import NotAuthorizedError
from tg._compat import im_func, unicode_text
from tg._compat import default_im_func, unicode_text
from webob.acceptparse import Accept
from tg.configuration import milestones
import tg
Expand Down Expand Up @@ -482,7 +482,7 @@ def use_custom_format(controller, custom_format):
render_custom_format = request._render_custom_format
except AttributeError:
render_custom_format = request._render_custom_format = {}
render_custom_format[im_func(controller)] = custom_format
render_custom_format[default_im_func(controller)] = custom_format


def override_template(controller, template):
Expand Down Expand Up @@ -514,7 +514,7 @@ def override_template(controller, template):
override_mapping = request._override_mapping
except AttributeError:
override_mapping = request._override_mapping = {}
override_mapping.setdefault(im_func(controller), {}).update({content_type: tmpl})
override_mapping.setdefault(default_im_func(controller), {}).update({content_type: tmpl})


class validate(object):
Expand Down
4 changes: 2 additions & 2 deletions tg/render.py
Expand Up @@ -78,8 +78,8 @@ def _get_tg_vars():
identity = req.environ.get('repoze.who.identity'),
session = session,
locale = req.plain_languages,
errors = getattr(tmpl_context, "form_errors", {}),
inputs = getattr(tmpl_context, "form_values", {}),
errors = req.validation['errors'],
inputs = req.validation['values'],
request = req,
auth_stack_enabled = 'repoze.who.plugins' in req.environ,
predicates = predicates)
Expand Down
40 changes: 39 additions & 1 deletion tg/util.py
@@ -1,6 +1,8 @@
"""Utilities"""
from pkg_resources import resource_filename
import warnings
from tg.request_local import request


class DottedFileLocatorError(Exception):pass

Expand Down Expand Up @@ -168,4 +170,40 @@ def newfunc(*args, **kwargs):
return newfunc

def call_controller(controller, remainder, params):
return controller(*remainder, **params)
return controller(*remainder, **params)


class ContextObj(object):
def __repr__(self):
attrs = sorted((name, value)
for name, value in self.__dict__.items()
if not name.startswith('_'))
parts = []
for name, value in attrs:
value_repr = repr(value)
if len(value_repr) > 70:
value_repr = value_repr[:60] + '...' + value_repr[-5:]
parts.append(' %s=%s' % (name, value_repr))
return '<%s.%s at %s%s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self)),
','.join(parts))

def __getattr__(self, item):
if item in ('form_values', 'form_errors'):
warnings.warn('tmpl_context.form_values and tmpl_context.form_errors got deprecated '
'use request.validation instead', DeprecationWarning)
return request.validation[item[5:]]

raise AttributeError()


class AttribSafeContextObj(ContextObj):
"""The :term:`tmpl_context` object, with lax attribute access (
returns '' when the attribute does not exist)"""
def __getattr__(self, name):
try:
return ContextObj.__getattr__(self, name)
except AttributeError:
return ''
27 changes: 2 additions & 25 deletions tg/wsgiapp.py
Expand Up @@ -7,43 +7,20 @@
from tg import request_local
from tg.i18n import _get_translator
from tg.request_local import Request, Response
from tg.util import ContextObj, AttribSafeContextObj

try: #pragma: no cover
import pylons
has_pylons = True
except:
has_pylons = False


class RequestLocals(object):
__slots__ = ('response', 'request', 'app_globals',
'config', 'tmpl_context', 'translator',
'session', 'cache', 'url')

class ContextObj(object):
def __repr__(self):
attrs = sorted((name, value)
for name, value in self.__dict__.items()
if not name.startswith('_'))
parts = []
for name, value in attrs:
value_repr = repr(value)
if len(value_repr) > 70:
value_repr = value_repr[:60] + '...' + value_repr[-5:]
parts.append(' %s=%s' % (name, value_repr))
return '<%s.%s at %s%s>' % (
self.__class__.__module__,
self.__class__.__name__,
hex(id(self)),
','.join(parts))

class AttribSafeContextObj(ContextObj):
"""The :term:`tmpl_context` object, with lax attribute access (
returns '' when the attribute does not exist)"""
def __getattr__(self, name):
try:
return object.__getattribute__(self, name)
except AttributeError:
return ''

class TGApp(object):
def __init__(self, config=None, **kwargs):
Expand Down

0 comments on commit 59f8ffc

Please sign in to comment.