Skip to content

Commit 335e2b4

Browse files
committed
Fix accept header scenario, add tests
1 parent 1b29d58 commit 335e2b4

File tree

2 files changed

+134
-13
lines changed

2 files changed

+134
-13
lines changed

apps/core/middleware.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from django.http import Http404
1+
from django.conf import settings
22
from django.urls import LocalePrefixPattern
33
from django.utils.translation import activate, get_language
44

@@ -12,16 +12,18 @@ def __init__(self, get_response):
1212
self.get_response = get_response
1313

1414
def __call__(self, request):
15-
response = self.get_response(request)
16-
if request.resolver_match:
17-
# Check if the path is in the i18n_patterns
18-
if pattern.match(request.resolver_match.route):
19-
try:
20-
HomePage.objects.get(
21-
locale__language_code=get_language(), live=True
22-
)
23-
except HomePage.DoesNotExist:
24-
# Activate English so that we have a site menu
25-
activate("en-latest")
26-
raise Http404()
15+
if (
16+
request.path == "/"
17+
or request.resolver_match
18+
and pattern.match(request.resolver_match.route)
19+
):
20+
try:
21+
HomePage.objects.get(locale__language_code=get_language(), live=True)
22+
response = self.get_response(request)
23+
except HomePage.DoesNotExist:
24+
# The requested language is not available, use the default
25+
activate(settings.LANGUAGE_CODE)
26+
response = self.get_response(request)
27+
else:
28+
response = self.get_response(request)
2729
return response

apps/core/tests/test_middleware.py

Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
from http import HTTPStatus
2+
3+
from django.conf import settings
4+
from django.test import TestCase
5+
from django.urls import reverse
6+
7+
from apps.core.factories import HomePageFactory, LocaleFactory
8+
9+
10+
class TestValidateLocaleMiddleware(TestCase):
11+
"""
12+
LocaleMiddleware tries to determine the user’s language preference by following
13+
this algorithm:
14+
15+
1. First, it looks for the language prefix in the requested URL.
16+
2. Failing that, it looks for a cookie, named `django_language`.
17+
3. Failing that, it looks at the Accept-Language HTTP header. This header is sent by
18+
your browser and tells the server which language(s) you prefer, in order by
19+
priority. Django tries each language in the header until it finds one with
20+
available translations.
21+
4. Failing that, it uses the global LANGUAGE_CODE setting.
22+
23+
Wagtail Guide has all languages in LANGUAGES setting, therefore LocaleMiddleware
24+
will redirect to any language prefix URL. Unfortunately, Wagtail might not have
25+
the corresponding homepage for that language published. If Wagtail has no
26+
homepage available for the requested language, it raises a 404.
27+
28+
This behaviour is undesirable. As step 3 of the algoritm will end up in a 404.
29+
To resolve this issue, we introduce our ValidateLocaleMiddleware.
30+
31+
ValidateLocaleMiddleware checks for a published homepage for the requested language.
32+
If it does not exist, it falls back to English. The default, and always published
33+
language/homepage.
34+
35+
The ValidateLocaleMiddleware kicks in on `/` and any i18n pattern.
36+
Other URLs are ignored, and have the default LocaleMiddleware behaviour.
37+
"""
38+
39+
def setUp(self):
40+
self.en = LocaleFactory(language_code="en-latest")
41+
self.home_en = HomePageFactory(locale=self.en)
42+
43+
def test_middleware_settings(self):
44+
self.assertIn("django.middleware.locale.LocaleMiddleware", settings.MIDDLEWARE)
45+
self.assertIn(
46+
"apps.core.middleware.ValidateLocaleMiddleware", settings.MIDDLEWARE
47+
)
48+
self.assertGreater(
49+
settings.MIDDLEWARE.index("apps.core.middleware.ValidateLocaleMiddleware"),
50+
settings.MIDDLEWARE.index("django.middleware.locale.LocaleMiddleware"),
51+
)
52+
53+
def test_request_root_redirects_to_language_code(self):
54+
self.assertEqual(settings.LANGUAGE_CODE, "en-latest")
55+
response = self.client.get("/")
56+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
57+
self.assertEqual(response.url, "/en-latest/")
58+
self.assertEqual(self.client.get("/en-latest/").status_code, HTTPStatus.OK)
59+
60+
def test_request_root_with_accept_language_header(self):
61+
# To English, if German doesn't exist
62+
response = self.client.get("/", HTTP_ACCEPT_LANGUAGE="de")
63+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
64+
self.assertEqual(response.url, "/en-latest/")
65+
self.assertEqual(self.client.get("/en-latest/").status_code, HTTPStatus.OK)
66+
# To German, if German exists
67+
de = LocaleFactory(language_code="de-latest")
68+
self.home_de = self.home_en.copy_for_translation(locale=de)
69+
self.home_de.save_revision().publish()
70+
response = self.client.get("/", HTTP_ACCEPT_LANGUAGE="de")
71+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
72+
self.assertEqual(response.url, "/de-latest/")
73+
self.assertEqual(self.client.get("/de-latest/").status_code, HTTPStatus.OK)
74+
75+
def test_request_root_with_cookie(self):
76+
self.assertEqual(settings.LANGUAGE_COOKIE_NAME, "django_language")
77+
de = LocaleFactory(language_code="de-latest")
78+
self.home_de = self.home_en.copy_for_translation(locale=de)
79+
self.home_de.save_revision().publish()
80+
# The HTTP_ACCEPT_LANGUAGE is ignored, the cookie takes precedence
81+
self.client.cookies["django_language"] = "en-latest"
82+
response = self.client.get("/", HTTP_ACCEPT_LANGUAGE="de")
83+
self.assertEqual(response.status_code, HTTPStatus.FOUND)
84+
self.assertEqual(response.url, "/en-latest/")
85+
self.assertEqual(self.client.get("/en-latest/").status_code, HTTPStatus.OK)
86+
87+
def test_request_specific_url(self):
88+
de = LocaleFactory(language_code="de-latest")
89+
self.home_de = self.home_en.copy_for_translation(locale=de)
90+
self.home_de.save_revision().publish()
91+
# The HTTP_ACCEPT_LANGUAGE is ignored
92+
self.client.cookies["django_language"] = "de"
93+
# The HTTP_ACCEPT_LANGUAGE is ignored
94+
response = self.client.get("/en-latest/", HTTP_ACCEPT_LANGUAGE="de")
95+
self.assertEqual(response.status_code, HTTPStatus.OK)
96+
97+
def test_wagtail_admin_respects_accept_language(self):
98+
# Non i18n_patterns respect HTTP_ACCEPT_LANGUAGE
99+
url = reverse("wagtailadmin_login")
100+
response = self.client.get(url, HTTP_ACCEPT_LANGUAGE="nl")
101+
expected = "<h1>Inloggen in Wagtail</h1>"
102+
self.assertInHTML(expected, str(response.content))
103+
104+
def test_django_admin_respects_accept_language(self):
105+
# Non i18n_patterns respect HTTP_ACCEPT_LANGUAGE
106+
url = reverse("admin:login")
107+
response = self.client.get(url, HTTP_ACCEPT_LANGUAGE="nl")
108+
expected = '<h1 id="site-name"><a href="/django-admin/">Django-beheer</a></h1>'
109+
self.assertInHTML(expected, str(response.content))
110+
111+
def test_sitemap_xml(self):
112+
# Non i18n_patterns respect HTTP_ACCEPT_LANGUAGE
113+
url = "/sitemap.xml"
114+
response = self.client.get(url, HTTP_ACCEPT_LANGUAGE="nl")
115+
expected = (
116+
b'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" '
117+
b'xmlns:xhtml="http://www.w3.org/1999/xhtml">'
118+
)
119+
self.assertIn(expected, response.content)

0 commit comments

Comments
 (0)