Merge pull request #13018 from edx/TNL-4742_DarkLangChanges
Tnl 4742 dark lang changes
This commit is contained in:
15
cms/templates/darklang/preview_lang.html
Normal file
15
cms/templates/darklang/preview_lang.html
Normal file
@@ -0,0 +1,15 @@
|
||||
## Override the default styles_version to the Pattern Library version (version 2)
|
||||
<%! main_css = "style-main-v2" %>
|
||||
<%page expression_filter="h"/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
|
||||
<%inherit file="../base.html" />
|
||||
<%block name="title">${_("Preview Language Setting")}</%block>
|
||||
<%block name="bodyclass">is-signedin pattern-library</%block>
|
||||
|
||||
<%block name="content">
|
||||
<%include file="/darklang/preview_lang_include.html" />
|
||||
</%block>
|
||||
|
||||
@@ -55,6 +55,9 @@ urlpatterns = patterns(
|
||||
|
||||
# Update session view
|
||||
url(r'^lang_pref/session_language', 'lang_pref.views.update_session_language', name='session_language'),
|
||||
|
||||
# Darklang View to change the preview language (or dark language)
|
||||
url(r'^update_lang/', include('dark_lang.urls', namespace='darklang')),
|
||||
)
|
||||
|
||||
# User creation and updating views
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
"""
|
||||
Middleware for dark-launching languages. These languages won't be used
|
||||
when determining which translation to give a user based on their browser
|
||||
header, but can be selected by setting the ``preview-lang`` query parameter
|
||||
to the language code.
|
||||
|
||||
Adding the query parameter ``clear-lang`` will reset the language stored
|
||||
in the user's session.
|
||||
header, but can be selected by setting the Preview Languages on the Dark
|
||||
Language setting page.
|
||||
|
||||
This middleware must be placed before the LocaleMiddleware, but after
|
||||
the SessionMiddleware.
|
||||
@@ -15,16 +12,29 @@ from django.conf import settings
|
||||
from dark_lang import DARK_LANGUAGE_KEY
|
||||
from dark_lang.models import DarkLangConfig
|
||||
from openedx.core.djangoapps.user_api.preferences.api import (
|
||||
delete_user_preference, get_user_preference, set_user_preference
|
||||
get_user_preference
|
||||
)
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
|
||||
from django.utils.translation.trans_real import parse_accept_lang_header
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
|
||||
# If django 1.7 or higher is used, the right-side can be updated with new-style codes.
|
||||
CHINESE_LANGUAGE_CODE_MAP = {
|
||||
# The following are the new-style language codes for chinese language
|
||||
'zh-hans': 'zh-CN', # Chinese (Simplified),
|
||||
'zh-hans-cn': 'zh-CN', # Chinese (Simplified, China)
|
||||
'zh-hans-sg': 'zh-CN', # Chinese (Simplified, Singapore)
|
||||
'zh-hant': 'zh-TW', # Chinese (Traditional)
|
||||
'zh-hant-hk': 'zh-HK', # Chinese (Traditional, Hongkong)
|
||||
'zh-hant-mo': 'zh-TW', # Chinese (Traditional, Macau)
|
||||
'zh-hant-tw': 'zh-TW', # Chinese (Traditional, Taiwan)
|
||||
# The following are the old-style language codes that django does not recognize
|
||||
'zh-mo': 'zh-TW', # Chinese (Traditional, Macau)
|
||||
'zh-sg': 'zh-CN', # Chinese (Simplified, Singapore)
|
||||
}
|
||||
|
||||
def dark_parse_accept_lang_header(accept):
|
||||
'''
|
||||
|
||||
def _dark_parse_accept_lang_header(accept):
|
||||
"""
|
||||
The use of 'zh-cn' for 'Simplified Chinese' and 'zh-tw' for 'Traditional Chinese'
|
||||
are now deprecated, as discussed here: https://code.djangoproject.com/ticket/18419.
|
||||
The new language codes 'zh-hans' and 'zh-hant' are now used since django 1.7.
|
||||
@@ -34,7 +44,7 @@ def dark_parse_accept_lang_header(accept):
|
||||
This function can keep compatibility between the old and new language codes. If one
|
||||
day edX uses django 1.7 or higher, this function can be modified to support the old
|
||||
language codes until there are no browsers use them.
|
||||
'''
|
||||
"""
|
||||
browser_langs = parse_accept_lang_header(accept)
|
||||
django_langs = []
|
||||
for lang, priority in browser_langs:
|
||||
@@ -43,21 +53,6 @@ def dark_parse_accept_lang_header(accept):
|
||||
|
||||
return django_langs
|
||||
|
||||
# If django 1.7 or higher is used, the right-side can be updated with new-style codes.
|
||||
CHINESE_LANGUAGE_CODE_MAP = {
|
||||
# The following are the new-style language codes for chinese language
|
||||
'zh-hans': 'zh-CN', # Chinese (Simplified),
|
||||
'zh-hans-cn': 'zh-CN', # Chinese (Simplified, China)
|
||||
'zh-hans-sg': 'zh-CN', # Chinese (Simplified, Singapore)
|
||||
'zh-hant': 'zh-TW', # Chinese (Traditional)
|
||||
'zh-hant-hk': 'zh-HK', # Chinese (Traditional, Hongkong)
|
||||
'zh-hant-mo': 'zh-TW', # Chinese (Traditional, Macau)
|
||||
'zh-hant-tw': 'zh-TW', # Chinese (Traditional, Taiwan)
|
||||
# The following are the old-style language codes that django does not recognize
|
||||
'zh-mo': 'zh-TW', # Chinese (Traditional, Macau)
|
||||
'zh-sg': 'zh-CN', # Chinese (Simplified, Singapore)
|
||||
}
|
||||
|
||||
|
||||
class DarkLangMiddleware(object):
|
||||
"""
|
||||
@@ -66,7 +61,6 @@ class DarkLangMiddleware(object):
|
||||
This is configured by creating ``DarkLangConfig`` rows in the database,
|
||||
using the django admin site.
|
||||
"""
|
||||
|
||||
@property
|
||||
def released_langs(self):
|
||||
"""
|
||||
@@ -89,21 +83,16 @@ class DarkLangMiddleware(object):
|
||||
|
||||
def _fuzzy_match(self, lang_code):
|
||||
"""Returns a fuzzy match for lang_code"""
|
||||
match = None
|
||||
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):
|
||||
"""
|
||||
Formats lang and priority into a valid accept header fragment.
|
||||
"""
|
||||
return "{};q={}".format(lang, priority)
|
||||
match = lang_code
|
||||
else:
|
||||
lang_prefix = lang_code.partition('-')[0]
|
||||
for released_lang in self.released_langs:
|
||||
released_prefix = released_lang.partition('-')[0]
|
||||
if lang_prefix == released_prefix:
|
||||
match = released_lang
|
||||
return match
|
||||
|
||||
def _clean_accept_headers(self, request):
|
||||
"""
|
||||
@@ -115,10 +104,11 @@ class DarkLangMiddleware(object):
|
||||
return
|
||||
|
||||
new_accept = []
|
||||
for lang, priority in dark_parse_accept_lang_header(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))
|
||||
# Formats lang and priority into a valid accept header fragment.
|
||||
new_accept.append("{};q={}".format(fuzzy_code, priority))
|
||||
|
||||
new_accept = ", ".join(new_accept)
|
||||
|
||||
@@ -126,29 +116,11 @@ class DarkLangMiddleware(object):
|
||||
|
||||
def _activate_preview_language(self, request):
|
||||
"""
|
||||
If the request has the get parameter ``preview-lang``,
|
||||
and that language doesn't appear in ``self.released_langs``,
|
||||
then set the session LANGUAGE_SESSION_KEY to that language.
|
||||
Check the user's dark language setting in the session and apply it
|
||||
"""
|
||||
auth_user = request.user.is_authenticated()
|
||||
if 'clear-lang' in request.GET:
|
||||
# delete the session language key (if one is set)
|
||||
if LANGUAGE_SESSION_KEY in request.session:
|
||||
del request.session[LANGUAGE_SESSION_KEY]
|
||||
|
||||
if auth_user:
|
||||
# Reset user's dark lang preference to null
|
||||
delete_user_preference(request.user, DARK_LANGUAGE_KEY)
|
||||
# Get & set user's preferred language
|
||||
user_pref = get_user_preference(request.user, LANGUAGE_KEY)
|
||||
if user_pref:
|
||||
request.session[LANGUAGE_SESSION_KEY] = user_pref
|
||||
return
|
||||
|
||||
# Get the user's preview lang - this is either going to be set from a query
|
||||
# param `?preview-lang=xx`, or we may have one already set as a dark lang preference.
|
||||
preview_lang = request.GET.get('preview-lang', None)
|
||||
if not preview_lang and auth_user:
|
||||
preview_lang = None
|
||||
if auth_user:
|
||||
# Get the request user's dark lang preference
|
||||
preview_lang = get_user_preference(request.user, DARK_LANGUAGE_KEY)
|
||||
|
||||
@@ -158,8 +130,3 @@ class DarkLangMiddleware(object):
|
||||
|
||||
# Set the session key to the requested preview lang
|
||||
request.session[LANGUAGE_SESSION_KEY] = preview_lang
|
||||
|
||||
# Make sure that we set the requested preview lang as the dark lang preference for the
|
||||
# user, so that the lang_pref middleware doesn't clobber away the dark lang preview.
|
||||
if auth_user:
|
||||
set_user_preference(request.user, DARK_LANGUAGE_KEY, preview_lang)
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
"""
|
||||
Tests of DarkLangMiddleware
|
||||
"""
|
||||
from django.contrib.auth.models import User
|
||||
from django.http import HttpRequest
|
||||
import unittest
|
||||
|
||||
import ddt
|
||||
from django.http import HttpRequest
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from mock import Mock
|
||||
import unittest
|
||||
|
||||
from dark_lang.middleware import DarkLangMiddleware
|
||||
from dark_lang.models import DarkLangConfig
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
UNSET = object()
|
||||
|
||||
|
||||
@@ -34,23 +32,23 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
"""
|
||||
def setUp(self):
|
||||
super(DarkLangMiddlewareTests, self).setUp()
|
||||
self.user = User()
|
||||
self.user = UserFactory.build(username='test', email='test@edx.org', password='test_password')
|
||||
self.user.save()
|
||||
self.client = Client()
|
||||
self.client.login(username=self.user.username, password='test_password')
|
||||
DarkLangConfig(
|
||||
released_languages='rel',
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
def process_request(self, language_session_key=UNSET, accept=UNSET, preview_lang=UNSET, clear_lang=UNSET):
|
||||
def process_middleware_request(self, language_session_key=UNSET, accept=UNSET):
|
||||
"""
|
||||
Build a request and then process it using the ``DarkLangMiddleware``.
|
||||
|
||||
Args:
|
||||
language_session_key (str): The language code to set in request.session[LANUGAGE_SESSION_KEY]
|
||||
accept (str): The accept header to set in request.META['HTTP_ACCEPT_LANGUAGE']
|
||||
preview_lang (str): The value to set in request.GET['preview_lang']
|
||||
clear_lang (str): The value to set in request.GET['clear_lang']
|
||||
"""
|
||||
session = {}
|
||||
set_if_set(session, LANGUAGE_SESSION_KEY, language_session_key)
|
||||
@@ -58,17 +56,16 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
meta = {}
|
||||
set_if_set(meta, 'HTTP_ACCEPT_LANGUAGE', accept)
|
||||
|
||||
get = {}
|
||||
set_if_set(get, 'preview-lang', preview_lang)
|
||||
set_if_set(get, 'clear-lang', clear_lang)
|
||||
|
||||
request = Mock(
|
||||
spec=HttpRequest,
|
||||
session=session,
|
||||
META=meta,
|
||||
GET=get,
|
||||
user=UserFactory()
|
||||
GET={},
|
||||
method='GET',
|
||||
user=self.user
|
||||
)
|
||||
|
||||
# Process it through the Middleware to ensure the language is available as expected.
|
||||
self.assertIsNone(DarkLangMiddleware().process_request(request))
|
||||
return request
|
||||
|
||||
@@ -83,31 +80,31 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
)
|
||||
|
||||
def test_empty_accept(self):
|
||||
self.assertAcceptEquals(UNSET, self.process_request())
|
||||
self.assertAcceptEquals(UNSET, self.process_middleware_request())
|
||||
|
||||
def test_wildcard_accept(self):
|
||||
self.assertAcceptEquals('*', self.process_request(accept='*'))
|
||||
self.assertAcceptEquals('*', self.process_middleware_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'))
|
||||
self.assertAcceptEquals('', self.process_middleware_request(accept='xxxxxxxxxxxx'))
|
||||
self.assertAcceptEquals('', self.process_middleware_request(accept='en;q=1.0, es-419:q-0.8'))
|
||||
|
||||
def test_released_accept(self):
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0',
|
||||
self.process_request(accept='rel;q=1.0')
|
||||
self.process_middleware_request(accept='rel;q=1.0')
|
||||
)
|
||||
|
||||
def test_unreleased_accept(self):
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0',
|
||||
self.process_request(accept='rel;q=1.0, unrel;q=0.5')
|
||||
self.process_middleware_request(accept='rel;q=1.0, unrel;q=0.5')
|
||||
)
|
||||
|
||||
def test_accept_with_syslang(self):
|
||||
self.assertAcceptEquals(
|
||||
'en;q=1.0, rel;q=0.8',
|
||||
self.process_request(accept='en;q=1.0, rel;q=0.8, unrel;q=0.5')
|
||||
self.process_middleware_request(accept='en;q=1.0, rel;q=0.8, unrel;q=0.5')
|
||||
)
|
||||
|
||||
def test_accept_multiple_released_langs(self):
|
||||
@@ -119,17 +116,17 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0, unrel;q=0.5',
|
||||
self.process_request(accept='rel;q=1.0, unrel;q=0.5')
|
||||
self.process_middleware_request(accept='rel;q=1.0, unrel;q=0.5')
|
||||
)
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0, unrel;q=0.5',
|
||||
self.process_request(accept='rel;q=1.0, notrel;q=0.3, unrel;q=0.5')
|
||||
self.process_middleware_request(accept='rel;q=1.0, notrel;q=0.3, unrel;q=0.5')
|
||||
)
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0, unrel;q=0.5',
|
||||
self.process_request(accept='notrel;q=0.3, rel;q=1.0, unrel;q=0.5')
|
||||
self.process_middleware_request(accept='notrel;q=0.3, rel;q=1.0, unrel;q=0.5')
|
||||
)
|
||||
|
||||
def test_accept_released_territory(self):
|
||||
@@ -138,17 +135,17 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
# (Otherwise, the user will actually end up getting the server default)
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0, rel;q=0.5',
|
||||
self.process_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
self.process_middleware_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
)
|
||||
|
||||
def test_accept_mixed_case(self):
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0, rel;q=0.5',
|
||||
self.process_request(accept='rel-TER;q=1.0, REL;q=0.5')
|
||||
self.process_middleware_request(accept='rel-TER;q=1.0, REL;q=0.5')
|
||||
)
|
||||
|
||||
DarkLangConfig(
|
||||
released_languages=('REL-TER'),
|
||||
released_languages='REL-TER',
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
@@ -157,7 +154,7 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
# fuzzy match to "rel-ter", in addition to "rel-ter" exact matching "rel-ter"
|
||||
self.assertAcceptEquals(
|
||||
'rel-ter;q=1.0, rel-ter;q=0.5',
|
||||
self.process_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
self.process_middleware_request(accept='rel-ter;q=1.0, rel;q=0.5')
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
@@ -175,7 +172,7 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
|
||||
self.assertAcceptEquals(
|
||||
expected,
|
||||
self.process_request(accept=accept_header)
|
||||
self.process_middleware_request(accept=accept_header)
|
||||
)
|
||||
|
||||
def test_partial_match_esar_es(self):
|
||||
@@ -188,7 +185,7 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'es;q=1.0',
|
||||
self.process_request(accept='es-AR;q=1.0, pt;q=0.5')
|
||||
self.process_middleware_request(accept='es-AR;q=1.0, pt;q=0.5')
|
||||
)
|
||||
|
||||
@ddt.data(
|
||||
@@ -207,7 +204,7 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
).save()
|
||||
self.assertAcceptEquals(
|
||||
expected,
|
||||
self.process_request(accept=accept_header)
|
||||
self.process_middleware_request(accept=accept_header)
|
||||
)
|
||||
|
||||
@unittest.skip("This won't work until fallback is implemented for LA country codes. See LOC-86")
|
||||
@@ -229,55 +226,100 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'es-419;q=1.0',
|
||||
self.process_request(accept='{};q=1.0, pt;q=0.5'.format(latin_america_code))
|
||||
self.process_middleware_request(accept='{};q=1.0, pt;q=0.5'.format(latin_america_code))
|
||||
)
|
||||
|
||||
def assertSessionLangEquals(self, value, request):
|
||||
def assert_session_lang_equals(self, value, session):
|
||||
"""
|
||||
Assert that the LANGUAGE_SESSION_KEY set in request.session is equal to value
|
||||
Assert that the LANGUAGE_SESSION_KEY set in session is equal to value
|
||||
"""
|
||||
self.assertEquals(
|
||||
value,
|
||||
request.session.get(LANGUAGE_SESSION_KEY, UNSET)
|
||||
session.get(LANGUAGE_SESSION_KEY, UNSET)
|
||||
)
|
||||
|
||||
def _post_set_preview_lang(self, preview_language):
|
||||
"""
|
||||
Sends a post request to set the preview language
|
||||
"""
|
||||
return self.client.post('/update_lang/', {'preview_lang': preview_language, 'set_language': 'set_language'})
|
||||
|
||||
def _post_clear_preview_lang(self):
|
||||
"""
|
||||
Sends a post request to Clear the preview language
|
||||
"""
|
||||
return self.client.post('/update_lang/', {'reset': 'reset'})
|
||||
|
||||
def _set_client_session_language(self, session_language):
|
||||
"""
|
||||
Set the session language in the Client
|
||||
"""
|
||||
session = self.client.session
|
||||
session[LANGUAGE_SESSION_KEY] = session_language
|
||||
session.save()
|
||||
|
||||
def test_preview_lang_with_released_language(self):
|
||||
# Preview lang should always override selection.
|
||||
self.assertSessionLangEquals(
|
||||
# Preview lang should always override selection
|
||||
self._post_set_preview_lang('rel')
|
||||
# Refresh the page with a get request to confirm the preview language was set
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'rel',
|
||||
self.process_request(preview_lang='rel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
# Set the session language and ensure that the preview language overrides
|
||||
self._set_client_session_language('notrel')
|
||||
self._post_set_preview_lang('rel')
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'rel',
|
||||
self.process_request(preview_lang='rel', language_session_key='notrel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
def test_preview_lang_with_dark_language(self):
|
||||
self.assertSessionLangEquals(
|
||||
self._post_set_preview_lang('unrel')
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'unrel',
|
||||
self.process_request(preview_lang='unrel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
# Test a clear and then a set of the preview language
|
||||
self._post_clear_preview_lang()
|
||||
self._post_set_preview_lang('unrel')
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'unrel',
|
||||
self.process_request(preview_lang='unrel', language_session_key='notrel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
def test_empty_preview_language(self):
|
||||
# When posting an empty preview_language the currently set language should not change
|
||||
self._set_client_session_language('rel')
|
||||
self._post_set_preview_lang(' ')
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'rel',
|
||||
self.client.session
|
||||
)
|
||||
|
||||
def test_clear_lang(self):
|
||||
self.assertSessionLangEquals(
|
||||
# Clear a language when no language was set
|
||||
self._post_clear_preview_lang()
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
UNSET,
|
||||
self.process_request(clear_lang=True)
|
||||
self.client.session
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
# Set a language and clear it to ensure the clear is working as expected
|
||||
self._post_set_preview_lang('notclear')
|
||||
self._post_clear_preview_lang()
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
UNSET,
|
||||
self.process_request(clear_lang=True, language_session_key='rel')
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
UNSET,
|
||||
self.process_request(clear_lang=True, language_session_key='unrel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
def test_disabled(self):
|
||||
@@ -285,22 +327,25 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'notrel;q=0.3, rel;q=1.0, unrel;q=0.5',
|
||||
self.process_request(accept='notrel;q=0.3, rel;q=1.0, unrel;q=0.5')
|
||||
self.process_middleware_request(accept='notrel;q=0.3, rel;q=1.0, unrel;q=0.5')
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
# With DarkLang disabled the clear should not change the session language
|
||||
self._set_client_session_language('rel')
|
||||
self._post_clear_preview_lang()
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'rel',
|
||||
self.process_request(clear_lang=True, language_session_key='rel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
# Test that setting the preview language with DarkLang disabled does nothing
|
||||
self._set_client_session_language('unrel')
|
||||
self._post_set_preview_lang('rel')
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'unrel',
|
||||
self.process_request(clear_lang=True, language_session_key='unrel')
|
||||
)
|
||||
|
||||
self.assertSessionLangEquals(
|
||||
'rel',
|
||||
self.process_request(preview_lang='unrel', language_session_key='rel')
|
||||
self.client.session
|
||||
)
|
||||
|
||||
def test_accept_chinese_language_codes(self):
|
||||
@@ -312,5 +357,5 @@ class DarkLangMiddlewareTests(TestCase):
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'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')
|
||||
self.process_middleware_request(accept='zh-Hans;q=1.0, zh-Hant-TW;q=0.5, zh-HK;q=0.3')
|
||||
)
|
||||
|
||||
12
common/djangoapps/dark_lang/urls.py
Normal file
12
common/djangoapps/dark_lang/urls.py
Normal file
@@ -0,0 +1,12 @@
|
||||
"""
|
||||
Contains all the URLs for the Dark Language Support App
|
||||
"""
|
||||
|
||||
from django.conf.urls import patterns, url
|
||||
|
||||
from dark_lang import views
|
||||
|
||||
urlpatterns = patterns(
|
||||
'',
|
||||
url(r'^$', views.DarkLangView.as_view(), name='preview_lang'),
|
||||
)
|
||||
163
common/djangoapps/dark_lang/views.py
Normal file
163
common/djangoapps/dark_lang/views.py
Normal file
@@ -0,0 +1,163 @@
|
||||
"""
|
||||
Views file for the Darklang Django App
|
||||
"""
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.decorators import method_decorator
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.views.generic.base import View
|
||||
from openedx.core.djangoapps.user_api.preferences.api import (
|
||||
delete_user_preference, get_user_preference, set_user_preference
|
||||
)
|
||||
from openedx.core.lib.api.view_utils import view_auth_classes
|
||||
|
||||
from dark_lang import DARK_LANGUAGE_KEY
|
||||
from dark_lang.models import DarkLangConfig
|
||||
from edxmako.shortcuts import render_to_response
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
|
||||
LANGUAGE_INPUT_FIELD = 'preview_lang'
|
||||
|
||||
|
||||
@view_auth_classes()
|
||||
class DarkLangView(View):
|
||||
"""
|
||||
View used when a user is attempting to change the preview language using Darklang.
|
||||
|
||||
Expected Behavior:
|
||||
GET - returns a form for setting/resetting the user's dark language
|
||||
POST - updates or clears the setting to the given dark language
|
||||
"""
|
||||
template_name = 'darklang/preview_lang.html'
|
||||
|
||||
@method_decorator(login_required)
|
||||
def get(self, request):
|
||||
"""
|
||||
Returns the Form for setting/resetting a User's dark language setting
|
||||
|
||||
Arguments:
|
||||
request (Request): The Django Request Object
|
||||
|
||||
Returns:
|
||||
HttpResponse: View containing the form for setting the preview lang
|
||||
"""
|
||||
context = {
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True
|
||||
}
|
||||
return render_to_response(self.template_name, context)
|
||||
|
||||
@method_decorator(login_required)
|
||||
def post(self, request):
|
||||
"""
|
||||
Sets or clears the DarkLang depending on the incoming post data.
|
||||
|
||||
Arguments:
|
||||
request (Request): The Django Request Object
|
||||
|
||||
Returns:
|
||||
HttpResponse: View containing the form for setting the preview lang with the status
|
||||
included in the context
|
||||
"""
|
||||
return self.process_darklang_request(request)
|
||||
|
||||
def process_darklang_request(self, request):
|
||||
"""
|
||||
Proccess the request to Set or clear the DarkLang depending on the incoming request.
|
||||
|
||||
Arguments:
|
||||
request (Request): The Django Request Object
|
||||
|
||||
Returns:
|
||||
HttpResponse: View containing the form for setting the preview lang with the status
|
||||
included in the context
|
||||
"""
|
||||
context = {
|
||||
'disable_courseware_js': True,
|
||||
'uses_pattern_library': True
|
||||
}
|
||||
response = None
|
||||
if not DarkLangConfig.current().enabled:
|
||||
message = _('Preview Language is currently disabled')
|
||||
context.update({'form_submit_message': message})
|
||||
context.update({'success': False})
|
||||
response = render_to_response(self.template_name, context, request=request)
|
||||
|
||||
elif 'set_language' in request.POST:
|
||||
# Set the Preview Language
|
||||
response = self._set_preview_language(request, context)
|
||||
elif 'reset' in request.POST:
|
||||
# Reset and clear the language preference
|
||||
response = self._clear_preview_language(request, context)
|
||||
return response
|
||||
|
||||
def _set_preview_language(self, request, context):
|
||||
"""
|
||||
Set the Preview language
|
||||
|
||||
Arguments:
|
||||
request (Request): The incoming Django Request
|
||||
context dict: The basic context for the Response
|
||||
|
||||
Returns:
|
||||
HttpResponse: View containing the form for setting the preview lang with the status
|
||||
included in the context
|
||||
"""
|
||||
message = None
|
||||
show_refresh_message = False
|
||||
|
||||
preview_lang = request.POST.get(LANGUAGE_INPUT_FIELD, '')
|
||||
if not preview_lang.strip():
|
||||
message = _('Language code not provided')
|
||||
else:
|
||||
# Set the session key to the requested preview lang
|
||||
request.session[LANGUAGE_SESSION_KEY] = preview_lang
|
||||
|
||||
# Make sure that we set the requested preview lang as the dark lang preference for the
|
||||
# user, so that the lang_pref middleware doesn't clobber away the dark lang preview.
|
||||
auth_user = request.user
|
||||
if auth_user:
|
||||
set_user_preference(request.user, DARK_LANGUAGE_KEY, preview_lang)
|
||||
|
||||
message = _('Language set to language code: {preview_language_code}').format(
|
||||
preview_language_code=preview_lang
|
||||
)
|
||||
show_refresh_message = True
|
||||
context.update({'form_submit_message': message})
|
||||
context.update({'success': show_refresh_message})
|
||||
response = render_to_response(self.template_name, context)
|
||||
return response
|
||||
|
||||
def _clear_preview_language(self, request, context):
|
||||
"""
|
||||
Clears the dark language preview
|
||||
|
||||
Arguments:
|
||||
request (Request): The incoming Django Request
|
||||
context dict: The basic context for the Response
|
||||
Returns:
|
||||
HttpResponse: View containing the form for setting the preview lang with the status
|
||||
included in the context
|
||||
"""
|
||||
# delete the session language key (if one is set)
|
||||
if LANGUAGE_SESSION_KEY in request.session:
|
||||
del request.session[LANGUAGE_SESSION_KEY]
|
||||
|
||||
user_pref = ''
|
||||
auth_user = request.user
|
||||
if auth_user:
|
||||
# Reset user's dark lang preference to null
|
||||
delete_user_preference(auth_user, DARK_LANGUAGE_KEY)
|
||||
# Get & set user's preferred language
|
||||
user_pref = get_user_preference(auth_user, LANGUAGE_KEY)
|
||||
if user_pref:
|
||||
request.session[LANGUAGE_SESSION_KEY] = user_pref
|
||||
if user_pref is None:
|
||||
message = _('Language reset to the default language code')
|
||||
else:
|
||||
message = _("Language reset to user's preference: {preview_language_code}").format(
|
||||
preview_language_code=user_pref
|
||||
)
|
||||
context.update({'form_submit_message': message})
|
||||
context.update({'success': True})
|
||||
return render_to_response(self.template_name, context)
|
||||
36
common/templates/darklang/preview_lang_include.html
Normal file
36
common/templates/darklang/preview_lang_include.html
Normal file
@@ -0,0 +1,36 @@
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
<%page expression_filter="h"/>
|
||||
|
||||
<h1>
|
||||
${_("Preview Language Setting")}
|
||||
</h1>
|
||||
<div >
|
||||
<form class="form" action="/update_lang/" method="post">
|
||||
<fieldset class="form-group">
|
||||
<div class="field">
|
||||
<label class="field-label">${_("Language Code")}
|
||||
<input class="field-input input-text" type="text" name="preview_lang"
|
||||
placeholder="${_('For example use en for English')}" />
|
||||
</label>
|
||||
</div>
|
||||
</fieldset>
|
||||
<div class="form-actions">
|
||||
<button class="btn-brand btn-base" type="submit" name="set_language" value="set_language">${_("Submit")}</button>
|
||||
<button class="btn-brand btn-base" type="submit" name="reset" value="reset">${_("Reset")}</button>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="csrfmiddlewaretoken" value="${ csrf_token }"/>
|
||||
</form>
|
||||
|
||||
<br/>
|
||||
% if not form_submit_message is UNDEFINED:
|
||||
<h3 class="alert-title"> ${form_submit_message}</h3>
|
||||
% if success:
|
||||
<p class="alert-copy-with-title">
|
||||
${_("Please refresh the page to see the changes applied.")}
|
||||
</p>
|
||||
% endif
|
||||
% endif
|
||||
</div>
|
||||
@@ -2,29 +2,34 @@
|
||||
Tests i18n in courseware
|
||||
"""
|
||||
import re
|
||||
from nose.plugins.attrib import attr
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.urlresolvers import reverse, NoReverseMatch
|
||||
from django.core.urlresolvers import reverse
|
||||
from django.test import TestCase
|
||||
from django.test.client import Client
|
||||
from django.utils import translation
|
||||
from nose.plugins.attrib import attr
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
|
||||
from dark_lang.models import DarkLangConfig
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference
|
||||
from student.tests.factories import UserFactory, RegistrationFactory, UserProfileFactory
|
||||
from student.tests.factories import UserFactory
|
||||
|
||||
|
||||
class BaseI18nTestCase(TestCase):
|
||||
"""
|
||||
Base utilities for i18n test classes to derive from
|
||||
"""
|
||||
preview_language_url = '/update_lang/'
|
||||
url = reverse('dashboard')
|
||||
site_lang = settings.LANGUAGE_CODE
|
||||
pwd = 'test_password'
|
||||
|
||||
def setUp(self):
|
||||
super(BaseI18nTestCase, self).setUp()
|
||||
self.addCleanup(translation.deactivate)
|
||||
self.client = Client()
|
||||
self.create_user()
|
||||
|
||||
def assert_tag_has_attr(self, content, tag, attname, value):
|
||||
"""Assert that a tag in `content` has a certain value in a certain attribute."""
|
||||
@@ -47,6 +52,22 @@ class BaseI18nTestCase(TestCase):
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
def create_user(self):
|
||||
"""
|
||||
Creates the user log in
|
||||
"""
|
||||
# Create one user and save it to the database
|
||||
email = 'test@edx.org'
|
||||
self.user = UserFactory.build(username='test', email=email, password=self.pwd)
|
||||
self.user.save()
|
||||
|
||||
def user_login(self):
|
||||
"""
|
||||
Log the user in
|
||||
"""
|
||||
# Get the login url & log in our user
|
||||
self.client.login(username=self.user.username, password=self.pwd)
|
||||
|
||||
|
||||
@attr('shard_1')
|
||||
class I18nTestCase(BaseI18nTestCase):
|
||||
@@ -96,35 +117,44 @@ class I18nRegressionTests(BaseI18nTestCase):
|
||||
def test_unreleased_lang_resolution(self):
|
||||
# Regression test; LOC-85
|
||||
self.release_languages('fa')
|
||||
self.user_login()
|
||||
|
||||
# We've released 'fa', AND we have language files for 'fa-ir' but
|
||||
# we want to keep 'fa-ir' as a dark language. Requesting 'fa-ir'
|
||||
# in the http request (NOT with the ?preview-lang query param) should
|
||||
# receive files for 'fa'
|
||||
response = self.client.get('/', HTTP_ACCEPT_LANGUAGE='fa-ir')
|
||||
response = self.client.get(self.url, HTTP_ACCEPT_LANGUAGE='fa-ir')
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", "fa")
|
||||
|
||||
# Now try to access with dark lang
|
||||
response = self.client.get('/?preview-lang=fa-ir')
|
||||
self.client.post(self.preview_language_url, {'preview_lang': 'fa-ir', 'set_language': 'set_language'})
|
||||
response = self.client.get(self.url)
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", "fa-ir")
|
||||
|
||||
def test_preview_lang(self):
|
||||
self.user_login()
|
||||
|
||||
# Regression test; LOC-87
|
||||
self.release_languages('es-419')
|
||||
site_lang = settings.LANGUAGE_CODE
|
||||
# Visit the front page; verify we see site default lang
|
||||
response = self.client.get('/')
|
||||
response = self.client.get(self.url)
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", site_lang)
|
||||
|
||||
# Verify we can switch language using the preview-lang query param
|
||||
response = self.client.get('/?preview-lang=eo')
|
||||
# Set the language
|
||||
self.client.post(self.preview_language_url, {'preview_lang': 'eo', 'set_language': 'set_language'})
|
||||
|
||||
response = self.client.get(self.url)
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", "eo")
|
||||
# We should be able to see released languages using preview-lang, too
|
||||
response = self.client.get('/?preview-lang=es-419')
|
||||
self.client.post(self.preview_language_url, {'preview_lang': 'es-419', 'set_language': 'set_language'})
|
||||
response = self.client.get(self.url)
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", "es-419")
|
||||
|
||||
# Clearing the language should go back to site default
|
||||
response = self.client.get('/?clear-lang')
|
||||
self.client.post(self.preview_language_url, {'reset': 'reset'})
|
||||
response = self.client.get(self.url)
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", site_lang)
|
||||
|
||||
|
||||
@@ -137,32 +167,7 @@ class I18nLangPrefTests(BaseI18nTestCase):
|
||||
"""
|
||||
def setUp(self):
|
||||
super(I18nLangPrefTests, self).setUp()
|
||||
# Create one user and save it to the database
|
||||
email = 'test@edx.org'
|
||||
pwd = 'test_password'
|
||||
self.user = UserFactory.build(username='test', email=email)
|
||||
self.user.set_password(pwd)
|
||||
self.user.save()
|
||||
|
||||
# Create a registration for the user
|
||||
RegistrationFactory(user=self.user)
|
||||
|
||||
# Create a profile for the user
|
||||
UserProfileFactory(user=self.user)
|
||||
|
||||
# Create the test client
|
||||
self.client = Client()
|
||||
|
||||
# Get the login url & log in our user
|
||||
try:
|
||||
login_url = reverse('login_post')
|
||||
except NoReverseMatch:
|
||||
login_url = reverse('login')
|
||||
self.client.post(login_url, {'email': email, 'password': pwd})
|
||||
|
||||
# Url and site lang vars for tests to use
|
||||
self.url = reverse('dashboard')
|
||||
self.site_lang = settings.LANGUAGE_CODE
|
||||
self.user_login()
|
||||
|
||||
def test_lang_preference(self):
|
||||
# Regression test; LOC-87
|
||||
@@ -190,12 +195,16 @@ class I18nLangPrefTests(BaseI18nTestCase):
|
||||
# Set user language preference
|
||||
set_user_preference(self.user, LANGUAGE_KEY, 'ar')
|
||||
# Verify preview-lang takes precedence
|
||||
response = self.client.get('{}?preview-lang=eo'.format(self.url))
|
||||
self.client.post(self.preview_language_url, {'preview_lang': 'eo', 'set_language': 'set_language'})
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", 'eo')
|
||||
# Hitting another page should keep the dark language set.
|
||||
response = self.client.get(reverse('courses'))
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", "eo")
|
||||
|
||||
# Clearing language must set language back to preference language
|
||||
response = self.client.get('{}?clear-lang'.format(self.url))
|
||||
self.client.post(self.preview_language_url, {'reset': 'reset'})
|
||||
response = self.client.get(self.url)
|
||||
|
||||
self.assert_tag_has_attr(response.content, "html", "lang", 'ar')
|
||||
|
||||
15
lms/templates/darklang/preview_lang.html
Normal file
15
lms/templates/darklang/preview_lang.html
Normal file
@@ -0,0 +1,15 @@
|
||||
## Override the default styles_version to the Pattern Library version (version 2)
|
||||
<%! main_css = "style-main-v2" %>
|
||||
<%page expression_filter="h"/>
|
||||
<%!
|
||||
from django.utils.translation import ugettext as _
|
||||
%>
|
||||
|
||||
<%inherit file="/main.html" />
|
||||
<%block name="pagetitle">${_("Preview Language Setting")}</%block>
|
||||
<%block name="nav_skip"></%block>
|
||||
<%block name="bodyclass">pattern-library</%block>
|
||||
|
||||
<%block name="content">
|
||||
<%include file="/darklang/preview_lang_include.html" />
|
||||
</%block>
|
||||
@@ -114,6 +114,7 @@ urlpatterns = (
|
||||
url(r'^course_modes/', include('course_modes.urls')),
|
||||
url(r'^verify_student/', include('verify_student.urls')),
|
||||
|
||||
url(r'^update_lang/', include('dark_lang.urls', namespace='darklang')),
|
||||
# URLs for API access management
|
||||
url(r'^api-admin/', include('openedx.core.djangoapps.api_admin.urls', namespace='api_admin')),
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user