Move dark_lang and lang_pref apps to openedx/core

This commit is contained in:
Andy Armstrong
2016-10-06 14:57:20 -04:00
parent 14d8a46d8c
commit 4d4097e13c
37 changed files with 137 additions and 91 deletions

View File

@@ -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'

View File

@@ -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)

View File

@@ -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

View File

@@ -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,
},
),
]

View File

@@ -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),
]

View File

@@ -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

View File

@@ -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')
)

View File

@@ -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'),
)

View File

@@ -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)

View File

@@ -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'

View File

@@ -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])

View File

@@ -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

View File

@@ -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])

View File

@@ -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)

View File

@@ -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')

View File

@@ -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)

View File

@@ -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)"><%

View File

@@ -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

View File

@@ -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")