Move dark_lang and lang_pref apps to openedx/core
This commit is contained in:
@@ -1,22 +0,0 @@
|
||||
"""
|
||||
Language Translation Dark Launching
|
||||
===================================
|
||||
|
||||
This app adds the ability to launch language translations that
|
||||
are only accessible through the use of a specific query parameter
|
||||
(and are not activated by browser settings).
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Add the ``DarkLangMiddleware`` to your list of ``MIDDLEWARE_CLASSES``.
|
||||
It must come after the ``SessionMiddleware``, and before the ``LocaleMiddleware``.
|
||||
|
||||
Run migrations to install the configuration table.
|
||||
|
||||
Use the admin site to add a new ``DarkLangConfig`` that is enabled, and lists the
|
||||
languages that should be released.
|
||||
"""
|
||||
|
||||
# this is the UserPreference key for the currently-active dark language, if any
|
||||
DARK_LANGUAGE_KEY = 'dark-lang'
|
||||
@@ -1,10 +0,0 @@
|
||||
"""
|
||||
Admin site bindings for dark_lang
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
|
||||
from config_models.admin import ConfigurationModelAdmin
|
||||
from dark_lang.models import DarkLangConfig
|
||||
|
||||
admin.site.register(DarkLangConfig, ConfigurationModelAdmin)
|
||||
@@ -1,132 +0,0 @@
|
||||
"""
|
||||
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 Languages on the Dark
|
||||
Language setting page.
|
||||
|
||||
This middleware must be placed before the LocaleMiddleware, but after
|
||||
the SessionMiddleware.
|
||||
"""
|
||||
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 (
|
||||
get_user_preference
|
||||
)
|
||||
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):
|
||||
"""
|
||||
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.
|
||||
Although majority of browsers still use the old language codes, some new browsers
|
||||
such as IE11 in Windows 8.1 start to use the new ones, which makes the current
|
||||
chinese translations of edX don't work properly under these browsers.
|
||||
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:
|
||||
lang = CHINESE_LANGUAGE_CODE_MAP.get(lang.lower(), lang)
|
||||
django_langs.append((lang, priority))
|
||||
|
||||
return django_langs
|
||||
|
||||
|
||||
class DarkLangMiddleware(object):
|
||||
"""
|
||||
Middleware for dark-launching languages.
|
||||
|
||||
This is configured by creating ``DarkLangConfig`` rows in the database,
|
||||
using the django admin site.
|
||||
"""
|
||||
@property
|
||||
def released_langs(self):
|
||||
"""
|
||||
Current list of released languages
|
||||
"""
|
||||
language_options = DarkLangConfig.current().released_languages_list
|
||||
if settings.LANGUAGE_CODE not in language_options:
|
||||
language_options.append(settings.LANGUAGE_CODE)
|
||||
return language_options
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
Prevent user from requesting un-released languages except by using the preview-lang query string.
|
||||
"""
|
||||
if not DarkLangConfig.current().enabled:
|
||||
return
|
||||
|
||||
self._clean_accept_headers(request)
|
||||
self._activate_preview_language(request)
|
||||
|
||||
def _fuzzy_match(self, lang_code):
|
||||
"""Returns a fuzzy match for lang_code"""
|
||||
match = None
|
||||
if lang_code in self.released_langs:
|
||||
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):
|
||||
"""
|
||||
Remove any language that is not either in ``self.released_langs`` or
|
||||
a territory of one of those languages.
|
||||
"""
|
||||
accept = request.META.get('HTTP_ACCEPT_LANGUAGE', None)
|
||||
if accept is None or accept == '*':
|
||||
return
|
||||
|
||||
new_accept = []
|
||||
for lang, priority in _dark_parse_accept_lang_header(accept):
|
||||
fuzzy_code = self._fuzzy_match(lang.lower())
|
||||
if fuzzy_code:
|
||||
# Formats lang and priority into a valid accept header fragment.
|
||||
new_accept.append("{};q={}".format(fuzzy_code, priority))
|
||||
|
||||
new_accept = ", ".join(new_accept)
|
||||
|
||||
request.META['HTTP_ACCEPT_LANGUAGE'] = new_accept
|
||||
|
||||
def _activate_preview_language(self, request):
|
||||
"""
|
||||
Check the user's dark language setting in the session and apply it
|
||||
"""
|
||||
auth_user = request.user.is_authenticated()
|
||||
preview_lang = None
|
||||
if auth_user:
|
||||
# Get the request user's dark lang preference
|
||||
preview_lang = get_user_preference(request.user, DARK_LANGUAGE_KEY)
|
||||
|
||||
# User doesn't have a dark lang preference, so just return
|
||||
if not preview_lang:
|
||||
return
|
||||
|
||||
# Set the session key to the requested preview lang
|
||||
request.session[LANGUAGE_SESSION_KEY] = preview_lang
|
||||
@@ -1,30 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
import django.db.models.deletion
|
||||
from django.conf import settings
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='DarkLangConfig',
|
||||
fields=[
|
||||
('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
|
||||
('change_date', models.DateTimeField(auto_now_add=True, verbose_name='Change date')),
|
||||
('enabled', models.BooleanField(default=False, verbose_name='Enabled')),
|
||||
('released_languages', models.TextField(help_text=b'A comma-separated list of language codes to release to the public.', blank=True)),
|
||||
('changed_by', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, editable=False, to=settings.AUTH_USER_MODEL, null=True, verbose_name='Changed by')),
|
||||
],
|
||||
options={
|
||||
'ordering': ('-change_date',),
|
||||
'abstract': False,
|
||||
},
|
||||
),
|
||||
]
|
||||
@@ -1,32 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# Converted from the original South migration 0002_enable_on_install.py
|
||||
#
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def create_dark_lang_config(apps, schema_editor):
|
||||
"""
|
||||
Enable DarkLang by default when it is installed, to prevent accidental
|
||||
release of testing languages.
|
||||
"""
|
||||
DarkLangConfig = apps.get_model("dark_lang", "DarkLangConfig")
|
||||
|
||||
objects = DarkLangConfig.objects
|
||||
if not objects.exists():
|
||||
objects.create(enabled=True)
|
||||
|
||||
def remove_dark_lang_config(apps, schema_editor):
|
||||
"""Write your backwards methods here."""
|
||||
raise RuntimeError("Cannot reverse this migration.")
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('dark_lang', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(create_dark_lang_config, remove_dark_lang_config),
|
||||
]
|
||||
@@ -1,31 +0,0 @@
|
||||
"""
|
||||
Models for the dark-launching languages
|
||||
"""
|
||||
from django.db import models
|
||||
|
||||
from config_models.models import ConfigurationModel
|
||||
|
||||
|
||||
class DarkLangConfig(ConfigurationModel):
|
||||
"""
|
||||
Configuration for the dark_lang django app
|
||||
"""
|
||||
released_languages = models.TextField(
|
||||
blank=True,
|
||||
help_text="A comma-separated list of language codes to release to the public."
|
||||
)
|
||||
|
||||
@property
|
||||
def released_languages_list(self):
|
||||
"""
|
||||
``released_languages`` as a list of language codes.
|
||||
|
||||
Example: ['it', 'de-at', 'es', 'pt-br']
|
||||
"""
|
||||
if not self.released_languages.strip():
|
||||
return []
|
||||
|
||||
languages = [lang.lower().strip() for lang in self.released_languages.split(',')]
|
||||
# Put in alphabetical order
|
||||
languages.sort()
|
||||
return languages
|
||||
@@ -1,361 +0,0 @@
|
||||
"""
|
||||
Tests of DarkLangMiddleware
|
||||
"""
|
||||
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
|
||||
|
||||
from dark_lang.middleware import DarkLangMiddleware
|
||||
from dark_lang.models import DarkLangConfig
|
||||
from student.tests.factories import UserFactory
|
||||
UNSET = object()
|
||||
|
||||
|
||||
def set_if_set(dct, key, value):
|
||||
"""
|
||||
Sets ``key`` in ``dct`` to ``value``
|
||||
unless ``value`` is ``UNSET``
|
||||
"""
|
||||
if value is not UNSET:
|
||||
dct[key] = value
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class DarkLangMiddlewareTests(TestCase):
|
||||
"""
|
||||
Tests of DarkLangMiddleware
|
||||
"""
|
||||
def setUp(self):
|
||||
super(DarkLangMiddlewareTests, self).setUp()
|
||||
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_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']
|
||||
"""
|
||||
session = {}
|
||||
set_if_set(session, LANGUAGE_SESSION_KEY, language_session_key)
|
||||
|
||||
meta = {}
|
||||
set_if_set(meta, 'HTTP_ACCEPT_LANGUAGE', accept)
|
||||
|
||||
request = Mock(
|
||||
spec=HttpRequest,
|
||||
session=session,
|
||||
META=meta,
|
||||
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
|
||||
|
||||
def assertAcceptEquals(self, value, request):
|
||||
"""
|
||||
Assert that the HTML_ACCEPT_LANGUAGE header in request
|
||||
is equal to value
|
||||
"""
|
||||
self.assertEquals(
|
||||
value,
|
||||
request.META.get('HTTP_ACCEPT_LANGUAGE', UNSET)
|
||||
)
|
||||
|
||||
def test_empty_accept(self):
|
||||
self.assertAcceptEquals(UNSET, self.process_middleware_request())
|
||||
|
||||
def test_wildcard_accept(self):
|
||||
self.assertAcceptEquals('*', self.process_middleware_request(accept='*'))
|
||||
|
||||
def test_malformed_accept(self):
|
||||
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_middleware_request(accept='rel;q=1.0')
|
||||
)
|
||||
|
||||
def test_unreleased_accept(self):
|
||||
self.assertAcceptEquals(
|
||||
'rel;q=1.0',
|
||||
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_middleware_request(accept='en;q=1.0, rel;q=0.8, unrel;q=0.5')
|
||||
)
|
||||
|
||||
def test_accept_multiple_released_langs(self):
|
||||
DarkLangConfig(
|
||||
released_languages=('rel, unrel'),
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'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_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_middleware_request(accept='notrel;q=0.3, rel;q=1.0, unrel;q=0.5')
|
||||
)
|
||||
|
||||
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;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_middleware_request(accept='rel-TER;q=1.0, REL;q=0.5')
|
||||
)
|
||||
|
||||
DarkLangConfig(
|
||||
released_languages='REL-TER',
|
||||
changed_by=self.user,
|
||||
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=0.5',
|
||||
self.process_middleware_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_middleware_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_middleware_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_middleware_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_middleware_request(accept='{};q=1.0, pt;q=0.5'.format(latin_america_code))
|
||||
)
|
||||
|
||||
def assert_session_lang_equals(self, value, session):
|
||||
"""
|
||||
Assert that the LANGUAGE_SESSION_KEY set in session is equal to value
|
||||
"""
|
||||
self.assertEquals(
|
||||
value,
|
||||
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._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.client.session
|
||||
)
|
||||
|
||||
# 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.client.session
|
||||
)
|
||||
|
||||
def test_preview_lang_with_dark_language(self):
|
||||
self._post_set_preview_lang('unrel')
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
'unrel',
|
||||
self.client.session
|
||||
)
|
||||
|
||||
# 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.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):
|
||||
# Clear a language when no language was set
|
||||
self._post_clear_preview_lang()
|
||||
self.client.get('/home')
|
||||
self.assert_session_lang_equals(
|
||||
UNSET,
|
||||
self.client.session
|
||||
)
|
||||
|
||||
# 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.client.session
|
||||
)
|
||||
|
||||
def test_disabled(self):
|
||||
DarkLangConfig(enabled=False, changed_by=self.user).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'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')
|
||||
)
|
||||
|
||||
# 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.client.session
|
||||
)
|
||||
|
||||
# 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.client.session
|
||||
)
|
||||
|
||||
def test_accept_chinese_language_codes(self):
|
||||
DarkLangConfig(
|
||||
released_languages=('zh-cn, zh-hk, zh-tw'),
|
||||
changed_by=self.user,
|
||||
enabled=True
|
||||
).save()
|
||||
|
||||
self.assertAcceptEquals(
|
||||
'zh-cn;q=1.0, zh-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')
|
||||
)
|
||||
@@ -1,12 +0,0 @@
|
||||
"""
|
||||
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'),
|
||||
)
|
||||
@@ -1,163 +0,0 @@
|
||||
"""
|
||||
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)
|
||||
@@ -1,6 +0,0 @@
|
||||
"""
|
||||
Useful information for setting the language preference
|
||||
"""
|
||||
|
||||
# this is the UserPreference key for the user's preferred language
|
||||
LANGUAGE_KEY = 'pref-lang'
|
||||
@@ -1,61 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Python API for language and translation management. """
|
||||
|
||||
from collections import namedtuple
|
||||
|
||||
from django.conf import settings
|
||||
from django.utils.translation import ugettext as _
|
||||
from dark_lang.models import DarkLangConfig
|
||||
|
||||
|
||||
# Named tuples can be referenced using object-like variable
|
||||
# deferencing, making the use of tuples more readable by
|
||||
# eliminating the need to see the context of the tuple packing.
|
||||
Language = namedtuple('Language', 'code name')
|
||||
|
||||
|
||||
def released_languages():
|
||||
"""Retrieve the list of released languages.
|
||||
|
||||
Constructs a list of Language tuples by intersecting the
|
||||
list of valid language tuples with the list of released
|
||||
language codes.
|
||||
|
||||
Returns:
|
||||
list of Language: Languages in which full translations are available.
|
||||
|
||||
Example:
|
||||
|
||||
>>> print released_languages()
|
||||
[Language(code='en', name=u'English'), Language(code='fr', name=u'Français')]
|
||||
|
||||
"""
|
||||
released_language_codes = DarkLangConfig.current().released_languages_list
|
||||
default_language_code = settings.LANGUAGE_CODE
|
||||
|
||||
if default_language_code not in released_language_codes:
|
||||
released_language_codes.append(default_language_code)
|
||||
released_language_codes.sort()
|
||||
|
||||
# Intersect the list of valid language tuples with the list
|
||||
# of release language codes
|
||||
released_languages = [
|
||||
Language(tuple[0], tuple[1])
|
||||
for tuple in settings.LANGUAGES
|
||||
if tuple[0] in released_language_codes
|
||||
]
|
||||
|
||||
return released_languages
|
||||
|
||||
|
||||
def all_languages():
|
||||
"""Retrieve the list of all languages, translated and sorted.
|
||||
|
||||
Returns:
|
||||
list of (language code (str), language name (str)): the language names
|
||||
are translated in the current activated language and the results sorted
|
||||
alphabetically.
|
||||
|
||||
"""
|
||||
languages = [(lang[0], _(lang[1])) for lang in settings.ALL_LANGUAGES] # pylint: disable=translation-of-non-string
|
||||
return sorted(languages, key=lambda lang: lang[1])
|
||||
@@ -1,46 +0,0 @@
|
||||
"""
|
||||
Middleware for Language Preferences
|
||||
"""
|
||||
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference, delete_user_preference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from django.utils.translation.trans_real import parse_accept_lang_header
|
||||
from lang_pref.api import released_languages
|
||||
|
||||
|
||||
class LanguagePreferenceMiddleware(object):
|
||||
"""
|
||||
Middleware for user preferences.
|
||||
|
||||
Ensures that, once set, a user's preferences are reflected in the page
|
||||
whenever they are logged in.
|
||||
"""
|
||||
|
||||
def process_request(self, request):
|
||||
"""
|
||||
If a user's UserPreference contains a language preference, use the user's preference.
|
||||
"""
|
||||
languages = released_languages()
|
||||
system_released_languages = [seq[0] for seq in languages]
|
||||
|
||||
# If the user is logged in, check for their language preference
|
||||
if request.user.is_authenticated():
|
||||
# Get the user's language preference
|
||||
user_pref = get_user_preference(request.user, LANGUAGE_KEY)
|
||||
# Set it to the LANGUAGE_SESSION_KEY (Django-specific session setting governing language pref)
|
||||
if user_pref:
|
||||
if user_pref in system_released_languages:
|
||||
request.session[LANGUAGE_SESSION_KEY] = user_pref
|
||||
else:
|
||||
delete_user_preference(request.user, LANGUAGE_KEY)
|
||||
else:
|
||||
preferred_language = request.META.get('HTTP_ACCEPT_LANGUAGE', '')
|
||||
lang_headers = [seq[0] for seq in parse_accept_lang_header(preferred_language)]
|
||||
|
||||
# Setting the session language to the browser language, if it is supported.
|
||||
for browser_lang in lang_headers:
|
||||
if browser_lang in system_released_languages:
|
||||
if request.session.get(LANGUAGE_SESSION_KEY, None) is None:
|
||||
request.session[LANGUAGE_SESSION_KEY] = unicode(browser_lang)
|
||||
break
|
||||
@@ -1,26 +0,0 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
""" Tests for the language API. """
|
||||
|
||||
from django.test import TestCase
|
||||
from django.test.utils import override_settings
|
||||
from django.utils import translation
|
||||
from lang_pref import api as language_api
|
||||
|
||||
|
||||
class LanguageApiTest(TestCase):
|
||||
|
||||
def test_released_languages(self):
|
||||
released_languages = language_api.released_languages()
|
||||
self.assertGreaterEqual(len(released_languages), 1)
|
||||
|
||||
@override_settings(ALL_LANGUAGES=[[u"cs", u"Czech"], [u"nl", u"Dutch"]])
|
||||
def test_all_languages(self):
|
||||
with translation.override('fr'):
|
||||
all_languages = language_api.all_languages()
|
||||
|
||||
self.assertEqual(2, len(all_languages))
|
||||
self.assertLess(all_languages[0][1], all_languages[1][1])
|
||||
self.assertEqual("nl", all_languages[0][0])
|
||||
self.assertEqual("cs", all_languages[1][0])
|
||||
self.assertEqual(u"Hollandais", all_languages[0][1])
|
||||
self.assertEqual(u"Tchèque", all_languages[1][1])
|
||||
@@ -1,87 +0,0 @@
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
|
||||
from lang_pref.middleware import LanguagePreferenceMiddleware
|
||||
from openedx.core.djangoapps.user_api.preferences.api import set_user_preference, get_user_preference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from student.tests.factories import UserFactory
|
||||
from student.tests.factories import AnonymousUserFactory
|
||||
import mock
|
||||
|
||||
|
||||
class TestUserPreferenceMiddleware(TestCase):
|
||||
"""
|
||||
Tests to make sure user preferences are getting properly set in the middleware
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestUserPreferenceMiddleware, self).setUp()
|
||||
self.middleware = LanguagePreferenceMiddleware()
|
||||
self.session_middleware = SessionMiddleware()
|
||||
self.user = UserFactory.create()
|
||||
self.anonymous_user = AnonymousUserFactory()
|
||||
self.request = RequestFactory().get('/somewhere')
|
||||
self.request.user = self.user
|
||||
self.request.META['HTTP_ACCEPT_LANGUAGE'] = 'ar;q=1.0' # pylint: disable=no-member
|
||||
self.session_middleware.process_request(self.request)
|
||||
|
||||
def test_no_language_set_in_session_or_prefs(self):
|
||||
# nothing set in the session or the prefs
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertNotIn(LANGUAGE_SESSION_KEY, self.request.session)
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages', mock.Mock(return_value=[('eo', 'esperanto')]))
|
||||
def test_language_in_user_prefs(self):
|
||||
# language set in the user preferences and not the session
|
||||
set_user_preference(self.user, LANGUAGE_KEY, 'eo')
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertEquals(self.request.session[LANGUAGE_SESSION_KEY], 'eo')
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages', mock.Mock(
|
||||
return_value=[('en', 'english'), ('eo', 'esperanto')]
|
||||
))
|
||||
def test_language_in_session(self):
|
||||
# language set in both the user preferences and session,
|
||||
# preference should get precedence. The session will hold the last value,
|
||||
# which is probably the user's last preference. Look up the updated preference.
|
||||
|
||||
# Dark lang middleware should run after this middleware, so it can
|
||||
# set a session language as an override of the user's preference.
|
||||
self.request.session[LANGUAGE_SESSION_KEY] = 'en'
|
||||
set_user_preference(self.user, LANGUAGE_KEY, 'eo')
|
||||
self.middleware.process_request(self.request)
|
||||
|
||||
self.assertEquals(self.request.session[LANGUAGE_SESSION_KEY], 'eo')
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages',
|
||||
mock.Mock(return_value=[('eo', 'dummy Esperanto'), ('ar', 'arabic')]))
|
||||
def test_supported_browser_language_in_session(self):
|
||||
"""
|
||||
test: browser language should be set in user session if it is supported by system for unauthenticated user.
|
||||
"""
|
||||
self.request.user = self.anonymous_user
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertEqual(self.request.session[LANGUAGE_SESSION_KEY], 'ar') # pylint: disable=no-member
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages', mock.Mock(return_value=[('en', 'english')]))
|
||||
def test_browser_language_not_be_in_session(self):
|
||||
"""
|
||||
test: browser language should not be set in user session if it is not supported by system.
|
||||
"""
|
||||
self.request.user = self.anonymous_user
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertNotEqual(self.request.session.get(LANGUAGE_SESSION_KEY), 'ar') # pylint: disable=no-member
|
||||
|
||||
@mock.patch('lang_pref.middleware.released_languages', mock.Mock(
|
||||
return_value=[('en', 'english'), ('ar', 'arabic')]
|
||||
))
|
||||
def test_delete_user_lang_preference_not_supported_by_system(self):
|
||||
"""
|
||||
test: user preferred language has been removed from user preferences model if it is not supported by system
|
||||
for authenticated users.
|
||||
"""
|
||||
set_user_preference(self.user, LANGUAGE_KEY, 'eo')
|
||||
self.middleware.process_request(self.request)
|
||||
self.assertEqual(get_user_preference(self.request.user, LANGUAGE_KEY), None)
|
||||
@@ -1,38 +0,0 @@
|
||||
"""
|
||||
Tests: lang pref views
|
||||
"""
|
||||
import json
|
||||
from django.test import TestCase
|
||||
from django.test.client import RequestFactory
|
||||
from django.core.urlresolvers import reverse
|
||||
from student.tests.factories import UserFactory
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from django.contrib.sessions.middleware import SessionMiddleware
|
||||
from django.utils.translation import get_language
|
||||
|
||||
|
||||
class TestLangPrefView(TestCase):
|
||||
"""
|
||||
Language preference view tests.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
super(TestLangPrefView, self).setUp()
|
||||
self.session_middleware = SessionMiddleware()
|
||||
self.user = UserFactory.create()
|
||||
self.request = RequestFactory().get('/somewhere')
|
||||
self.request.user = self.user
|
||||
self.session_middleware.process_request(self.request)
|
||||
|
||||
def test_language_session_update(self):
|
||||
# test language session updating correctly.
|
||||
self.request.session[LANGUAGE_SESSION_KEY] = 'ar' # pylint: disable=no-member
|
||||
response = self.client.patch(reverse("session_language"), json.dumps({'pref-lang': 'eo'}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.client.get('/')
|
||||
self.assertEquals(get_language(), 'eo')
|
||||
|
||||
response = self.client.patch(reverse("session_language"), json.dumps({'pref-lang': 'en'}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.client.get('/')
|
||||
self.assertEquals(get_language(), 'en')
|
||||
@@ -1,22 +0,0 @@
|
||||
"""
|
||||
Language Preference Views
|
||||
"""
|
||||
import json
|
||||
from django.conf import settings
|
||||
from django.views.decorators.csrf import ensure_csrf_cookie
|
||||
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from django.http import HttpResponse
|
||||
|
||||
|
||||
@ensure_csrf_cookie
|
||||
def update_session_language(request):
|
||||
"""
|
||||
Update the language session key.
|
||||
"""
|
||||
if request.method == 'PATCH':
|
||||
data = json.loads(request.body)
|
||||
language = data.get(LANGUAGE_KEY, settings.LANGUAGE_CODE)
|
||||
if request.session.get(LANGUAGE_SESSION_KEY, None) != language:
|
||||
request.session[LANGUAGE_SESSION_KEY] = unicode(language)
|
||||
return HttpResponse(200)
|
||||
@@ -17,7 +17,7 @@ from openedx.core.djangoapps.theming.helpers import (
|
||||
is_request_in_themed_site,
|
||||
)
|
||||
from certificates.api import get_asset_url_by_slug
|
||||
from lang_pref.api import released_languages
|
||||
from openedx.core.djangoapps.lang_pref.api import released_languages
|
||||
%>
|
||||
|
||||
<%def name="marketing_link(name)"><%
|
||||
|
||||
@@ -15,7 +15,7 @@ import mock
|
||||
import pytz
|
||||
|
||||
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
|
||||
from notification_prefs import NOTIFICATION_PREF_KEY
|
||||
from openedx.core.djangoapps.external_auth.models import ExternalAuthMap
|
||||
import student
|
||||
|
||||
@@ -85,8 +85,6 @@ from openedx.core.djangoapps.external_auth.login_and_register import (
|
||||
register as external_auth_register
|
||||
)
|
||||
|
||||
from lang_pref import LANGUAGE_KEY
|
||||
|
||||
import track.views
|
||||
|
||||
import dogstats_wrapper as dog_stats_api
|
||||
@@ -120,11 +118,12 @@ from eventtracking import tracker
|
||||
from notification_prefs.views import enable_notifications
|
||||
|
||||
from openedx.core.djangoapps.credit.email_utils import get_credit_provider_display_names, make_providers_strings
|
||||
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY
|
||||
from openedx.core.djangoapps.programs import utils as programs_utils
|
||||
from openedx.core.djangoapps.programs.models import ProgramsApiConfig
|
||||
from openedx.core.djangoapps.site_configuration import helpers as configuration_helpers
|
||||
from openedx.core.djangoapps.theming import helpers as theming_helpers
|
||||
from openedx.core.djangoapps.user_api.preferences import api as preferences_api
|
||||
|
||||
|
||||
log = logging.getLogger("edx.student")
|
||||
|
||||
Reference in New Issue
Block a user