Fixed #26295 -- Allowed using i18n_patterns() in any root URLconf.

Thanks Tim for the review.
This commit is contained in:
Simon Charette 2015-12-14 12:36:09 -05:00
parent 2404d209a5
commit d0451e4cad
4 changed files with 34 additions and 12 deletions

View File

@ -5,9 +5,8 @@ from django.http import HttpResponseRedirect
from django.urls import ( from django.urls import (
LocaleRegexURLResolver, get_resolver, get_script_prefix, is_valid_path, LocaleRegexURLResolver, get_resolver, get_script_prefix, is_valid_path,
) )
from django.utils import translation from django.utils import lru_cache, translation
from django.utils.cache import patch_vary_headers from django.utils.cache import patch_vary_headers
from django.utils.functional import cached_property
class LocaleMiddleware(object): class LocaleMiddleware(object):
@ -21,17 +20,19 @@ class LocaleMiddleware(object):
response_redirect_class = HttpResponseRedirect response_redirect_class = HttpResponseRedirect
def process_request(self, request): def process_request(self, request):
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
language = translation.get_language_from_request( language = translation.get_language_from_request(
request, check_path=self.is_language_prefix_patterns_used) request, check_path=self.is_language_prefix_patterns_used(urlconf)
)
translation.activate(language) translation.activate(language)
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() language = translation.get_language()
language_from_path = translation.get_language_from_path(request.path_info) language_from_path = translation.get_language_from_path(request.path_info)
urlconf = getattr(request, 'urlconf', settings.ROOT_URLCONF)
if (response.status_code == 404 and not language_from_path if (response.status_code == 404 and not language_from_path
and self.is_language_prefix_patterns_used): and self.is_language_prefix_patterns_used(urlconf)):
urlconf = getattr(request, 'urlconf', None)
language_path = '/%s%s' % (language, request.path_info) language_path = '/%s%s' % (language, request.path_info)
path_valid = is_valid_path(language_path, urlconf) path_valid = is_valid_path(language_path, urlconf)
path_needs_slash = ( path_needs_slash = (
@ -52,20 +53,20 @@ class LocaleMiddleware(object):
) )
return self.response_redirect_class(language_url) return self.response_redirect_class(language_url)
if not (self.is_language_prefix_patterns_used if not (self.is_language_prefix_patterns_used(urlconf)
and language_from_path): and language_from_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'] = language response['Content-Language'] = language
return response return response
@cached_property @lru_cache.lru_cache(maxsize=None)
def is_language_prefix_patterns_used(self): def is_language_prefix_patterns_used(self, urlconf):
""" """
Returns `True` if the `LocaleRegexURLResolver` is used Returns `True` if the `LocaleRegexURLResolver` is used
at root level of the urlpatterns, else it returns `False`. at root level of the urlpatterns, else it returns `False`.
""" """
for url_pattern in get_resolver(None).url_patterns: for url_pattern in get_resolver(urlconf).url_patterns:
if isinstance(url_pattern, LocaleRegexURLResolver): if isinstance(url_pattern, LocaleRegexURLResolver):
return True return True
return False return False

View File

@ -244,7 +244,9 @@ Generic Views
Internationalization Internationalization
~~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~~
* ... * The :func:`~django.conf.urls.i18n.i18n_patterns` helper function can now be
used in a root URLConf specified using :attr:`request.urlconf
<django.http.HttpRequest.urlconf>`.
Management Commands Management Commands
~~~~~~~~~~~~~~~~~~~ ~~~~~~~~~~~~~~~~~~~

View File

@ -1328,7 +1328,7 @@ Language prefix in URL patterns
.. function:: i18n_patterns(*pattern_list) .. function:: i18n_patterns(*pattern_list)
This function can be used in your root URLconf and Django will automatically This function can be used in a root URLconf and Django will automatically
prepend the current active language code to all url patterns defined within prepend the current active language code to all url patterns defined within
:func:`~django.conf.urls.i18n.i18n_patterns`. Example URL patterns:: :func:`~django.conf.urls.i18n.i18n_patterns`. Example URL patterns::
@ -1373,10 +1373,16 @@ function. Example::
.. warning:: .. warning::
:func:`~django.conf.urls.i18n.i18n_patterns` is only allowed in your root :func:`~django.conf.urls.i18n.i18n_patterns` is only allowed in a root
URLconf. Using it within an included URLconf will throw an URLconf. Using it within an included URLconf will throw an
:exc:`~django.core.exceptions.ImproperlyConfigured` exception. :exc:`~django.core.exceptions.ImproperlyConfigured` exception.
.. versionchanged:: 1.10
In older version, using ``i18n_patterns`` in a root URLconf different from
:setting:`ROOT_URLCONF` by setting :attr:`request.urlconf
<django.http.HttpRequest.urlconf>` wasn't supported.
.. warning:: .. warning::
Ensure that you don't have non-prefixed URL patterns that might collide Ensure that you don't have non-prefixed URL patterns that might collide

View File

@ -7,6 +7,7 @@ from django.http import HttpResponsePermanentRedirect
from django.middleware.locale import LocaleMiddleware from django.middleware.locale import LocaleMiddleware
from django.template import Context, Template from django.template import Context, Template
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, override_settings
from django.test.client import RequestFactory
from django.test.utils import override_script_prefix from django.test.utils import override_script_prefix
from django.urls import clear_url_caches, reverse, translate_url from django.urls import clear_url_caches, reverse, translate_url
from django.utils import translation from django.utils import translation
@ -92,6 +93,18 @@ class URLDisabledTests(URLTestCaseBase):
self.assertEqual(reverse('prefixed'), '/prefixed/') self.assertEqual(reverse('prefixed'), '/prefixed/')
class RequestURLConfTests(SimpleTestCase):
@override_settings(ROOT_URLCONF='i18n.patterns.urls.path_unused')
def test_request_urlconf_considered(self):
request = RequestFactory().get('/nl/')
request.urlconf = 'i18n.patterns.urls.default'
middleware = LocaleMiddleware()
with translation.override('nl'):
middleware.process_request(request)
self.assertEqual(request.LANGUAGE_CODE, 'nl')
@override_settings(ROOT_URLCONF='i18n.patterns.urls.path_unused') @override_settings(ROOT_URLCONF='i18n.patterns.urls.path_unused')
class PathUnusedTests(URLTestCaseBase): class PathUnusedTests(URLTestCaseBase):
""" """