Browse Source

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>
Mariusz Felisiak 1 year ago
parent
commit
0e444e84f8

+ 12 - 21
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.

+ 2 - 3
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

+ 0 - 1
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",

+ 1 - 1
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):

+ 5 - 1
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)

+ 5 - 0
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`).

+ 0 - 4
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
 ~~~~~~~
 

+ 21 - 0
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.

+ 6 - 17
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

+ 1 - 0
tests/i18n/urls_default_unprefixed.py

@@ -7,5 +7,6 @@ urlpatterns = i18n_patterns(
     re_path(r"^(?P<arg>[\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,
 )