Skip to content

Commit

Permalink
Merge branch 'next'
Browse files Browse the repository at this point in the history
  • Loading branch information
amol- committed Apr 6, 2018
2 parents 1026853 + 2934661 commit 4bf4860
Show file tree
Hide file tree
Showing 11 changed files with 147 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ install:
- "pip install --upgrade setuptools"
- "pip install git+git://github.com/TurboGears/crank.git"
- "pip install git+git://github.com/TurboGears/backlash.git"
- "pip install git+git://git.code.sf.net/p/merciless/code"
- "pip install repoze.who"
- "pip install coverage==4.2"
- "pip install nose"
- "pip install --no-use-wheel -e .[testing]"
- "pip install --upgrade git+http://git.code.sf.net/p/merciless/code"

- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then pip install git+git://github.com/TurboGears/tgext.chameleon_genshi.git; fi
- if [[ $TRAVIS_PYTHON_VERSION == '2.7' ]]; then pip install git+git://github.com/TurboGears/tgext.chameleon_genshi.git; fi
Expand Down
6 changes: 6 additions & 0 deletions CHANGES.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
= Change Log =

== 2.3.12 (**April 6, 2018**) ==

* OPTIONS HTTP method for a RestController will now respond with a 204 "No Content" by default instead of a 404
* WebOb version constrained to >=1.2,<1.8.0 to ensure full compatibility with TurboGears2.3
* Fixed highlight of active page in Jinja quickstart

== 2.3.11 (**July 3, 2017**) ==

* When configuring authentication it is now possible to provide ``('default', None)`` as an identifier to set custom identifiers and retain the default one.
Expand Down
10 changes: 6 additions & 4 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@
'Mako',
'WebTest < 2.0',
'backlash >= 0.0.7',
'sqlalchemy',
'raven < 4.1.0',
'formencode>=1.3.0a1',
'tw2.forms',
Expand All @@ -38,8 +37,11 @@
test_requirements.append('coverage')


if py_version != (3, 2):
# Ming is not compatible with Python3.2
if py_version == (2, 6):
test_requirements.append('sqlalchemy < 1.2')
test_requirements.append('ming < 0.5.6')
else:
test_requirements.append('sqlalchemy')
test_requirements.append('ming > 0.5.0')


Expand All @@ -49,7 +51,7 @@
'tw.forms'])

install_requires=[
'WebOb >= 1.2',
'WebOb >= 1.2, < 1.8.0',
'crank >= 0.8.0, < 0.9.0',
'repoze.lru'
]
Expand Down
4 changes: 4 additions & 0 deletions tests/test_rest_controller_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,10 @@ def __init__(self, *args, **kargs):
TestWSGIController.__init__(self, *args, **kargs)
self.app = make_app(BasicTGController)

def test_options(self):
r = self.app.options('/rest/')
assert r.status_int == 204

def test_post(self):
r = self.app.post('/rest/')
assert 'rest post' in r, r
Expand Down
63 changes: 62 additions & 1 deletion tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import json
from functools import partial
from nose.tools import raises
from nose import SkipTest
Expand Down Expand Up @@ -375,6 +376,34 @@ def unicode_error_pow(self, num=-1):
def lazy_unicode_error_pow(self, num=-1):
return str(num * num)

@expose(content_type='text/plain')
@validate({
'val': Convert(lambda v: int(v) > 0 or int('ERROR'))
}, error_handler=validation_errors_response)
def chain_validation_0(self, val):
return '>0'

@expose(content_type='text/plain')
@validate({
'val': Convert(lambda v: int(v) > 1 or int('ERROR'))
}, error_handler=chain_validation_0, chain_validation=True)
def chain_validation_1(self, val):
return '>1'

@expose(content_type='text/plain')
@validate({
'val': Convert(lambda v: int(v) > 2 or int('ERROR'))
}, error_handler=chain_validation_1, chain_validation=True)
def chain_validation_2(self, val):
return '>2'

@expose(content_type='text/plain')
@validate({
'val': Convert(lambda v: int(v) > 3 or int('ERROR'))
}, error_handler=chain_validation_2, chain_validation=True)
def chain_validation_begin(self, val):
return '>3'


class TestTGController(TestWSGIController):
def setUp(self):
Expand Down Expand Up @@ -688,4 +717,36 @@ def test_validation_errors_unicode(self):

def test_validation_errors_lazy_unicode(self):
resp = self.app.post('/lazy_unicode_error_pow', {'num': 'NOT_A_NUMBER'}, status=412)
assert resp.json['errors']['num'] == u_('àèìòù'), resp.json
assert resp.json['errors']['num'] == u_('àèìòù'), resp.json


class TestChainValidation(TestWSGIController):
def setUp(self):
TestWSGIController.setUp(self)
tg.config.update({
'paths': {'root': data_dir},
'package': tests,
})

self.app = make_app(BasicTGController, config_options={
'i18n.enabled': True
})

def test_no_chain_validation(self):
res = self.app.get('/chain_validation_begin', params={'val': 4})
self.assertEqual(res.text, '>3')

res = self.app.get('/chain_validation_begin', params={'val': 3})
self.assertEqual(res.text, '>2')

def test_single_chain_validation(self):
res = self.app.get('/chain_validation_begin', params={'val': 2})
self.assertEqual(res.text, '>1')

def test_double_chain_validation(self):
res = self.app.get('/chain_validation_begin', params={'val': 1})
self.assertEqual(res.text, '>0')

def test_last_chain_validation(self):
res = self.app.get('/chain_validation_begin', params={'val': 0}, status=412)
self.assertEqual(res.json, json.loads('{"errors":{"val":"Invalid"},"values":{"val":"0"}}'))
6 changes: 3 additions & 3 deletions tg/configuration/app_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,9 @@ class _DeprecatedControllerWrapper(object):
def __init__(self, controller_wrapper, config, next_wrapper):
# Backward compatible old-way of configuring controller wrappers
warnings.warn("Controller wrapper will now accept the configuration"
"as parameter when called instead of receiving it as"
"a constructor parameter, please refer to the documentation"
"to update your controller wrappers",
" as parameter when called instead of receiving it as"
" a constructor parameter, please refer to the documentation"
" to update your controller wrappers",
DeprecationWarning, stacklevel=3)

def _adapted_next_wrapper(controller, remainder, params):
Expand Down
50 changes: 32 additions & 18 deletions tg/controllers/decoratedcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def _is_exposed(self, controller, name):
if method and inspect.ismethod(method) and hasattr(method, 'decoration'):
return method.decoration.exposed

def _call(self, controller, params, remainder=None, context=None):
def _call(self, action, params, remainder=None, context=None):
"""Run the controller with the given parameters.
_call is called by _perform_call in CoreDispatcher.
Expand Down Expand Up @@ -94,38 +94,50 @@ def _call(self, controller, params, remainder=None, context=None):
remainder = tuple()

hooks.notify('before_validate', args=(remainder, params),
controller=controller, context_config=context_config)
controller=action, context_config=context_config)

validate_params = get_params_with_argspec(controller, params, remainder)
validate_params = get_params_with_argspec(action, params, remainder)
context.request.args_params = validate_params # Update args_params with positional args

try:
params = self._perform_validate(controller, validate_params, context)
params = self._perform_validate(action, validate_params, context)
except validation_errors as inv:
instance, controller = self._process_validation_errors(controller,
remainder, params,
inv, context=context)
bound_controller_callable = partial(controller, instance)
instance, error_handler, chain_validation = self._process_validation_errors(
action, remainder, params, inv, context=context
)
while chain_validation:
# The validation asked for chained validation,
# go on and validate the error_handler too.
try:
params = self._perform_validate(error_handler, validate_params, context)
except validation_errors as inv:
instance, error_handler, chain_validation = self._process_validation_errors(
error_handler, remainder, params, inv, context=context
)
else:
chain_validation = False
action = error_handler
bound_controller_callable = partial(error_handler, instance)
else:
bound_controller_callable = controller
bound_controller_callable = action
context.request.validation.values = params
remainder, params = flatten_arguments(controller, params, remainder)
remainder, params = flatten_arguments(action, params, remainder)

hooks.notify('before_call', args=(remainder, params),
controller=controller, context_config=context_config)
controller=action, context_config=context_config)

# call controller method with applied wrappers
controller_caller = controller.decoration.controller_caller
controller_caller = action.decoration.controller_caller
output = controller_caller(context_config, bound_controller_callable, remainder, params)

# Render template
hooks.notify('before_render', args=(remainder, params, output),
controller=controller, context_config=context_config)
controller=action, context_config=context_config)

response = self._render_response(context, controller, output)
response = self._render_response(context, action, output)

hooks.notify('after_render', args=(response,),
controller=controller, context_config=context_config)
controller=action, context_config=context_config)

return response['response']

Expand Down Expand Up @@ -285,10 +297,12 @@ def _process_validation_errors(cls, controller, remainder, params, exception, co

# Get the error handler associated to the current validation status.
error_handler = validation_status.error_handler
chain_validation = validation_status.chain_validation
if error_handler is None:
error_handler = default_im_func(controller)
chain_validation = False

return im_self(controller), error_handler
return im_self(controller), error_handler, chain_validation

@classmethod
def _handle_validation_errors(cls, controller, remainder, params, exception, tgl=None):
Expand All @@ -305,8 +319,8 @@ def _handle_validation_errors(cls, controller, remainder, params, exception, tgl
# compatibility with old code that didn't pass request locals explicitly
tgl = tg.request.environ['tg.locals']

obj, error_handler = cls._process_validation_errors(controller, remainder, params,
exception, tgl)
obj, error_handler,_ = cls._process_validation_errors(controller, remainder, params,
exception, tgl)
return error_handler, error_handler(obj, *remainder, **dict(params))

def _check_security(self):
Expand Down
13 changes: 13 additions & 0 deletions tg/controllers/restcontroller.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

from crank.restdispatcher import RestDispatcher
from tg.decorators import expose

from tg.controllers.dispatcher import CoreDispatcher
from tg.controllers.decoratedcontroller import DecoratedController
Expand Down Expand Up @@ -69,4 +70,16 @@ class RestController(DecoratedController, CoreDispatcher, RestDispatcher):
"""

@expose()
def options(self, *args, **kw):
"""The OPTIONS http verb is automatically sent by the browser when doing cross origin requests.
This default method was added to prevent receiving 404 HTTP Not found error.
It returns None which translates to 204 No Content for HTTP.
You can use the _before hook to set up your CORS headers.
"""
return None


__all__ = ['RestController']
20 changes: 8 additions & 12 deletions tg/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -583,23 +583,19 @@ class validate(_ValidationIntent):
you can use the ``@validate()`` decorator to register
the validators that ought to be called.
:Parameters:
validators
Pass in a dictionary of FormEncode validators.
The keys should match the form field names.
error_handler
Pass in the controller method which shoudl be used
to handle any form errors
form
Pass in a ToscaWidget based form with validators
:param validators: A dictionary of FormEncode/TW2 validators, a :class:`tg.validation.Convert`
or any callable that might throw :class:`tg.validation.TGValidationError`.
:param error_handler: Function or action that should be used to handle the errors.
:param form: A TW2 or ToscaWidgets form to validate ( to be provided instead of ``validators`` )
:param chain_validation: Whenever ``error_handler`` should perform validation too in
case it's a controller action or not. By default it's disabled.
The first positional parameter can either be a dictonary of validators,
a FormEncode schema validator, or a callable which acts like a FormEncode
validator.
"""
def __init__(self, validators=None, error_handler=None, form=None):
super(validate, self).__init__(validators or form, error_handler)
def __init__(self, validators=None, error_handler=None, form=None, chain_validation=False):
super(validate, self).__init__(validators or form, error_handler, chain_validation)

def __call__(self, func):
deco = Decoration.get_decoration(func)
Expand Down
8 changes: 4 additions & 4 deletions tg/release.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""TurboGears project related information"""
version = "2.3.11"
version = "2.3.12"
description = "Next generation TurboGears"
long_description="""
TurboGears brings together a best of breed python tools
Expand All @@ -24,7 +24,7 @@
https://github.com/TurboGears
"""
url="http://www.turbogears.org/"
author= "Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, Michael Pedersen, Alessandro Molina, and the TurboGears community"
email = "[email protected], [email protected]"
copyright = """Copyright 2005-2017 Kevin Dangoor, Alberto Valverde, Mark Ramm, Christopher Perkins, Alessandro Molina and contributors"""
author= "Alessandro Molina, Mark Ramm, Christopher Perkins, Jonathan LaCour, Rick Copland, Alberto Valverde, Michael Pedersen and the TurboGears community"
email = "[email protected]"
copyright = """Copyright 2005-2018 Kevin Dangoor, Alberto Valverde, Mark Ramm, Christopher Perkins, Alessandro Molina and contributors"""
license = "MIT"
10 changes: 8 additions & 2 deletions tg/validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,14 @@ def __init__(self, errors=None, values=None, exception=None, intent=None):
def error_handler(self):
if self.intent is None:
return None

return self.intent.error_handler

@property
def chain_validation(self):
if self.intent is None:
return False
return self.intent.chain_validation

def __getitem__(self, item):
warnings.warn("Accessing validation status properties with [] syntax is deprecated. "
" Please use dot notation instead", DeprecationWarning, stacklevel=2)
Expand All @@ -61,9 +66,10 @@ class _ValidationIntent(object):
validation itself on the parameters for a given
controller method.
"""
def __init__(self, validators, error_handler):
def __init__(self, validators, error_handler, chain_validation):
self.validators = validators
self.error_handler = error_handler
self.chain_validation = chain_validation

def check(self, method, params):
validators = self.validators
Expand Down

0 comments on commit 4bf4860

Please sign in to comment.