fix: use cookies for storing language instead of session

- Fixed LANGUAGE_COOKIE settings name to LANGUAGE_COOKIE_NAME beacuse later is recognised by django
- Added test to verify cookies use in dark lang middleware
- Fixing Django 3.0 tests
This commit is contained in:
Soban Javed
2021-09-20 17:24:38 +05:00
parent 6ea900cba6
commit 44ddbdf925
12 changed files with 114 additions and 41 deletions

View File

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

View File

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

View File

@@ -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, '<html class="no-js" lang="fr">')
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, '<html class="no-js" lang="ar">')

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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