dark_lang: only allow released langs in accept header LOC-72, LOC-85
Only return languages we've actually released LOC-85 Perform fuzzy matching to greedily serve the best released language LOC-72
This commit is contained in:
@@ -83,11 +83,17 @@ class DarkLangMiddleware(object):
|
||||
self._clean_accept_headers(request)
|
||||
self._activate_preview_language(request)
|
||||
|
||||
def _is_released(self, lang_code):
|
||||
"""
|
||||
``True`` iff one of the values in ``self.released_langs`` is a prefix of ``lang_code``.
|
||||
"""
|
||||
return any(lang_code.lower().startswith(released_lang.lower()) for released_lang in self.released_langs)
|
||||
def _fuzzy_match(self, lang_code):
|
||||
"""Returns a fuzzy match for lang_code"""
|
||||
if lang_code in self.released_langs:
|
||||
return lang_code
|
||||
|
||||
lang_prefix = lang_code.partition('-')[0]
|
||||
for released_lang in self.released_langs:
|
||||
released_prefix = released_lang.partition('-')[0]
|
||||
if lang_prefix == released_prefix:
|
||||
return released_lang
|
||||
return None
|
||||
|
||||
def _format_accept_value(self, lang, priority=1.0):
|
||||
"""
|
||||
@@ -104,12 +110,13 @@ class DarkLangMiddleware(object):
|
||||
if accept is None or accept == '*':
|
||||
return
|
||||
|
||||
new_accept = ", ".join(
|
||||
self._format_accept_value(lang, priority)
|
||||
for lang, priority
|
||||
in dark_parse_accept_lang_header(accept)
|
||||
if self._is_released(lang)
|
||||
)
|
||||
new_accept = []
|
||||
for lang, priority in dark_parse_accept_lang_header(accept):
|
||||
fuzzy_code = self._fuzzy_match(lang.lower())
|
||||
if fuzzy_code:
|
||||
new_accept.append(self._format_accept_value(fuzzy_code, priority))
|
||||
|
||||
new_accept = ", ".join(new_accept)
|
||||
|
||||
request.META['HTTP_ACCEPT_LANGUAGE'] = new_accept
|
||||
|
||||
|
||||
@@ -4,8 +4,10 @@ Tests of DarkLangMiddleware
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpRequest
|
||||
|
||||
import ddt
|
||||
from django.test import TestCase
|
||||
from mock import Mock
|
||||
import unittest
|
||||
|
||||
from dark_lang.middleware import DarkLangMiddleware
|
||||
from dark_lang.models import DarkLangConfig
|
||||
@@ -23,6 +25,7 @@ def set_if_set(dct, key, value):
|
||||
dct[key] = value
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class DarkLangMiddlewareTests(TestCase):
|
||||
"""
|
||||
Tests of DarkLangMiddleware
|
||||
@@ -82,6 +85,10 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
def test_wildcard_accept(self):
|
||||
self.assertAcceptEquals('*', self.process_request(accept='*'))
|
||||
|
||||
def test_malformed_accept(self):
|
||||
self.assertAcceptEquals('', self.process_request(accept='xxxxxxxxxxxx'))
|
||||
self.assertAcceptEquals('', self.process_request(accept='en;q=1.0, es-419:q-0.8'))
|
||||
|
||||
def test_released_accept(self):
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0',
|
||||
@@ -123,14 +130,17 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
)
|
||||
|
||||
def test_accept_released_territory(self):
|
||||
# We will munge 'rel-ter' to be 'rel', so the 'rel-ter'
|
||||
# user will actually receive the released language 'rel'
|
||||
# (Otherwise, the user will actually end up getting the server default)
|
||||
self.assertAcceptEquals(
|
||||
'rel-ter;q=1.0, rel;q=0.5',
|
||||
'rel;q=1.0, rel;q=0.5',
|
||||
self.process_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
)
|
||||
|
||||
def test_accept_mixed_case(self):
|
||||
self.assertAcceptEquals(
|
||||
'rel-TER;q=1.0, REL;q=0.5',
|
||||
'rel;q=1.0, rel;q=0.5',
|
||||
self.process_request(accept='rel-TER;q=1.0, REL;q=0.5')
|
||||
)
|
||||
|
||||
@@ -140,11 +150,85 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
# Since we have only released "rel-ter", the requested code "rel" will
|
||||
# fuzzy match to "rel-ter", in addition to "rel-ter" exact matching "rel-ter"
|
||||
self.assertAcceptEquals(
|
||||
'rel-ter;q=1.0',
|
||||
'rel-ter;q=1.0, rel-ter;q=0.5',
|
||||
self.process_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
('es;q=1.0, pt;q=0.5', 'es-419;q=1.0'), # 'es' should get 'es-419', not English
|
||||
('es-AR;q=1.0, pt;q=0.5', 'es-419;q=1.0'), # 'es-AR' should get 'es-419', not English
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_partial_match_es419(self, accept_header, expected):
|
||||
# Release es-419
|
||||
DarkLangConfig(
|
||||
released_languages=('es-419, en'),
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
expected,
|
||||
self.process_request(accept=accept_header)
|
||||
)
|
||||
|
||||
def test_partial_match_esar_es(self):
|
||||
# If I release 'es', 'es-AR' should get 'es', not English
|
||||
DarkLangConfig(
|
||||
released_languages=('es, en'),
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'es;q=1.0',
|
||||
self.process_request(accept='es-AR;q=1.0, pt;q=0.5')
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
# Test condition: If I release 'es-419, es, es-es'...
|
||||
('es;q=1.0, pt;q=0.5', 'es;q=1.0'), # 1. es should get es
|
||||
('es-419;q=1.0, pt;q=0.5', 'es-419;q=1.0'), # 2. es-419 should get es-419
|
||||
('es-es;q=1.0, pt;q=0.5', 'es-es;q=1.0'), # 3. es-es should get es-es
|
||||
)
|
||||
@ddt.unpack
|
||||
def test_exact_match_gets_priority(self, accept_header, expected):
|
||||
# Release 'es-419, es, es-es'
|
||||
DarkLangConfig(
|
||||
released_languages=('es-419, es, es-es'),
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
self.assertAcceptEquals(
|
||||
expected,
|
||||
self.process_request(accept=accept_header)
|
||||
)
|
||||
|
||||
@unittest.skip("This won't work until fallback is implemented for LA country codes. See LOC-86")
|
||||
@ddt.data(
|
||||
'es-AR', # Argentina
|
||||
'es-PY', # Paraguay
|
||||
)
|
||||
def test_partial_match_es_la(self, latin_america_code):
|
||||
# We need to figure out the best way to implement this. There are a ton of LA country
|
||||
# codes that ought to fall back to 'es-419' rather than 'es-es'.
|
||||
# http://unstats.un.org/unsd/methods/m49/m49regin.htm#americas
|
||||
# If I release 'es, es-419'
|
||||
# Latin American codes should get es-419
|
||||
DarkLangConfig(
|
||||
released_languages=('es, es-419'),
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'es-419;q=1.0',
|
||||
self.process_request(accept='{};q=1.0, pt;q=0.5'.format(latin_america_code))
|
||||
)
|
||||
|
||||
def assertSessionLangEquals(self, value, request):
|
||||
"""
|
||||
Assert that the 'django_language' set in request.session is equal to value
|
||||
@@ -224,6 +308,6 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'zh-CN;q=1.0, zh-TW;q=0.5, zh-HK;q=0.3',
|
||||
'zh-cn;q=1.0, zh-tw;q=0.5, zh-hk;q=0.3',
|
||||
self.process_request(accept='zh-Hans;q=1.0, zh-Hant-TW;q=0.5, zh-HK;q=0.3')
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user