Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
53 changes: 50 additions & 3 deletions cornice/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
import logging
from functools import partial

from cornice import util
from cornice.errors import Errors # NOQA
Expand All @@ -14,7 +15,8 @@
register_resource_views,
)
from cornice.util import ContentTypePredicate

from pyramid.events import BeforeRender, NewRequest
from pyramid.i18n import get_localizer
from pyramid.httpexceptions import HTTPNotFound, HTTPForbidden
from pyramid.security import NO_PERMISSION_REQUIRED

Expand All @@ -33,11 +35,53 @@ def add_apidoc(config, pattern, func, service, **kwargs):
info['func'] = func


def set_localizer_for_languages(event, available_languages,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a docstring here?

default_locale_name):
"""
Sets the current locale based on the incoming Accept-Language header, if
present, and sets a localizer attribute on the request object based on
the current locale.

To be used as an event handler, this function needs to be partially applied
with the available_languages and default_locale_name arguments. The
resulting function will be an event handler which takes an event object as
its only argument.
"""
request = event.request
if request.accept_language:
accepted = request.accept_language
locale = accepted.best_match(available_languages, default_locale_name)
request._LOCALE_ = locale
localizer = get_localizer(request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if we should do that all the time, or only lazy load it and do it when we really need?

request.localizer = localizer


def setup_localization(config):
"""
Setup localization based on the available_languages and
pyramid.default_locale_name settings.

These settings are named after suggestions from the "Internationalization
and Localization" section of the Pyramid documentation.
"""
try:
config.add_translation_dirs('colander:locale/')
settings = config.get_settings()
available_languages = settings['available_languages'].split()
default_locale_name = settings.get('pyramid.default_locale_name', 'en')
set_localizer = partial(set_localizer_for_languages,
available_languages=available_languages,
default_locale_name=default_locale_name)
config.add_subscriber(set_localizer, NewRequest)
except ImportError:
# add_translation_dirs raises an ImportError if colander is not
# installed
pass


def includeme(config):
"""Include the Cornice definitions
"""
from pyramid.events import BeforeRender, NewRequest

# attributes required to maintain services
config.registry.cornice_services = {}

Expand All @@ -61,3 +105,6 @@ def includeme(config):
permission=NO_PERMISSION_REQUIRED)
config.add_view(handle_exceptions, context=HTTPForbidden,
permission=NO_PERMISSION_REQUIRED)

if settings.get('available_languages'):
setup_localization(config)
6 changes: 4 additions & 2 deletions cornice/schemas.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,11 +148,13 @@ def _validate_fields(location, data):
deserialized = attr.deserialize(serialized)
except Invalid as e:
# the struct is invalid
translate = request.localizer.translate
error_dict = e.asdict(translate=translate)
try:
request.errors.add(location, attr.name,
e.asdict()[attr.name])
error_dict[attr.name])
except KeyError:
for k, v in e.asdict().items():
for k, v in error_dict.items():
if k.startswith(attr.name):
request.errors.add(location, k, v)
else:
Expand Down
53 changes: 53 additions & 0 deletions cornice/tests/test_validation.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# -*- encoding: utf-8 -*-
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this file,
# You can obtain one at http://mozilla.org/MPL/2.0/.
Expand Down Expand Up @@ -365,3 +366,55 @@ def low_priority_deserializer(request):
"hello,open,yeah",
headers={'content-type': 'text/dummy'})
self.assertEqual(response.json['test'], 'succeeded')


class TestErrorMessageTranslation(TestCase):

def post(self, settings={}, headers={}):
app = TestApp(main({}, **settings))
return app.post_json('/foobar?yeah=test', {
'foo': 'hello',
'bar': 'open',
'yeah': 'man',
'ipsum': 10,
}, status=400, headers=headers)

def assertErrorDescription(self, response, message):
error_description = response.json['errors'][0]['description']
self.assertEqual(error_description, message)

def test_accept_language_header(self):
response = self.post(
settings={'available_languages': 'fr en'},
headers={'Accept-Language': 'fr'})
self.assertErrorDescription(
response,
u'10 est plus grand que la valeur maximum autorisée (3)')

def test_default_language(self):
response = self.post(settings={
'available_languages': 'fr ja',
'pyramid.default_locale_name': 'ja',
})
self.assertErrorDescription(
response,
u'10 は最大値 3 を超過しています')

def test_default_language_fallback(self):
"""Should fallback to default language if requested language is not
available"""
response = self.post(
settings={
'available_languages': 'ja en',
'pyramid.default_locale_name': 'ja',
},
headers={'Accept-Language': 'ru'})
self.assertErrorDescription(
response,
u'10 は最大値 3 を超過しています')

def test_no_language_settings(self):
response = self.post()
self.assertErrorDescription(
response,
u'10 is greater than maximum value 3')
2 changes: 1 addition & 1 deletion cornice/tests/validationapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,6 @@ def includeme(config):


def main(global_config, **settings):
config = Configurator(settings={})
config = Configurator(settings=settings)
config.include(includeme)
return CatchErrors(config.make_wsgi_app())
4 changes: 4 additions & 0 deletions docs/source/validation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,10 @@ before passing the result to Colander.
View-specific deserializers have priority over global content-type
deserializers.

To enable localization of Colander error messages, you must set
`available_languages <http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/i18n.html#detecting-available-languages>`_ in your settings.
You may also set `pyramid.default_locale_name <http://docs.pylonsproject.org/projects/pyramid/en/1.3-branch/narr/environment.html#default-locale-name-setting>`_.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lmctv good catch, done


Using formencode
~~~~~~~~~~~~~~~~
Expand Down