diff --git a/cms/envs/common.py b/cms/envs/common.py index 2271e715e9..5036425d25 100644 --- a/cms/envs/common.py +++ b/cms/envs/common.py @@ -1080,7 +1080,7 @@ TIME_ZONE = 'UTC' LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html LANGUAGES_BIDI = lms.envs.common.LANGUAGES_BIDI -LANGUAGE_COOKIE = lms.envs.common.LANGUAGE_COOKIE +LANGUAGE_COOKIE_NAME = lms.envs.common.LANGUAGE_COOKIE_NAME LANGUAGES = lms.envs.common.LANGUAGES LANGUAGE_DICT = dict(LANGUAGES) diff --git a/cms/envs/production.py b/cms/envs/production.py index 077ed0236d..0f9740cc4f 100644 --- a/cms/envs/production.py +++ b/cms/envs/production.py @@ -600,3 +600,5 @@ COURSE_OLX_VALIDATION_IGNORE_LIST = ENV_TOKENS.get( ################# show account activate cta after register ######################## SHOW_ACCOUNT_ACTIVATION_CTA = ENV_TOKENS.get('SHOW_ACCOUNT_ACTIVATION_CTA', SHOW_ACCOUNT_ACTIVATION_CTA) + +LANGUAGE_COOKIE_NAME = ENV_TOKENS.get('LANGUAGE_COOKIE', None) or ENV_TOKENS.get('LANGUAGE_COOKIE_NAME') diff --git a/lms/djangoapps/certificates/tests/test_webview_views.py b/lms/djangoapps/certificates/tests/test_webview_views.py index bbf731194c..1a23eb8522 100644 --- a/lms/djangoapps/certificates/tests/test_webview_views.py +++ b/lms/djangoapps/certificates/tests/test_webview_views.py @@ -647,12 +647,12 @@ class CertificatesViewsTests(CommonCertificatesTestCase, CacheIsolationTestCase) ) user_language = 'fr' - self.client.cookies[settings.LANGUAGE_COOKIE] = user_language + self.client.cookies[settings.LANGUAGE_COOKIE_NAME] = user_language response = self.client.get(test_url) self.assertContains(response, '') user_language = 'ar' - self.client.cookies[settings.LANGUAGE_COOKIE] = user_language + self.client.cookies[settings.LANGUAGE_COOKIE_NAME] = user_language response = self.client.get(test_url) self.assertContains(response, '') diff --git a/lms/djangoapps/courseware/tests/test_masquerade.py b/lms/djangoapps/courseware/tests/test_masquerade.py index 79de1474d8..934fd32ea3 100644 --- a/lms/djangoapps/courseware/tests/test_masquerade.py +++ b/lms/djangoapps/courseware/tests/test_masquerade.py @@ -372,7 +372,7 @@ class TestStaffMasqueradeAsSpecificStudent(StaffMasqueradeTestCase, ProblemSubmi expected_language_code: string indicating a language code """ assert get_user_preference(user, LANGUAGE_KEY) == expected_language_code - assert self.client.cookies[settings.LANGUAGE_COOKIE].value == expected_language_code + assert self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value == expected_language_code @override_waffle_flag(DISABLE_UNIFIED_COURSE_TAB_FLAG, active=True) @patch.dict('django.conf.settings.FEATURES', {'DISABLE_START_DATES': False}) diff --git a/lms/envs/common.py b/lms/envs/common.py index 62b6d71add..167f59b2a9 100644 --- a/lms/envs/common.py +++ b/lms/envs/common.py @@ -730,7 +730,7 @@ FEATURES = { # .. toggle_description: When set to True, language selector will be visible in the footer. # .. toggle_use_cases: open_edx # .. toggle_creation_date: 2017-05-25 - # .. toggle_warnings: LANGUAGE_COOKIE is required to use footer-language-selector, set it if it has not been set. + # .. toggle_warnings: LANGUAGE_COOKIE_NAME is required to use footer-language-selector, set it if it has not been set. # .. toggle_tickets: https://github.com/edx/edx-platform/pull/15133 'SHOW_FOOTER_LANGUAGE_SELECTOR': False, @@ -1708,7 +1708,7 @@ LANGUAGE_CODE = 'en' # http://www.i18nguy.com/unicode/language-identifiers.html # these languages display right to left LANGUAGES_BIDI = ("he", "ar", "fa", "ur", "fa-ir", "rtl") -LANGUAGE_COOKIE = "openedx-language-preference" +LANGUAGE_COOKIE_NAME = "openedx-language-preference" # Sourced from http://www.localeplanet.com/icu/ and wikipedia LANGUAGES = [ diff --git a/lms/envs/production.py b/lms/envs/production.py index 35961e43f6..63bc68d343 100644 --- a/lms/envs/production.py +++ b/lms/envs/production.py @@ -294,6 +294,8 @@ TIME_ZONE = ENV_TOKENS.get('CELERY_TIMEZONE', CELERY_TIMEZONE) # Translation overrides LANGUAGE_DICT = dict(LANGUAGES) +LANGUAGE_COOKIE_NAME = ENV_TOKENS.get('LANGUAGE_COOKIE', None) or ENV_TOKENS.get('LANGUAGE_COOKIE_NAME') + # Additional installed apps for app in ENV_TOKENS.get('ADDL_INSTALLED_APPS', []): INSTALLED_APPS.append(app) diff --git a/lms/templates/widgets/footer-language-selector.html b/lms/templates/widgets/footer-language-selector.html index 932821745f..4182a89e64 100644 --- a/lms/templates/widgets/footer-language-selector.html +++ b/lms/templates/widgets/footer-language-selector.html @@ -1,6 +1,6 @@ ## Language-selection widget for the footer. ## -## Requires settings.LANGUAGE_COOKIE. +## Requires settings.LANGUAGE_COOKIE_NAME. <%page expression_filter="h"/> <%! from babel import Locale @@ -11,9 +11,9 @@ from openedx.core.djangoapps.lang_pref.api import released_languages from openedx.core.djangolib.js_utils import js_escaped_string - # Make sure LANGUAGE_COOKIE is present. - if not settings.LANGUAGE_COOKIE: - raise ValueError('settings.LANGUAGE_COOKIE is required to use footer-language-selector.') + # Make sure LANGUAGE_COOKIE_NAME is present. + if not settings.LANGUAGE_COOKIE_NAME: + raise ValueError('settings.LANGUAGE_COOKIE_NAME is required to use footer-language-selector.') %> <%namespace name='static' file='../static_content.html'/> @@ -61,7 +61,7 @@ }, setLanguageCookie: function(value, callback) { - var cookie = '${settings.LANGUAGE_COOKIE | n, js_escaped_string}=' + value + ';path=/'; + var cookie = '${settings.LANGUAGE_COOKIE_NAME | n, js_escaped_string}=' + value + ';path=/'; <% session_cookie_domain = static.get_value('SESSION_COOKIE_DOMAIN', settings.SESSION_COOKIE_DOMAIN) %> % if session_cookie_domain: diff --git a/openedx/core/djangoapps/dark_lang/middleware.py b/openedx/core/djangoapps/dark_lang/middleware.py index 07181a27df..4663478fff 100644 --- a/openedx/core/djangoapps/dark_lang/middleware.py +++ b/openedx/core/djangoapps/dark_lang/middleware.py @@ -16,6 +16,7 @@ from django.utils.deprecation import MiddlewareMixin from openedx.core.djangoapps.dark_lang import DARK_LANGUAGE_KEY from openedx.core.djangoapps.dark_lang.models import DarkLangConfig +from openedx.core.djangoapps.lang_pref import COOKIE_DURATION from openedx.core.djangoapps.site_configuration.helpers import get_value from openedx.core.djangoapps.user_api.preferences.api import get_user_preference @@ -91,16 +92,31 @@ class DarkLangMiddleware(MiddlewareMixin): return self._clean_accept_headers(request) - self._set_site_or_microsite_language(request) - self._activate_preview_language(request) - def _set_site_or_microsite_language(self, request): + def process_response(self, request, response): + """ + Apply user's dark lang preference as a cookie for future requests. + """ + if DarkLangConfig.current().enabled: + self._set_site_or_microsite_language(request, response) + self._activate_preview_language(request, response) + + return response + + def _set_site_or_microsite_language(self, request, response): """ Apply language specified in site configuration. """ language = get_value('LANGUAGE_CODE', None) if language: request.session[LANGUAGE_SESSION_KEY] = language + response.set_cookie( + settings.LANGUAGE_COOKIE_NAME, + value=language, + domain=settings.SHARED_COOKIE_DOMAIN, + max_age=COOKIE_DURATION, + secure=request.is_secure() + ) def _fuzzy_match(self, lang_code): """Returns a fuzzy match for lang_code""" @@ -142,7 +158,7 @@ class DarkLangMiddleware(MiddlewareMixin): request.META['HTTP_ACCEPT_LANGUAGE'] = new_accept - def _activate_preview_language(self, request): + def _activate_preview_language(self, request, response): """ Check the user's dark language setting in the session and apply it """ @@ -158,3 +174,10 @@ class DarkLangMiddleware(MiddlewareMixin): # Set the session key to the requested preview lang request.session[LANGUAGE_SESSION_KEY] = preview_lang + response.set_cookie( + settings.LANGUAGE_COOKIE_NAME, + value=preview_lang, + domain=settings.SHARED_COOKIE_DOMAIN, + max_age=COOKIE_DURATION, + secure=request.is_secure() + ) diff --git a/openedx/core/djangoapps/dark_lang/tests.py b/openedx/core/djangoapps/dark_lang/tests.py index cfa5780110..8ef114ad78 100644 --- a/openedx/core/djangoapps/dark_lang/tests.py +++ b/openedx/core/djangoapps/dark_lang/tests.py @@ -7,6 +7,7 @@ import unittest from unittest.mock import Mock import ddt +from django.conf import settings from django.http import HttpRequest from django.test.client import Client from django.utils.translation import LANGUAGE_SESSION_KEY @@ -368,3 +369,38 @@ class DarkLangMiddlewareTests(CacheIsolationTestCase): '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') ) + + def test_language_cookie_is_set(self): + site_lang = settings.LANGUAGE_CODE + url = '/dashboard' + + response = self.client.get(url) + assert response.cookies.get(settings.LANGUAGE_COOKIE_NAME).value == '' + assert response['Content-Language'] == site_lang + + # Set preview language + self._post_set_preview_lang("es-419") + + # Check if view has cookies and language set to desired preview language + response = self.client.get(url) + assert settings.LANGUAGE_COOKIE_NAME in response.cookies + assert response.cookies.get(settings.LANGUAGE_COOKIE_NAME).value == 'es-419' + assert response['Content-Language'] == 'es-419' + + # Change preview language + self._post_set_preview_lang("eo") + + # Check if view has cookies and language set to desired preview language + response = self.client.get(url) + assert settings.LANGUAGE_COOKIE_NAME in response.cookies + assert response.cookies.get(settings.LANGUAGE_COOKIE_NAME).value == 'eo' + assert response['Content-Language'] == 'eo' + + # Reset preview language + self._post_clear_preview_lang() + + # Check if view has cookies and language set to default language + response = self.client.get(url) + assert settings.LANGUAGE_COOKIE_NAME in response.cookies + assert response.cookies.get(settings.LANGUAGE_COOKIE_NAME).value == '' + assert response['Content-Language'] == site_lang diff --git a/openedx/core/djangoapps/lang_pref/middleware.py b/openedx/core/djangoapps/lang_pref/middleware.py index e00dca0e0d..8e568baaf7 100644 --- a/openedx/core/djangoapps/lang_pref/middleware.py +++ b/openedx/core/djangoapps/lang_pref/middleware.py @@ -8,6 +8,8 @@ from django.utils.deprecation import MiddlewareMixin from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils.translation.trans_real import parse_accept_lang_header +from openedx.core.djangoapps.dark_lang import DARK_LANGUAGE_KEY +from openedx.core.djangoapps.dark_lang.models import DarkLangConfig from openedx.core.djangoapps.lang_pref import COOKIE_DURATION, LANGUAGE_HEADER, LANGUAGE_KEY from openedx.core.djangoapps.user_api.errors import UserAPIInternalError, UserAPIRequestError from openedx.core.djangoapps.user_api.preferences.api import get_user_preference, set_user_preference @@ -27,9 +29,12 @@ class LanguagePreferenceMiddleware(MiddlewareMixin): If a user's UserPreference contains a language preference, use the user's preference. Save the current language preference cookie as the user's preferred language. """ - cookie_lang = request.COOKIES.get(settings.LANGUAGE_COOKIE, None) + cookie_lang = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME, None) if cookie_lang: if request.user.is_authenticated: + # DarkLangMiddleware will take care of this so don't change anything + if DarkLangConfig.current().enabled and get_user_preference(request.user, DARK_LANGUAGE_KEY): + return set_user_preference(request.user, LANGUAGE_KEY, cookie_lang) else: request._anonymous_user_cookie_lang = cookie_lang # lint-amnesty, pylint: disable=protected-access @@ -58,6 +63,11 @@ class LanguagePreferenceMiddleware(MiddlewareMixin): current_user = getattr(request.user, 'real_user', request.user) if current_user and current_user.is_authenticated: + + # DarkLangMiddleware has already set this cookie + if DarkLangConfig.current().enabled and get_user_preference(current_user, DARK_LANGUAGE_KEY): + return response + anonymous_cookie_lang = getattr(request, '_anonymous_user_cookie_lang', None) if anonymous_cookie_lang: user_pref = anonymous_cookie_lang @@ -70,19 +80,19 @@ class LanguagePreferenceMiddleware(MiddlewareMixin): # If we can't find the user preferences, then don't modify the cookie pass - # If set, set the user_pref in the LANGUAGE_COOKIE + # If set, set the user_pref in the LANGUAGE_COOKIE_NAME if user_pref and not is_request_from_mobile_app(request): response.set_cookie( - settings.LANGUAGE_COOKIE, + settings.LANGUAGE_COOKIE_NAME, value=user_pref, - domain=settings.SESSION_COOKIE_DOMAIN, + domain=settings.SHARED_COOKIE_DOMAIN, max_age=COOKIE_DURATION, secure=request.is_secure() ) else: response.delete_cookie( - settings.LANGUAGE_COOKIE, - domain=settings.SESSION_COOKIE_DOMAIN + settings.LANGUAGE_COOKIE_NAME, + domain=settings.SHARED_COOKIE_DOMAIN ) return response diff --git a/openedx/core/djangoapps/lang_pref/tests/test_middleware.py b/openedx/core/djangoapps/lang_pref/tests/test_middleware.py index 3d746b2962..1a071679d1 100644 --- a/openedx/core/djangoapps/lang_pref/tests/test_middleware.py +++ b/openedx/core/djangoapps/lang_pref/tests/test_middleware.py @@ -57,7 +57,7 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): @ddt.data(None, 'es', 'en') def test_preference_setting_changes_cookie(self, lang_pref_out): """ - Test that the LANGUAGE_COOKIE is always set to the user's current language preferences + Test that the LANGUAGE_COOKIE_NAME is always set to the user's current language preferences at the end of the request, with an expiry that's the same as the users current session cookie. """ if lang_pref_out: @@ -70,7 +70,7 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): if lang_pref_out: response.set_cookie.assert_called_with( - settings.LANGUAGE_COOKIE, + settings.LANGUAGE_COOKIE_NAME, value=lang_pref_out, domain=settings.SESSION_COOKIE_DOMAIN, max_age=COOKIE_DURATION, @@ -78,20 +78,20 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): ) else: response.delete_cookie.assert_called_with( - settings.LANGUAGE_COOKIE, + settings.LANGUAGE_COOKIE_NAME, domain=settings.SESSION_COOKIE_DOMAIN, ) assert LANGUAGE_SESSION_KEY not in self.request.session @ddt.data(*itertools.product( - (None, 'eo', 'es'), # LANGUAGE_COOKIE + (None, 'eo', 'es'), # LANGUAGE_COOKIE_NAME (None, 'es', 'en'), # Language Preference In )) @ddt.unpack @mock.patch('openedx.core.djangoapps.lang_pref.middleware.set_user_preference') def test_preference_cookie_changes_setting(self, lang_cookie, lang_pref_in, mock_set_user_preference): - self.request.COOKIES[settings.LANGUAGE_COOKIE] = lang_cookie + self.request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = lang_cookie if lang_pref_in: set_user_preference(self.user, LANGUAGE_KEY, lang_pref_in) @@ -109,7 +109,7 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): (logged_in, ) + test_def for logged_in in (True, False) for test_def in [ - # (LANGUAGE_COOKIE, LANGUAGE_SESSION_KEY, Accept-Language In, + # (LANGUAGE_COOKIE_NAME, LANGUAGE_SESSION_KEY, Accept-Language In, # Accept-Language Out, Session Lang Out) (None, None, None, None, None), (None, 'eo', None, None, 'eo'), @@ -132,7 +132,7 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): if not logged_in: self.request.user = self.anonymous_user if lang_cookie: - self.request.COOKIES[settings.LANGUAGE_COOKIE] = lang_cookie + self.request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = lang_cookie if lang_session_in: self.request.session[LANGUAGE_SESSION_KEY] = lang_session_in if accept_lang_in: @@ -159,16 +159,16 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): @ddt.data(None, 'es', 'en') def test_logout_preserves_cookie(self, lang_cookie): if lang_cookie: - self.client.cookies[settings.LANGUAGE_COOKIE] = lang_cookie - elif settings.LANGUAGE_COOKIE in self.client.cookies: - del self.client.cookies[settings.LANGUAGE_COOKIE] + self.client.cookies[settings.LANGUAGE_COOKIE_NAME] = lang_cookie + elif settings.LANGUAGE_COOKIE_NAME in self.client.cookies: + del self.client.cookies[settings.LANGUAGE_COOKIE_NAME] # Use an actual call to the logout endpoint, because the logout function # explicitly clears all cookies self.client.get(reverse('logout')) if lang_cookie: - assert self.client.cookies[settings.LANGUAGE_COOKIE].value == lang_cookie + assert self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value == lang_cookie else: - assert settings.LANGUAGE_COOKIE not in self.client.cookies + assert settings.LANGUAGE_COOKIE_NAME not in self.client.cookies @ddt.data( (None, None), @@ -179,9 +179,9 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): @ddt.unpack def test_login_captures_lang_pref(self, lang_cookie, expected_lang): if lang_cookie: - self.client.cookies[settings.LANGUAGE_COOKIE] = lang_cookie - elif settings.LANGUAGE_COOKIE in self.client.cookies: - del self.client.cookies[settings.LANGUAGE_COOKIE] + self.client.cookies[settings.LANGUAGE_COOKIE_NAME] = lang_cookie + elif settings.LANGUAGE_COOKIE_NAME in self.client.cookies: + del self.client.cookies[settings.LANGUAGE_COOKIE_NAME] # Use an actual call to the login endpoint, to validate that the middleware # stack does the right thing @@ -199,11 +199,11 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): if lang_cookie: assert response['Content-Language'] == expected_lang assert get_user_preference(self.user, LANGUAGE_KEY) == lang_cookie - assert self.client.cookies[settings.LANGUAGE_COOKIE].value == lang_cookie + assert self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value == lang_cookie else: assert response['Content-Language'] == 'en' assert get_user_preference(self.user, LANGUAGE_KEY) is None - assert self.client.cookies[settings.LANGUAGE_COOKIE].value == '' + assert self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value == '' def test_process_response_no_user_noop(self): del self.request.user @@ -215,7 +215,7 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): assert response.mock_calls == [] def test_preference_update_noop(self): - self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'es' + self.request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = 'es' # No preference yet, should write to the database @@ -242,7 +242,7 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): # Cookie changed, should write to the database again - self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'en' + self.request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = 'en' self.middleware.process_request(self.request) assert get_user_preference(self.user, LANGUAGE_KEY) == 'en' diff --git a/openedx/core/djangoapps/lang_pref/views.py b/openedx/core/djangoapps/lang_pref/views.py index 28329fbf25..af753701f4 100644 --- a/openedx/core/djangoapps/lang_pref/views.py +++ b/openedx/core/djangoapps/lang_pref/views.py @@ -25,7 +25,7 @@ def update_session_language(request): if request.session.get(LANGUAGE_SESSION_KEY, None) != language: request.session[LANGUAGE_SESSION_KEY] = str(language) response.set_cookie( - settings.LANGUAGE_COOKIE, + settings.LANGUAGE_COOKIE_NAME, language, domain=settings.SHARED_COOKIE_DOMAIN, max_age=COOKIE_DURATION,