Skip to content

Commit 373d83b

Browse files
committed
Fix accept header scenario, add tests
1 parent 1b29d58 commit 373d83b

File tree

2 files changed

+135
-13
lines changed

2 files changed

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

0 commit comments

Comments
 (0)