Fixed #11585 -- Added ability to translate and prefix URL patterns with a language code as an alternative method for language discovery. Many thanks to Orne Brocaar for his initial work and Carl Meyer for feedback.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@16405 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
62bb4b8c37
commit
896e3c69c7
1
AUTHORS
1
AUTHORS
|
@ -94,6 +94,7 @@ answer newbie questions, and generally made Django that much better:
|
||||||
Sean Brant
|
Sean Brant
|
||||||
Andrew Brehaut <http://brehaut.net/blog>
|
Andrew Brehaut <http://brehaut.net/blog>
|
||||||
David Brenneman <http://davidbrenneman.com>
|
David Brenneman <http://davidbrenneman.com>
|
||||||
|
Orne Brocaar <http://brocaar.com/>
|
||||||
brut.alll@gmail.com
|
brut.alll@gmail.com
|
||||||
bthomas
|
bthomas
|
||||||
btoll@bestweb.net
|
btoll@bestweb.net
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
from django.core.urlresolvers import RegexURLPattern, RegexURLResolver
|
from django.core.urlresolvers import (RegexURLPattern,
|
||||||
|
RegexURLResolver, LocaleRegexURLResolver)
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.utils.importlib import import_module
|
||||||
|
|
||||||
|
|
||||||
__all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
|
__all__ = ['handler404', 'handler500', 'include', 'patterns', 'url']
|
||||||
|
|
||||||
|
@ -15,6 +18,21 @@ def include(arg, namespace=None, app_name=None):
|
||||||
else:
|
else:
|
||||||
# No namespace hint - use manually provided namespace
|
# No namespace hint - use manually provided namespace
|
||||||
urlconf_module = arg
|
urlconf_module = arg
|
||||||
|
|
||||||
|
if isinstance(urlconf_module, basestring):
|
||||||
|
urlconf_module = import_module(urlconf_module)
|
||||||
|
patterns = getattr(urlconf_module, 'urlpatterns', urlconf_module)
|
||||||
|
|
||||||
|
# Make sure we can iterate through the patterns (without this, some
|
||||||
|
# testcases will break).
|
||||||
|
if isinstance(patterns, (list, tuple)):
|
||||||
|
for url_pattern in patterns:
|
||||||
|
# Test if the LocaleRegexURLResolver is used within the include;
|
||||||
|
# this should throw an error since this is not allowed!
|
||||||
|
if isinstance(url_pattern, LocaleRegexURLResolver):
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
'Using i18n_patterns in an included URLconf is not allowed.')
|
||||||
|
|
||||||
return (urlconf_module, app_name, namespace)
|
return (urlconf_module, app_name, namespace)
|
||||||
|
|
||||||
def patterns(prefix, *args):
|
def patterns(prefix, *args):
|
||||||
|
|
|
@ -1,4 +1,19 @@
|
||||||
from django.conf.urls.defaults import *
|
from django.conf import settings
|
||||||
|
from django.conf.urls.defaults import patterns
|
||||||
|
from django.core.urlresolvers import LocaleRegexURLResolver
|
||||||
|
|
||||||
|
def i18n_patterns(prefix, *args):
|
||||||
|
"""
|
||||||
|
Adds the language code prefix to every URL pattern within this
|
||||||
|
function. This may only be used in the root URLconf, not in an included
|
||||||
|
URLconf.
|
||||||
|
|
||||||
|
"""
|
||||||
|
pattern_list = patterns(prefix, *args)
|
||||||
|
if not settings.USE_I18N:
|
||||||
|
return pattern_list
|
||||||
|
return [LocaleRegexURLResolver(pattern_list)]
|
||||||
|
|
||||||
|
|
||||||
urlpatterns = patterns('',
|
urlpatterns = patterns('',
|
||||||
(r'^setlang/$', 'django.views.i18n.set_language'),
|
(r'^setlang/$', 'django.views.i18n.set_language'),
|
||||||
|
|
|
@ -346,12 +346,12 @@ def extract_views_from_urlpatterns(urlpatterns, base=''):
|
||||||
"""
|
"""
|
||||||
views = []
|
views = []
|
||||||
for p in urlpatterns:
|
for p in urlpatterns:
|
||||||
if hasattr(p, '_get_callback'):
|
if hasattr(p, 'callback'):
|
||||||
try:
|
try:
|
||||||
views.append((p._get_callback(), base + p.regex.pattern))
|
views.append((p.callback, base + p.regex.pattern))
|
||||||
except ViewDoesNotExist:
|
except ViewDoesNotExist:
|
||||||
continue
|
continue
|
||||||
elif hasattr(p, '_get_url_patterns'):
|
elif hasattr(p, 'url_patterns'):
|
||||||
try:
|
try:
|
||||||
patterns = p.url_patterns
|
patterns = p.url_patterns
|
||||||
except ImportError:
|
except ImportError:
|
||||||
|
|
|
@ -11,13 +11,14 @@ import re
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
from django.http import Http404
|
from django.http import Http404
|
||||||
from django.conf import settings
|
|
||||||
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
|
||||||
from django.utils.datastructures import MultiValueDict
|
from django.utils.datastructures import MultiValueDict
|
||||||
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
|
from django.utils.encoding import iri_to_uri, force_unicode, smart_str
|
||||||
from django.utils.functional import memoize, lazy
|
from django.utils.functional import memoize, lazy
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
from django.utils.regex_helper import normalize
|
from django.utils.regex_helper import normalize
|
||||||
|
from django.utils.translation import get_language
|
||||||
|
|
||||||
|
|
||||||
_resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
|
_resolver_cache = {} # Maps URLconf modules to RegexURLResolver instances.
|
||||||
_callable_cache = {} # Maps view and url pattern names to their view functions.
|
_callable_cache = {} # Maps view and url pattern names to their view functions.
|
||||||
|
@ -50,13 +51,13 @@ class ResolverMatch(object):
|
||||||
url_name = '.'.join([func.__module__, func.__name__])
|
url_name = '.'.join([func.__module__, func.__name__])
|
||||||
self.url_name = url_name
|
self.url_name = url_name
|
||||||
|
|
||||||
|
@property
|
||||||
def namespace(self):
|
def namespace(self):
|
||||||
return ':'.join(self.namespaces)
|
return ':'.join(self.namespaces)
|
||||||
namespace = property(namespace)
|
|
||||||
|
|
||||||
|
@property
|
||||||
def view_name(self):
|
def view_name(self):
|
||||||
return ':'.join([ x for x in [ self.namespace, self.url_name ] if x ])
|
return ':'.join([ x for x in [ self.namespace, self.url_name ] if x ])
|
||||||
view_name = property(view_name)
|
|
||||||
|
|
||||||
def __getitem__(self, index):
|
def __getitem__(self, index):
|
||||||
return (self.func, self.args, self.kwargs)[index]
|
return (self.func, self.args, self.kwargs)[index]
|
||||||
|
@ -115,13 +116,43 @@ def get_mod_func(callback):
|
||||||
return callback, ''
|
return callback, ''
|
||||||
return callback[:dot], callback[dot+1:]
|
return callback[:dot], callback[dot+1:]
|
||||||
|
|
||||||
class RegexURLPattern(object):
|
class LocaleRegexProvider(object):
|
||||||
|
"""
|
||||||
|
A mixin to provide a default regex property which can vary by active
|
||||||
|
language.
|
||||||
|
|
||||||
|
"""
|
||||||
|
def __init__(self, regex):
|
||||||
|
# regex is either a string representing a regular expression, or a
|
||||||
|
# translatable string (using ugettext_lazy) representing a regular
|
||||||
|
# expression.
|
||||||
|
self._regex = regex
|
||||||
|
self._regex_dict = {}
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def regex(self):
|
||||||
|
"""
|
||||||
|
Returns a compiled regular expression, depending upon the activated
|
||||||
|
language-code.
|
||||||
|
"""
|
||||||
|
language_code = get_language()
|
||||||
|
if language_code not in self._regex_dict:
|
||||||
|
if isinstance(self._regex, basestring):
|
||||||
|
compiled_regex = re.compile(self._regex, re.UNICODE)
|
||||||
|
else:
|
||||||
|
regex = force_unicode(self._regex)
|
||||||
|
compiled_regex = re.compile(regex, re.UNICODE)
|
||||||
|
self._regex_dict[language_code] = compiled_regex
|
||||||
|
return self._regex_dict[language_code]
|
||||||
|
|
||||||
|
|
||||||
|
class RegexURLPattern(LocaleRegexProvider):
|
||||||
def __init__(self, regex, callback, default_args=None, name=None):
|
def __init__(self, regex, callback, default_args=None, name=None):
|
||||||
# regex is a string representing a regular expression.
|
LocaleRegexProvider.__init__(self, regex)
|
||||||
# callback is either a string like 'foo.views.news.stories.story_detail'
|
# callback is either a string like 'foo.views.news.stories.story_detail'
|
||||||
# which represents the path to a module and a view function name, or a
|
# which represents the path to a module and a view function name, or a
|
||||||
# callable object (view).
|
# callable object (view).
|
||||||
self.regex = re.compile(regex, re.UNICODE)
|
|
||||||
if callable(callback):
|
if callable(callback):
|
||||||
self._callback = callback
|
self._callback = callback
|
||||||
else:
|
else:
|
||||||
|
@ -157,7 +188,8 @@ class RegexURLPattern(object):
|
||||||
|
|
||||||
return ResolverMatch(self.callback, args, kwargs, self.name)
|
return ResolverMatch(self.callback, args, kwargs, self.name)
|
||||||
|
|
||||||
def _get_callback(self):
|
@property
|
||||||
|
def callback(self):
|
||||||
if self._callback is not None:
|
if self._callback is not None:
|
||||||
return self._callback
|
return self._callback
|
||||||
try:
|
try:
|
||||||
|
@ -169,13 +201,11 @@ class RegexURLPattern(object):
|
||||||
mod_name, func_name = get_mod_func(self._callback_str)
|
mod_name, func_name = get_mod_func(self._callback_str)
|
||||||
raise ViewDoesNotExist("Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)))
|
raise ViewDoesNotExist("Tried %s in module %s. Error was: %s" % (func_name, mod_name, str(e)))
|
||||||
return self._callback
|
return self._callback
|
||||||
callback = property(_get_callback)
|
|
||||||
|
|
||||||
class RegexURLResolver(object):
|
class RegexURLResolver(LocaleRegexProvider):
|
||||||
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
def __init__(self, regex, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||||
# regex is a string representing a regular expression.
|
LocaleRegexProvider.__init__(self, regex)
|
||||||
# urlconf_name is a string representing the module containing URLconfs.
|
# urlconf_name is a string representing the module containing URLconfs.
|
||||||
self.regex = re.compile(regex, re.UNICODE)
|
|
||||||
self.urlconf_name = urlconf_name
|
self.urlconf_name = urlconf_name
|
||||||
if not isinstance(urlconf_name, basestring):
|
if not isinstance(urlconf_name, basestring):
|
||||||
self._urlconf_module = self.urlconf_name
|
self._urlconf_module = self.urlconf_name
|
||||||
|
@ -183,9 +213,9 @@ class RegexURLResolver(object):
|
||||||
self.default_kwargs = default_kwargs or {}
|
self.default_kwargs = default_kwargs or {}
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
self.app_name = app_name
|
self.app_name = app_name
|
||||||
self._reverse_dict = None
|
self._reverse_dict = {}
|
||||||
self._namespace_dict = None
|
self._namespace_dict = {}
|
||||||
self._app_dict = None
|
self._app_dict = {}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
|
return smart_str(u'<%s %s (%s:%s) %s>' % (self.__class__.__name__, self.urlconf_name, self.app_name, self.namespace, self.regex.pattern))
|
||||||
|
@ -194,6 +224,7 @@ class RegexURLResolver(object):
|
||||||
lookups = MultiValueDict()
|
lookups = MultiValueDict()
|
||||||
namespaces = {}
|
namespaces = {}
|
||||||
apps = {}
|
apps = {}
|
||||||
|
language_code = get_language()
|
||||||
for pattern in reversed(self.url_patterns):
|
for pattern in reversed(self.url_patterns):
|
||||||
p_pattern = pattern.regex.pattern
|
p_pattern = pattern.regex.pattern
|
||||||
if p_pattern.startswith('^'):
|
if p_pattern.startswith('^'):
|
||||||
|
@ -220,27 +251,30 @@ class RegexURLResolver(object):
|
||||||
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
|
lookups.appendlist(pattern.callback, (bits, p_pattern, pattern.default_args))
|
||||||
if pattern.name is not None:
|
if pattern.name is not None:
|
||||||
lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
|
lookups.appendlist(pattern.name, (bits, p_pattern, pattern.default_args))
|
||||||
self._reverse_dict = lookups
|
self._reverse_dict[language_code] = lookups
|
||||||
self._namespace_dict = namespaces
|
self._namespace_dict[language_code] = namespaces
|
||||||
self._app_dict = apps
|
self._app_dict[language_code] = apps
|
||||||
|
|
||||||
def _get_reverse_dict(self):
|
@property
|
||||||
if self._reverse_dict is None:
|
def reverse_dict(self):
|
||||||
|
language_code = get_language()
|
||||||
|
if language_code not in self._reverse_dict:
|
||||||
self._populate()
|
self._populate()
|
||||||
return self._reverse_dict
|
return self._reverse_dict[language_code]
|
||||||
reverse_dict = property(_get_reverse_dict)
|
|
||||||
|
|
||||||
def _get_namespace_dict(self):
|
@property
|
||||||
if self._namespace_dict is None:
|
def namespace_dict(self):
|
||||||
|
language_code = get_language()
|
||||||
|
if language_code not in self._namespace_dict:
|
||||||
self._populate()
|
self._populate()
|
||||||
return self._namespace_dict
|
return self._namespace_dict[language_code]
|
||||||
namespace_dict = property(_get_namespace_dict)
|
|
||||||
|
|
||||||
def _get_app_dict(self):
|
@property
|
||||||
if self._app_dict is None:
|
def app_dict(self):
|
||||||
|
language_code = get_language()
|
||||||
|
if language_code not in self._app_dict:
|
||||||
self._populate()
|
self._populate()
|
||||||
return self._app_dict
|
return self._app_dict[language_code]
|
||||||
app_dict = property(_get_app_dict)
|
|
||||||
|
|
||||||
def resolve(self, path):
|
def resolve(self, path):
|
||||||
tried = []
|
tried = []
|
||||||
|
@ -267,22 +301,22 @@ class RegexURLResolver(object):
|
||||||
raise Resolver404({'tried': tried, 'path': new_path})
|
raise Resolver404({'tried': tried, 'path': new_path})
|
||||||
raise Resolver404({'path' : path})
|
raise Resolver404({'path' : path})
|
||||||
|
|
||||||
def _get_urlconf_module(self):
|
@property
|
||||||
|
def urlconf_module(self):
|
||||||
try:
|
try:
|
||||||
return self._urlconf_module
|
return self._urlconf_module
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
self._urlconf_module = import_module(self.urlconf_name)
|
self._urlconf_module = import_module(self.urlconf_name)
|
||||||
return self._urlconf_module
|
return self._urlconf_module
|
||||||
urlconf_module = property(_get_urlconf_module)
|
|
||||||
|
|
||||||
def _get_url_patterns(self):
|
@property
|
||||||
|
def url_patterns(self):
|
||||||
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
|
patterns = getattr(self.urlconf_module, "urlpatterns", self.urlconf_module)
|
||||||
try:
|
try:
|
||||||
iter(patterns)
|
iter(patterns)
|
||||||
except TypeError:
|
except TypeError:
|
||||||
raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
|
raise ImproperlyConfigured("The included urlconf %s doesn't have any patterns in it" % self.urlconf_name)
|
||||||
return patterns
|
return patterns
|
||||||
url_patterns = property(_get_url_patterns)
|
|
||||||
|
|
||||||
def _resolve_special(self, view_type):
|
def _resolve_special(self, view_type):
|
||||||
callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
|
callback = getattr(self.urlconf_module, 'handler%s' % view_type, None)
|
||||||
|
@ -343,6 +377,25 @@ class RegexURLResolver(object):
|
||||||
raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
|
raise NoReverseMatch("Reverse for '%s' with arguments '%s' and keyword "
|
||||||
"arguments '%s' not found." % (lookup_view_s, args, kwargs))
|
"arguments '%s' not found." % (lookup_view_s, args, kwargs))
|
||||||
|
|
||||||
|
class LocaleRegexURLResolver(RegexURLResolver):
|
||||||
|
"""
|
||||||
|
A URL resolver that always matches the active language code as URL prefix.
|
||||||
|
|
||||||
|
Rather than taking a regex argument, we just override the ``regex``
|
||||||
|
function to always return the active language-code as regex.
|
||||||
|
"""
|
||||||
|
def __init__(self, urlconf_name, default_kwargs=None, app_name=None, namespace=None):
|
||||||
|
super(LocaleRegexURLResolver, self).__init__(
|
||||||
|
None, urlconf_name, default_kwargs, app_name, namespace)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def regex(self):
|
||||||
|
language_code = get_language()
|
||||||
|
if language_code not in self._regex_dict:
|
||||||
|
regex_compiled = re.compile('^%s/' % language_code, re.UNICODE)
|
||||||
|
self._regex_dict[language_code] = regex_compiled
|
||||||
|
return self._regex_dict[language_code]
|
||||||
|
|
||||||
def resolve(path, urlconf=None):
|
def resolve(path, urlconf=None):
|
||||||
if urlconf is None:
|
if urlconf is None:
|
||||||
urlconf = get_urlconf()
|
urlconf = get_urlconf()
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
"this is the locale selecting middleware that will look at accept headers"
|
"This is the locale selecting middleware that will look at accept headers"
|
||||||
|
|
||||||
|
from django.core.urlresolvers import get_resolver, LocaleRegexURLResolver
|
||||||
|
from django.http import HttpResponseRedirect
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
|
@ -18,8 +20,26 @@ class LocaleMiddleware(object):
|
||||||
request.LANGUAGE_CODE = translation.get_language()
|
request.LANGUAGE_CODE = translation.get_language()
|
||||||
|
|
||||||
def process_response(self, request, response):
|
def process_response(self, request, response):
|
||||||
|
language = translation.get_language()
|
||||||
|
translation.deactivate()
|
||||||
|
|
||||||
|
if (response.status_code == 404 and
|
||||||
|
not translation.get_language_from_path(request.path_info)
|
||||||
|
and self.is_language_prefix_patterns_used()):
|
||||||
|
return HttpResponseRedirect(
|
||||||
|
'/%s%s' % (language, request.get_full_path()))
|
||||||
|
|
||||||
patch_vary_headers(response, ('Accept-Language',))
|
patch_vary_headers(response, ('Accept-Language',))
|
||||||
if 'Content-Language' not in response:
|
if 'Content-Language' not in response:
|
||||||
response['Content-Language'] = translation.get_language()
|
response['Content-Language'] = language
|
||||||
translation.deactivate()
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def is_language_prefix_patterns_used(self):
|
||||||
|
"""
|
||||||
|
Returns `True` if the `LocaleRegexURLResolver` is used
|
||||||
|
at root level of the urlpatterns, else it returns `False`.
|
||||||
|
"""
|
||||||
|
for url_pattern in get_resolver(None).url_patterns:
|
||||||
|
if isinstance(url_pattern, LocaleRegexURLResolver):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
|
@ -144,6 +144,9 @@ def to_locale(language):
|
||||||
def get_language_from_request(request):
|
def get_language_from_request(request):
|
||||||
return _trans.get_language_from_request(request)
|
return _trans.get_language_from_request(request)
|
||||||
|
|
||||||
|
def get_language_from_path(path):
|
||||||
|
return _trans.get_language_from_path(path)
|
||||||
|
|
||||||
def templatize(src, origin=None):
|
def templatize(src, origin=None):
|
||||||
return _trans.templatize(src, origin)
|
return _trans.templatize(src, origin)
|
||||||
|
|
||||||
|
|
|
@ -58,3 +58,7 @@ def to_locale(language):
|
||||||
|
|
||||||
def get_language_from_request(request):
|
def get_language_from_request(request):
|
||||||
return settings.LANGUAGE_CODE
|
return settings.LANGUAGE_CODE
|
||||||
|
|
||||||
|
def get_language_from_path(request):
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
|
@ -35,6 +35,8 @@ accept_language_re = re.compile(r'''
|
||||||
(?:\s*,\s*|$) # Multiple accepts per header.
|
(?:\s*,\s*|$) # Multiple accepts per header.
|
||||||
''', re.VERBOSE)
|
''', re.VERBOSE)
|
||||||
|
|
||||||
|
language_code_prefix_re = re.compile(r'^/([\w-]+)/')
|
||||||
|
|
||||||
def to_locale(language, to_lower=False):
|
def to_locale(language, to_lower=False):
|
||||||
"""
|
"""
|
||||||
Turns a language name (en-us) into a locale name (en_US). If 'to_lower' is
|
Turns a language name (en-us) into a locale name (en_US). If 'to_lower' is
|
||||||
|
@ -336,14 +338,28 @@ def check_for_language(lang_code):
|
||||||
"""
|
"""
|
||||||
Checks whether there is a global language file for the given language
|
Checks whether there is a global language file for the given language
|
||||||
code. This is used to decide whether a user-provided language is
|
code. This is used to decide whether a user-provided language is
|
||||||
available. This is only used for language codes from either the cookies or
|
available. This is only used for language codes from either the cookies
|
||||||
session and during format localization.
|
or session and during format localization.
|
||||||
"""
|
"""
|
||||||
for path in all_locale_paths():
|
for path in all_locale_paths():
|
||||||
if gettext_module.find('django', path, [to_locale(lang_code)]) is not None:
|
if gettext_module.find('django', path, [to_locale(lang_code)]) is not None:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_language_from_path(path, supported=None):
|
||||||
|
"""
|
||||||
|
Returns the language-code if there is a valid language-code
|
||||||
|
found in the `path`.
|
||||||
|
"""
|
||||||
|
if supported is None:
|
||||||
|
from django.conf import settings
|
||||||
|
supported = dict(settings.LANGUAGES)
|
||||||
|
regex_match = language_code_prefix_re.match(path)
|
||||||
|
if regex_match:
|
||||||
|
lang_code = regex_match.group(1)
|
||||||
|
if lang_code in supported and check_for_language(lang_code):
|
||||||
|
return lang_code
|
||||||
|
|
||||||
def get_language_from_request(request):
|
def get_language_from_request(request):
|
||||||
"""
|
"""
|
||||||
Analyzes the request to find what language the user wants the system to
|
Analyzes the request to find what language the user wants the system to
|
||||||
|
@ -355,6 +371,10 @@ def get_language_from_request(request):
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
supported = dict(settings.LANGUAGES)
|
supported = dict(settings.LANGUAGES)
|
||||||
|
|
||||||
|
lang_code = get_language_from_path(request.path_info, supported)
|
||||||
|
if lang_code is not None:
|
||||||
|
return lang_code
|
||||||
|
|
||||||
if hasattr(request, 'session'):
|
if hasattr(request, 'session'):
|
||||||
lang_code = request.session.get('django_language', None)
|
lang_code = request.session.get('django_language', None)
|
||||||
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
||||||
|
|
|
@ -167,6 +167,16 @@ a :class:`~django.forms.fields.GenericIPAddressField` form field and
|
||||||
the validators :data:`~django.core.validators.validate_ipv46_address` and
|
the validators :data:`~django.core.validators.validate_ipv46_address` and
|
||||||
:data:`~django.core.validators.validate_ipv6_address`
|
:data:`~django.core.validators.validate_ipv6_address`
|
||||||
|
|
||||||
|
Translating URL patterns
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
Django 1.4 gained the ability to look for a language prefix in the URL pattern
|
||||||
|
when using the new :func:`django.conf.urls.i18n.i18n_patterns` helper function.
|
||||||
|
Additionally, it's now possible to define translatable URL patterns using
|
||||||
|
:func:`~django.utils.translation.ugettext_lazy`. See
|
||||||
|
:ref:`url-internationalization` for more information about the language prefix
|
||||||
|
and how to internationalize URL patterns.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -59,7 +59,9 @@ matters, you should follow these guidelines:
|
||||||
|
|
||||||
* Make sure it's one of the first middlewares installed.
|
* Make sure it's one of the first middlewares installed.
|
||||||
* It should come after ``SessionMiddleware``, because ``LocaleMiddleware``
|
* It should come after ``SessionMiddleware``, because ``LocaleMiddleware``
|
||||||
makes use of session data.
|
makes use of session data. And it should come before ``CommonMiddleware``
|
||||||
|
because ``CommonMiddleware`` needs an activated language in order
|
||||||
|
to resolve the requested URL.
|
||||||
* If you use ``CacheMiddleware``, put ``LocaleMiddleware`` after it.
|
* If you use ``CacheMiddleware``, put ``LocaleMiddleware`` after it.
|
||||||
|
|
||||||
For example, your :setting:`MIDDLEWARE_CLASSES` might look like this::
|
For example, your :setting:`MIDDLEWARE_CLASSES` might look like this::
|
||||||
|
@ -76,8 +78,15 @@ For example, your :setting:`MIDDLEWARE_CLASSES` might look like this::
|
||||||
``LocaleMiddleware`` tries to determine the user's language preference by
|
``LocaleMiddleware`` tries to determine the user's language preference by
|
||||||
following this algorithm:
|
following this algorithm:
|
||||||
|
|
||||||
* First, it looks for a ``django_language`` key in the current user's
|
.. versionchanged:: 1.4
|
||||||
session.
|
|
||||||
|
* First, it looks for the language prefix in the requested URL. This is
|
||||||
|
only performed when you are using the ``i18n_patterns`` function in your
|
||||||
|
root URLconf. See :ref:`url-internationalization` for more information
|
||||||
|
about the language prefix and how to internationalize URL patterns.
|
||||||
|
|
||||||
|
* Failing that, it looks for a ``django_language`` key in the current
|
||||||
|
user's session.
|
||||||
|
|
||||||
* Failing that, it looks for a cookie.
|
* Failing that, it looks for a cookie.
|
||||||
|
|
||||||
|
|
|
@ -753,6 +753,138 @@ This isn't as fast as string interpolation in Python, so keep it to those
|
||||||
cases where you really need it (for example, in conjunction with ``ngettext``
|
cases where you really need it (for example, in conjunction with ``ngettext``
|
||||||
to produce proper pluralizations).
|
to produce proper pluralizations).
|
||||||
|
|
||||||
|
.. _url-internationalization:
|
||||||
|
|
||||||
|
Specifying translation strings: In URL patterns
|
||||||
|
===============================================
|
||||||
|
|
||||||
|
.. versionadded:: 1.4
|
||||||
|
|
||||||
|
.. module:: django.conf.urls.i18n
|
||||||
|
|
||||||
|
Django provides two mechanisms to internationalize URL patterns:
|
||||||
|
|
||||||
|
* Adding the language prefix to the root of the URL patterns to make it
|
||||||
|
possible for :class:`~django.middleware.locale.LocaleMiddleware` to detect
|
||||||
|
the language to activate from the requested URL.
|
||||||
|
|
||||||
|
* Making URL patterns themselves translatable via the
|
||||||
|
:func:`django.utils.translation.ugettext_lazy()` function.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Using either one of these features requires that an active language be set
|
||||||
|
for each request; in other words, you need to have
|
||||||
|
:class:`django.middleware.locale.LocaleMiddleware` in your
|
||||||
|
:setting:`MIDDLEWARE_CLASSES` setting.
|
||||||
|
|
||||||
|
Language prefix in URL patterns
|
||||||
|
-------------------------------
|
||||||
|
|
||||||
|
.. function:: i18n_patterns(prefix, pattern_description, ...)
|
||||||
|
|
||||||
|
This function can be used in your root URLconf as a replacement for the normal
|
||||||
|
:func:`django.conf.urls.defaults.patterns` function. Django will automatically
|
||||||
|
prepend the current active language code to all url patterns defined within
|
||||||
|
:func:`~django.conf.urls.i18n.i18n_patterns`. Example URL patterns::
|
||||||
|
|
||||||
|
from django.conf.urls.defaults import patterns, include, url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
|
||||||
|
urlpatterns = patterns(''
|
||||||
|
url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
|
||||||
|
)
|
||||||
|
|
||||||
|
news_patterns = patterns(''
|
||||||
|
url(r'^$', 'news.views.index', name='index'),
|
||||||
|
url(r'^category/(?P<slug>[\w-]+)/$', 'news.views.category', name='category'),
|
||||||
|
url(r'^(?P<slug>[\w-]+)/$', 'news.views.details', name='detail'),
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns += i18n_patterns('',
|
||||||
|
url(r'^about/$', 'about.view', name='about'),
|
||||||
|
url(r'^news/$', include(news_patterns, namespace='news')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
After defining these URL patterns, Django will automatically add the
|
||||||
|
language prefix to the URL patterns that were added by the ``i18n_patterns``
|
||||||
|
function. Example::
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import activate
|
||||||
|
|
||||||
|
>>> activate('en')
|
||||||
|
>>> reverse('sitemap_xml')
|
||||||
|
'/sitemap.xml'
|
||||||
|
>>> reverse('news:index')
|
||||||
|
'/en/news/'
|
||||||
|
|
||||||
|
>>> activate('nl')
|
||||||
|
>>> reverse('news:detail', kwargs={'slug': 'news-slug'})
|
||||||
|
'/nl/news/news-slug/'
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
:func:`~django.conf.urls.i18n.i18n_patterns` is only allowed in your root
|
||||||
|
URLconf. Using it within an included URLconf will throw an
|
||||||
|
:exc:`ImproperlyConfigured` exception.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Ensure that you don't have non-prefixed URL patterns that might collide
|
||||||
|
with an automatically-added language prefix.
|
||||||
|
|
||||||
|
|
||||||
|
Translating URL patterns
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
URL patterns can also be marked translatable using the
|
||||||
|
:func:`~django.utils.translation.ugettext_lazy` function. Example::
|
||||||
|
|
||||||
|
from django.conf.urls.defaults import patterns, include, url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
urlpatterns = patterns(''
|
||||||
|
url(r'^sitemap\.xml$', 'sitemap.view', name='sitemap_xml'),
|
||||||
|
)
|
||||||
|
|
||||||
|
news_patterns = patterns(''
|
||||||
|
url(r'^$', 'news.views.index', name='index'),
|
||||||
|
url(_(r'^category/(?P<slug>[\w-]+)/$'), 'news.views.category', name='category'),
|
||||||
|
url(r'^(?P<slug>[\w-]+)/$', 'news.views.details', name='detail'),
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns += i18n_patterns('',
|
||||||
|
url(_(r'^about/$'), 'about.view', name='about'),
|
||||||
|
url(_(r'^news/$'), include(news_patterns, namespace='news')),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
After you've created the translations (see :doc:`localization` for more
|
||||||
|
information), the :func:`~django.core.urlresolvers.reverse` function will
|
||||||
|
return the URL in the active language. Example::
|
||||||
|
|
||||||
|
from django.core.urlresolvers import reverse
|
||||||
|
from django.utils.translation import activate
|
||||||
|
|
||||||
|
>>> activate('en')
|
||||||
|
>>> reverse('news:category', kwargs={'slug': 'recent'})
|
||||||
|
'/en/news/category/recent/'
|
||||||
|
|
||||||
|
>>> activate('nl')
|
||||||
|
>>> reverse('news:category', kwargs={'slug': 'recent'})
|
||||||
|
'/nl/nieuws/categorie/recent/'
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
In most cases, it's best to use translated URLs only within a
|
||||||
|
language-code-prefixed block of patterns (using
|
||||||
|
:func:`~django.conf.urls.i18n.i18n_patterns`), to avoid the possibility
|
||||||
|
that a carelessly translated URL causes a collision with a non-translated
|
||||||
|
URL pattern.
|
||||||
|
|
||||||
.. _set_language-redirect-view:
|
.. _set_language-redirect-view:
|
||||||
|
|
||||||
The ``set_language`` redirect view
|
The ``set_language`` redirect view
|
||||||
|
|
Binary file not shown.
|
@ -0,0 +1,37 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2011-06-15 11:33+0200\n"
|
||||||
|
"PO-Revision-Date: 2011-06-14 16:16+0100\n"
|
||||||
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
|
||||||
|
#: urls/default.py:11
|
||||||
|
msgid "^translated/$"
|
||||||
|
msgstr "^translated/$"
|
||||||
|
|
||||||
|
#: urls/default.py:12
|
||||||
|
msgid "^translated/(?P<slug>[\\w-]+)/$"
|
||||||
|
msgstr "^translated/(?P<slug>[\\w-]+)/$"
|
||||||
|
|
||||||
|
#: urls/default.py:17
|
||||||
|
msgid "^users/$"
|
||||||
|
msgstr "^users/$"
|
||||||
|
|
||||||
|
#: urls/default.py:18 urls/wrong.py:7
|
||||||
|
msgid "^account/"
|
||||||
|
msgstr "^account/"
|
||||||
|
|
||||||
|
#: urls/namespace.py:9 urls/wrong_namespace.py:10
|
||||||
|
msgid "^register/$"
|
||||||
|
msgstr "^register/$"
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2011-06-15 11:33+0200\n"
|
||||||
|
"PO-Revision-Date: 2011-06-14 16:16+0100\n"
|
||||||
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n != 1)\n"
|
||||||
|
|
||||||
|
#: urls/default.py:11
|
||||||
|
msgid "^translated/$"
|
||||||
|
msgstr "^vertaald/$"
|
||||||
|
|
||||||
|
#: urls/default.py:12
|
||||||
|
msgid "^translated/(?P<slug>[\\w-]+)/$"
|
||||||
|
msgstr "^vertaald/(?P<slug>[\\w-]+)/$"
|
||||||
|
|
||||||
|
#: urls/default.py:17
|
||||||
|
msgid "^users/$"
|
||||||
|
msgstr "^gebruikers/$"
|
||||||
|
|
||||||
|
#: urls/default.py:18 urls/wrong.py:7
|
||||||
|
msgid "^account/"
|
||||||
|
msgstr "^profiel/"
|
||||||
|
|
||||||
|
#: urls/namespace.py:9 urls/wrong_namespace.py:10
|
||||||
|
msgid "^register/$"
|
||||||
|
msgstr "^registeren/$"
|
Binary file not shown.
|
@ -0,0 +1,38 @@
|
||||||
|
# SOME DESCRIPTIVE TITLE.
|
||||||
|
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||||
|
# This file is distributed under the same license as the PACKAGE package.
|
||||||
|
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||||
|
#
|
||||||
|
msgid ""
|
||||||
|
msgstr ""
|
||||||
|
"Project-Id-Version: PACKAGE VERSION\n"
|
||||||
|
"Report-Msgid-Bugs-To: \n"
|
||||||
|
"POT-Creation-Date: 2011-06-15 11:34+0200\n"
|
||||||
|
"PO-Revision-Date: 2011-06-14 16:17+0100\n"
|
||||||
|
"Last-Translator: Jannis Leidel <jannis@leidel.info>\n"
|
||||||
|
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||||
|
"MIME-Version: 1.0\n"
|
||||||
|
"Content-Type: text/plain; charset=UTF-8\n"
|
||||||
|
"Content-Transfer-Encoding: 8bit\n"
|
||||||
|
"Language: \n"
|
||||||
|
"Plural-Forms: nplurals=2; plural=(n > 1)\n"
|
||||||
|
|
||||||
|
#: urls/default.py:11
|
||||||
|
msgid "^translated/$"
|
||||||
|
msgstr "^traduzidos/$"
|
||||||
|
|
||||||
|
#: urls/default.py:12
|
||||||
|
msgid "^translated/(?P<slug>[\\w-]+)/$"
|
||||||
|
msgstr "^traduzidos/(?P<slug>[\\w-]+)/$"
|
||||||
|
|
||||||
|
#: urls/default.py:17
|
||||||
|
msgid "^users/$"
|
||||||
|
msgstr "^usuarios/$"
|
||||||
|
|
||||||
|
#: urls/default.py:18 urls/wrong.py:7
|
||||||
|
msgid "^account/"
|
||||||
|
msgstr "^conta/"
|
||||||
|
|
||||||
|
#: urls/namespace.py:9 urls/wrong_namespace.py:10
|
||||||
|
msgid "^register/$"
|
||||||
|
msgstr "^registre-se/$"
|
|
@ -0,0 +1,241 @@
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.core.urlresolvers import reverse, clear_url_caches
|
||||||
|
from django.test import TestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
from django.utils import translation
|
||||||
|
|
||||||
|
|
||||||
|
class URLTestCaseBase(TestCase):
|
||||||
|
"""
|
||||||
|
TestCase base-class for the URL tests.
|
||||||
|
"""
|
||||||
|
urls = 'regressiontests.i18n.patterns.urls.default'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
# Make sure the cache is empty before we are doing our tests.
|
||||||
|
clear_url_caches()
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
# Make sure we will leave an empty cache for other testcases.
|
||||||
|
clear_url_caches()
|
||||||
|
|
||||||
|
URLTestCaseBase = override_settings(
|
||||||
|
USE_I18N=True,
|
||||||
|
LOCALE_PATHS=(
|
||||||
|
os.path.join(os.path.dirname(__file__), 'locale'),
|
||||||
|
),
|
||||||
|
TEMPLATE_DIRS=(
|
||||||
|
os.path.join(os.path.dirname(__file__), 'templates'),
|
||||||
|
),
|
||||||
|
LANGUAGE_CODE='en',
|
||||||
|
LANGUAGES=(
|
||||||
|
('nl', 'Dutch'),
|
||||||
|
('en', 'English'),
|
||||||
|
('pt-br', 'Brazilian Portuguese'),
|
||||||
|
),
|
||||||
|
MIDDLEWARE_CLASSES=(
|
||||||
|
'django.middleware.locale.LocaleMiddleware',
|
||||||
|
'django.middleware.common.CommonMiddleware',
|
||||||
|
),
|
||||||
|
)(URLTestCaseBase)
|
||||||
|
|
||||||
|
|
||||||
|
class URLPrefixTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests if the `i18n_patterns` is adding the prefix correctly.
|
||||||
|
"""
|
||||||
|
def test_not_prefixed(self):
|
||||||
|
with translation.override('en'):
|
||||||
|
self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
|
||||||
|
with translation.override('nl'):
|
||||||
|
self.assertEqual(reverse('not-prefixed'), '/not-prefixed/')
|
||||||
|
|
||||||
|
def test_prefixed(self):
|
||||||
|
with translation.override('en'):
|
||||||
|
self.assertEqual(reverse('prefixed'), '/en/prefixed/')
|
||||||
|
with translation.override('nl'):
|
||||||
|
self.assertEqual(reverse('prefixed'), '/nl/prefixed/')
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF='regressiontests.i18n.patterns.urls.wrong')
|
||||||
|
def test_invalid_prefix_use(self):
|
||||||
|
self.assertRaises(ImproperlyConfigured, lambda: reverse('account:register'))
|
||||||
|
|
||||||
|
|
||||||
|
class URLDisabledTests(URLTestCaseBase):
|
||||||
|
urls = 'regressiontests.i18n.patterns.urls.disabled'
|
||||||
|
|
||||||
|
@override_settings(USE_I18N=False)
|
||||||
|
def test_prefixed_i18n_disabled(self):
|
||||||
|
with translation.override('en'):
|
||||||
|
self.assertEqual(reverse('prefixed'), '/prefixed/')
|
||||||
|
with translation.override('nl'):
|
||||||
|
self.assertEqual(reverse('prefixed'), '/prefixed/')
|
||||||
|
|
||||||
|
|
||||||
|
class URLTranslationTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests if the pattern-strings are translated correctly (within the
|
||||||
|
`i18n_patterns` and the normal `patterns` function).
|
||||||
|
"""
|
||||||
|
def test_no_prefix_translated(self):
|
||||||
|
with translation.override('en'):
|
||||||
|
self.assertEqual(reverse('no-prefix-translated'), '/translated/')
|
||||||
|
self.assertEqual(reverse('no-prefix-translated-slug', kwargs={'slug': 'yeah'}), '/translated/yeah/')
|
||||||
|
|
||||||
|
with translation.override('nl'):
|
||||||
|
self.assertEqual(reverse('no-prefix-translated'), '/vertaald/')
|
||||||
|
self.assertEqual(reverse('no-prefix-translated-slug', kwargs={'slug': 'yeah'}), '/vertaald/yeah/')
|
||||||
|
|
||||||
|
with translation.override('pt-br'):
|
||||||
|
self.assertEqual(reverse('no-prefix-translated'), '/traduzidos/')
|
||||||
|
self.assertEqual(reverse('no-prefix-translated-slug', kwargs={'slug': 'yeah'}), '/traduzidos/yeah/')
|
||||||
|
|
||||||
|
def test_users_url(self):
|
||||||
|
with translation.override('en'):
|
||||||
|
self.assertEqual(reverse('users'), '/en/users/')
|
||||||
|
|
||||||
|
with translation.override('nl'):
|
||||||
|
self.assertEqual(reverse('users'), '/nl/gebruikers/')
|
||||||
|
|
||||||
|
with translation.override('pt-br'):
|
||||||
|
self.assertEqual(reverse('users'), '/pt-br/usuarios/')
|
||||||
|
|
||||||
|
|
||||||
|
class URLNamespaceTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests if the translations are still working within namespaces.
|
||||||
|
"""
|
||||||
|
def test_account_register(self):
|
||||||
|
with translation.override('en'):
|
||||||
|
self.assertEqual(reverse('account:register'), '/en/account/register/')
|
||||||
|
|
||||||
|
with translation.override('nl'):
|
||||||
|
self.assertEqual(reverse('account:register'), '/nl/profiel/registeren/')
|
||||||
|
|
||||||
|
|
||||||
|
class URLRedirectTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests if the user gets redirected to the right URL when there is no
|
||||||
|
language-prefix in the request URL.
|
||||||
|
"""
|
||||||
|
def test_no_prefix_response(self):
|
||||||
|
response = self.client.get('/not-prefixed/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_en_redirect(self):
|
||||||
|
response = self.client.get('/account/register/', HTTP_ACCEPT_LANGUAGE='en')
|
||||||
|
self.assertRedirects(response, 'http://testserver/en/account/register/')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_en_redirect_wrong_url(self):
|
||||||
|
response = self.client.get('/profiel/registeren/', HTTP_ACCEPT_LANGUAGE='en')
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/en/profiel/registeren/')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_nl_redirect(self):
|
||||||
|
response = self.client.get('/profiel/registeren/', HTTP_ACCEPT_LANGUAGE='nl')
|
||||||
|
self.assertRedirects(response, 'http://testserver/nl/profiel/registeren/')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_nl_redirect_wrong_url(self):
|
||||||
|
response = self.client.get('/account/register/', HTTP_ACCEPT_LANGUAGE='nl')
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/nl/account/register/')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_pt_br_redirect(self):
|
||||||
|
response = self.client.get('/conta/registre-se/', HTTP_ACCEPT_LANGUAGE='pt-br')
|
||||||
|
self.assertRedirects(response, 'http://testserver/pt-br/conta/registre-se/')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
|
class URLRedirectWithoutTrailingSlashTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests the redirect when the requested URL doesn't end with a slash
|
||||||
|
(`settings.APPEND_SLASH=True`).
|
||||||
|
"""
|
||||||
|
def test_not_prefixed_redirect(self):
|
||||||
|
response = self.client.get('/not-prefixed', HTTP_ACCEPT_LANGUAGE='en')
|
||||||
|
self.assertEqual(response.status_code, 301)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/not-prefixed/')
|
||||||
|
|
||||||
|
def test_en_redirect(self):
|
||||||
|
response = self.client.get('/account/register', HTTP_ACCEPT_LANGUAGE='en')
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/en/account/register')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 301)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/en/account/register/')
|
||||||
|
|
||||||
|
|
||||||
|
class URLRedirectWithoutTrailingSlashSettingTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests the redirect when the requested URL doesn't end with a slash
|
||||||
|
(`settings.APPEND_SLASH=False`).
|
||||||
|
"""
|
||||||
|
@override_settings(APPEND_SLASH=False)
|
||||||
|
def test_not_prefixed_redirect(self):
|
||||||
|
response = self.client.get('/not-prefixed', HTTP_ACCEPT_LANGUAGE='en')
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/en/not-prefixed')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
@override_settings(APPEND_SLASH=False)
|
||||||
|
def test_en_redirect(self):
|
||||||
|
response = self.client.get('/account/register', HTTP_ACCEPT_LANGUAGE='en')
|
||||||
|
self.assertEqual(response.status_code, 302)
|
||||||
|
self.assertEqual(response['location'], 'http://testserver/en/account/register')
|
||||||
|
|
||||||
|
response = self.client.get(response['location'])
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
|
||||||
|
class URLResponseTests(URLTestCaseBase):
|
||||||
|
"""
|
||||||
|
Tests if the response has the right language-code.
|
||||||
|
"""
|
||||||
|
def test_not_prefixed_with_prefix(self):
|
||||||
|
response = self.client.get('/en/not-prefixed/')
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_en_url(self):
|
||||||
|
response = self.client.get('/en/account/register/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response['content-language'], 'en')
|
||||||
|
self.assertEqual(response.context['LANGUAGE_CODE'], 'en')
|
||||||
|
|
||||||
|
def test_nl_url(self):
|
||||||
|
response = self.client.get('/nl/profiel/registeren/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response['content-language'], 'nl')
|
||||||
|
self.assertEqual(response.context['LANGUAGE_CODE'], 'nl')
|
||||||
|
|
||||||
|
def test_wrong_en_prefix(self):
|
||||||
|
response = self.client.get('/en/profiel/registeren/')
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_wrong_nl_prefix(self):
|
||||||
|
response = self.client.get('/nl/account/register/')
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_pt_br_url(self):
|
||||||
|
response = self.client.get('/pt-br/conta/registre-se/')
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
self.assertEqual(response['content-language'], 'pt-br')
|
||||||
|
self.assertEqual(response.context['LANGUAGE_CODE'], 'pt-br')
|
|
@ -0,0 +1,19 @@
|
||||||
|
from django.conf.urls.defaults import patterns, include, url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
view = TemplateView.as_view(template_name='dummy.html')
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(r'^not-prefixed/$', view, name='not-prefixed'),
|
||||||
|
url(_(r'^translated/$'), view, name='no-prefix-translated'),
|
||||||
|
url(_(r'^translated/(?P<slug>[\w-]+)/$'), view, name='no-prefix-translated-slug'),
|
||||||
|
)
|
||||||
|
|
||||||
|
urlpatterns += i18n_patterns('',
|
||||||
|
url(r'^prefixed/$', view, name='prefixed'),
|
||||||
|
url(_(r'^users/$'), view, name='users'),
|
||||||
|
url(_(r'^account/'), include('regressiontests.i18n.patterns.urls.namespace', namespace='account')),
|
||||||
|
)
|
|
@ -0,0 +1,9 @@
|
||||||
|
from django.conf.urls.defaults import url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
view = TemplateView.as_view(template_name='dummy.html')
|
||||||
|
|
||||||
|
urlpatterns = i18n_patterns('',
|
||||||
|
url(r'^prefixed/$', view, name='prefixed'),
|
||||||
|
)
|
|
@ -0,0 +1,10 @@
|
||||||
|
from django.conf.urls.defaults import patterns, include, url
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
view = TemplateView.as_view(template_name='dummy.html')
|
||||||
|
|
||||||
|
urlpatterns = patterns('',
|
||||||
|
url(_(r'^register/$'), view, name='register'),
|
||||||
|
)
|
|
@ -0,0 +1,8 @@
|
||||||
|
from django.conf.urls.defaults import patterns, include, url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
|
||||||
|
|
||||||
|
urlpatterns = i18n_patterns('',
|
||||||
|
url(_(r'^account/'), include('regressiontests.i18n.patterns.urls.wrong_namespace', namespace='account')),
|
||||||
|
)
|
|
@ -0,0 +1,11 @@
|
||||||
|
from django.conf.urls.defaults import include, url
|
||||||
|
from django.conf.urls.i18n import i18n_patterns
|
||||||
|
from django.utils.translation import ugettext_lazy as _
|
||||||
|
from django.views.generic import TemplateView
|
||||||
|
|
||||||
|
|
||||||
|
view = TemplateView.as_view(template_name='dummy.html')
|
||||||
|
|
||||||
|
urlpatterns = i18n_patterns('',
|
||||||
|
url(_(r'^register/$'), view, name='register'),
|
||||||
|
)
|
|
@ -3,13 +3,12 @@ from __future__ import with_statement
|
||||||
import datetime
|
import datetime
|
||||||
import decimal
|
import decimal
|
||||||
import os
|
import os
|
||||||
import sys
|
|
||||||
import pickle
|
import pickle
|
||||||
from threading import local
|
from threading import local
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Template, Context
|
from django.template import Template, Context
|
||||||
from django.test import TestCase
|
from django.test import TestCase, RequestFactory
|
||||||
from django.utils.formats import (get_format, date_format, time_format,
|
from django.utils.formats import (get_format, date_format, time_format,
|
||||||
localize, localize_input, iter_format_modules, get_format_modules)
|
localize, localize_input, iter_format_modules, get_format_modules)
|
||||||
from django.utils.importlib import import_module
|
from django.utils.importlib import import_module
|
||||||
|
@ -18,14 +17,14 @@ from django.utils.safestring import mark_safe, SafeString, SafeUnicode
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
from django.utils.translation import (ugettext, ugettext_lazy, activate,
|
from django.utils.translation import (ugettext, ugettext_lazy, activate,
|
||||||
deactivate, gettext_lazy, pgettext, npgettext, to_locale,
|
deactivate, gettext_lazy, pgettext, npgettext, to_locale,
|
||||||
get_language_info, get_language)
|
get_language_info, get_language, get_language_from_request)
|
||||||
|
|
||||||
|
|
||||||
from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
|
from forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
|
||||||
from models import Company, TestModel
|
from models import Company, TestModel
|
||||||
|
|
||||||
from commands.tests import *
|
from commands.tests import *
|
||||||
|
from patterns.tests import *
|
||||||
from test_warnings import DeprecationWarningTests
|
from test_warnings import DeprecationWarningTests
|
||||||
|
|
||||||
class TranslationTests(TestCase):
|
class TranslationTests(TestCase):
|
||||||
|
@ -494,6 +493,9 @@ class FormattingTests(TestCase):
|
||||||
|
|
||||||
class MiscTests(TestCase):
|
class MiscTests(TestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.rf = RequestFactory()
|
||||||
|
|
||||||
def test_parse_spec_http_header(self):
|
def test_parse_spec_http_header(self):
|
||||||
"""
|
"""
|
||||||
Testing HTTP header parsing. First, we test that we can parse the
|
Testing HTTP header parsing. First, we test that we can parse the
|
||||||
|
@ -534,10 +536,8 @@ class MiscTests(TestCase):
|
||||||
"""
|
"""
|
||||||
Now test that we parse a literal HTTP header correctly.
|
Now test that we parse a literal HTTP header correctly.
|
||||||
"""
|
"""
|
||||||
from django.utils.translation.trans_real import get_language_from_request
|
|
||||||
g = get_language_from_request
|
g = get_language_from_request
|
||||||
from django.http import HttpRequest
|
r = self.rf.get('/')
|
||||||
r = HttpRequest
|
|
||||||
r.COOKIES = {}
|
r.COOKIES = {}
|
||||||
r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'}
|
r.META = {'HTTP_ACCEPT_LANGUAGE': 'pt-br'}
|
||||||
self.assertEqual('pt-br', g(r))
|
self.assertEqual('pt-br', g(r))
|
||||||
|
@ -569,10 +569,8 @@ class MiscTests(TestCase):
|
||||||
"""
|
"""
|
||||||
Now test that we parse language preferences stored in a cookie correctly.
|
Now test that we parse language preferences stored in a cookie correctly.
|
||||||
"""
|
"""
|
||||||
from django.utils.translation.trans_real import get_language_from_request
|
|
||||||
g = get_language_from_request
|
g = get_language_from_request
|
||||||
from django.http import HttpRequest
|
r = self.rf.get('/')
|
||||||
r = HttpRequest
|
|
||||||
r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'pt-br'}
|
r.COOKIES = {settings.LANGUAGE_COOKIE_NAME: 'pt-br'}
|
||||||
r.META = {}
|
r.META = {}
|
||||||
self.assertEqual('pt-br', g(r))
|
self.assertEqual('pt-br', g(r))
|
||||||
|
@ -827,4 +825,3 @@ class MultipleLocaleActivationTests(TestCase):
|
||||||
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
|
t = Template("{% load i18n %}{% blocktrans %}No{% endblocktrans %}")
|
||||||
with translation.override('nl'):
|
with translation.override('nl'):
|
||||||
self.assertEqual(t.render(Context({})), 'Nee')
|
self.assertEqual(t.render(Context({})), 'Nee')
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue