From 0e444e84f87d174713a2aef0c4f9704ce2865586 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 28 Apr 2023 08:05:43 +0200 Subject: Fixed #34515 -- Made LocaleMiddleware prefer language from paths when i18n patterns are used. Regression in 94e7f471c4edef845a4fe5e3160132997b4cca81. This reverts commit 94e7f471c4edef845a4fe5e3160132997b4cca81 (refs #34069) and partly reverts commit 3b4728310a7a64f8fcc548163b0aa5f98a5c78f5. Thanks Anthony Baillard for the report. Co-Authored-By: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com> --- django/middleware/locale.py | 33 ++++++++++++--------------------- django/urls/resolvers.py | 5 ++--- django/utils/translation/__init__.py | 1 - django/utils/translation/trans_null.py | 2 +- django/utils/translation/trans_real.py | 6 +++++- docs/releases/4.2.1.txt | 5 +++++ docs/releases/4.2.txt | 4 ---- tests/i18n/patterns/tests.py | 21 +++++++++++++++++++++ tests/i18n/tests.py | 23 ++++++----------------- tests/i18n/urls_default_unprefixed.py | 1 + 10 files changed, 53 insertions(+), 48 deletions(-) diff --git a/django/middleware/locale.py b/django/middleware/locale.py index bc1d862574..71db230da2 100644 --- a/django/middleware/locale.py +++ b/django/middleware/locale.py @@ -16,37 +16,28 @@ class LocaleMiddleware(MiddlewareMixin): response_redirect_class = HttpResponseRedirect - def get_fallback_language(self, request): - """ - Return the fallback language for the current request based on the - settings. If LANGUAGE_CODE is a variant not included in the supported - languages, get_fallback_language() will try to fallback to a supported - generic variant. - - Can be overridden to have a fallback language depending on the request, - e.g. based on top level domain. - """ - try: - return translation.get_supported_language_variant(settings.LANGUAGE_CODE) - except LookupError: - return settings.LANGUAGE_CODE - def process_request(self, request): urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) - i18n_patterns_used, _ = is_language_prefix_patterns_used(urlconf) + ( + i18n_patterns_used, + prefixed_default_language, + ) = is_language_prefix_patterns_used(urlconf) language = translation.get_language_from_request( request, check_path=i18n_patterns_used ) - if not language: - language = self.get_fallback_language(request) - + language_from_path = translation.get_language_from_path(request.path_info) + if ( + not language_from_path + and i18n_patterns_used + and not prefixed_default_language + ): + language = settings.LANGUAGE_CODE translation.activate(language) request.LANGUAGE_CODE = translation.get_language() def process_response(self, request, response): language = translation.get_language() language_from_path = translation.get_language_from_path(request.path_info) - language_from_request = translation.get_language_from_request(request) urlconf = getattr(request, "urlconf", settings.ROOT_URLCONF) ( i18n_patterns_used, @@ -57,7 +48,7 @@ class LocaleMiddleware(MiddlewareMixin): response.status_code == 404 and not language_from_path and i18n_patterns_used - and (prefixed_default_language or language_from_request) + and prefixed_default_language ): # Maybe the language code is missing in the URL? Try adding the # language prefix and redirecting to that URL. diff --git a/django/urls/resolvers.py b/django/urls/resolvers.py index 658a6a6f97..b021673772 100644 --- a/django/urls/resolvers.py +++ b/django/urls/resolvers.py @@ -23,7 +23,7 @@ from django.utils.datastructures import MultiValueDict from django.utils.functional import cached_property from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes from django.utils.regex_helper import _lazy_re_compile, normalize -from django.utils.translation import get_language, get_supported_language_variant +from django.utils.translation import get_language from .converters import get_converter from .exceptions import NoReverseMatch, Resolver404 @@ -351,8 +351,7 @@ class LocalePrefixPattern: @property def language_prefix(self): language_code = get_language() or settings.LANGUAGE_CODE - default_language = get_supported_language_variant(settings.LANGUAGE_CODE) - if language_code == default_language and not self.prefix_default_language: + if language_code == settings.LANGUAGE_CODE and not self.prefix_default_language: return "" else: return "%s/" % language_code diff --git a/django/utils/translation/__init__.py b/django/utils/translation/__init__.py index 69820a2fc4..0b3f78e486 100644 --- a/django/utils/translation/__init__.py +++ b/django/utils/translation/__init__.py @@ -17,7 +17,6 @@ __all__ = [ "get_language_from_request", "get_language_info", "get_language_bidi", - "get_supported_language_variant", "check_for_language", "to_language", "to_locale", diff --git a/django/utils/translation/trans_null.py b/django/utils/translation/trans_null.py index 595a705e2b..c8bfb1256e 100644 --- a/django/utils/translation/trans_null.py +++ b/django/utils/translation/trans_null.py @@ -53,7 +53,7 @@ def check_for_language(x): def get_language_from_request(request, check_path=False): - return None + return settings.LANGUAGE_CODE def get_language_from_path(request): diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py index 46a94b99ff..872c80b00f 100644 --- a/django/utils/translation/trans_real.py +++ b/django/utils/translation/trans_real.py @@ -583,7 +583,11 @@ def get_language_from_request(request, check_path=False): return get_supported_language_variant(accept_lang) except LookupError: continue - return None + + try: + return get_supported_language_variant(settings.LANGUAGE_CODE) + except LookupError: + return settings.LANGUAGE_CODE @functools.lru_cache(maxsize=1000) diff --git a/docs/releases/4.2.1.txt b/docs/releases/4.2.1.txt index 151d16466f..a1130effe8 100644 --- a/docs/releases/4.2.1.txt +++ b/docs/releases/4.2.1.txt @@ -31,6 +31,11 @@ Bugfixes ``prefix_default_language`` argument when a fallback language of the default language was used (:ticket:`34455`). +* Fixed a regression in Django 4.2 where translated URLs of the default + language from ``i18n_patterns()`` with ``prefix_default_language`` set to + ``False`` raised 404 errors for a request with a different language + (:ticket:`34515`). + * Fixed a regression in Django 4.2 where creating copies and deep copies of ``HttpRequest``, ``HttpResponse``, and their subclasses didn't always work correctly (:ticket:`34482`, :ticket:`34484`). diff --git a/docs/releases/4.2.txt b/docs/releases/4.2.txt index b113f3d217..d923be55b8 100644 --- a/docs/releases/4.2.txt +++ b/docs/releases/4.2.txt @@ -248,10 +248,6 @@ Internationalization * Added support and translations for the Central Kurdish (Sorani) language. -* The :class:`~django.middleware.locale.LocaleMiddleware` now respects a - language from the request when :func:`~django.conf.urls.i18n.i18n_patterns` - is used with the ``prefix_default_language`` argument set to ``False``. - Logging ~~~~~~~ diff --git a/tests/i18n/patterns/tests.py b/tests/i18n/patterns/tests.py index ad7b8ed1d4..df55a1ee13 100644 --- a/tests/i18n/patterns/tests.py +++ b/tests/i18n/patterns/tests.py @@ -431,6 +431,27 @@ class URLResponseTests(URLTestCaseBase): self.assertEqual(response.context["LANGUAGE_CODE"], "nl") +@override_settings(ROOT_URLCONF="i18n.urls_default_unprefixed", LANGUAGE_CODE="nl") +class URLPrefixedFalseTranslatedTests(URLTestCaseBase): + def test_translated_path_unprefixed_language_other_than_accepted_header(self): + response = self.client.get("/gebruikers/", headers={"accept-language": "en"}) + self.assertEqual(response.status_code, 200) + + def test_translated_path_unprefixed_language_other_than_cookie_language(self): + self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "en"}) + response = self.client.get("/gebruikers/") + self.assertEqual(response.status_code, 200) + + def test_translated_path_prefixed_language_other_than_accepted_header(self): + response = self.client.get("/en/users/", headers={"accept-language": "nl"}) + self.assertEqual(response.status_code, 200) + + def test_translated_path_prefixed_language_other_than_cookie_language(self): + self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "nl"}) + response = self.client.get("/en/users/") + self.assertEqual(response.status_code, 200) + + class URLRedirectWithScriptAliasTests(URLTestCaseBase): """ #21579 - LocaleMiddleware should respect the script prefix. diff --git a/tests/i18n/tests.py b/tests/i18n/tests.py index 01a729ed7b..d44ddb9f83 100644 --- a/tests/i18n/tests.py +++ b/tests/i18n/tests.py @@ -1926,22 +1926,8 @@ class UnprefixedDefaultLanguageTests(SimpleTestCase): response = self.client.get("/fr/simple/") self.assertEqual(response.content, b"Oui") - def test_unprefixed_language_with_accept_language(self): - """'Accept-Language' is respected.""" - response = self.client.get("/simple/", headers={"accept-language": "fr"}) - self.assertRedirects(response, "/fr/simple/") - - def test_unprefixed_language_with_cookie_language(self): - """A language set in the cookies is respected.""" - self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fr"}) - response = self.client.get("/simple/") - self.assertRedirects(response, "/fr/simple/") - - def test_unprefixed_language_with_non_valid_language(self): - response = self.client.get("/simple/", headers={"accept-language": "fi"}) - self.assertEqual(response.content, b"Yes") - self.client.cookies.load({settings.LANGUAGE_COOKIE_NAME: "fi"}) - response = self.client.get("/simple/") + def test_unprefixed_language_other_than_accept_language(self): + response = self.client.get("/simple/", HTTP_ACCEPT_LANGUAGE="fr") self.assertEqual(response.content, b"Yes") def test_page_with_dash(self): @@ -2017,7 +2003,10 @@ class CountrySpecificLanguageTests(SimpleTestCase): def test_get_language_from_request_null(self): lang = trans_null.get_language_from_request(None) - self.assertEqual(lang, None) + self.assertEqual(lang, "en") + with override_settings(LANGUAGE_CODE="de"): + lang = trans_null.get_language_from_request(None) + self.assertEqual(lang, "de") def test_specific_language_codes(self): # issue 11915 diff --git a/tests/i18n/urls_default_unprefixed.py b/tests/i18n/urls_default_unprefixed.py index f14b059d37..d4ab5a7e1d 100644 --- a/tests/i18n/urls_default_unprefixed.py +++ b/tests/i18n/urls_default_unprefixed.py @@ -7,5 +7,6 @@ urlpatterns = i18n_patterns( re_path(r"^(?P[\w-]+)-page", lambda request, **arg: HttpResponse(_("Yes"))), path("simple/", lambda r: HttpResponse(_("Yes"))), re_path(r"^(.+)/(.+)/$", lambda *args: HttpResponse()), + re_path(_(r"^users/$"), lambda *args: HttpResponse(), name="users"), prefix_default_language=False, ) -- cgit v1.2.1