diff --git a/cms/urls.py b/cms/urls.py index 5631c3aa95..74261de8f3 100644 --- a/cms/urls.py +++ b/cms/urls.py @@ -76,8 +76,8 @@ urlpatterns = oauth2_urlpatterns + [ path('api/user/', include('openedx.core.djangoapps.user_api.urls')), # Update session view - path('lang_pref/session_language', openedx.core.djangoapps.lang_pref.views.update_session_language, - name='session_language' + path('lang_pref/cookie_language', openedx.core.djangoapps.lang_pref.views.update_cookie_language, + name='cookie_language' ), # Darklang View to change the preview language (or dark language) diff --git a/lms/urls.py b/lms/urls.py index 8b8462034d..154c3587d2 100644 --- a/lms/urls.py +++ b/lms/urls.py @@ -164,7 +164,7 @@ urlpatterns = [ namespace='catalog')), # Update session view - path('lang_pref/session_language', lang_pref_views.update_session_language, name='session_language'), + path('lang_pref/cookie_language', lang_pref_views.update_cookie_language, name='cookie_language'), # Multiple course modes and identity verification path( diff --git a/openedx/core/djangoapps/dark_lang/middleware.py b/openedx/core/djangoapps/dark_lang/middleware.py index d2764a36a0..817d544e9f 100644 --- a/openedx/core/djangoapps/dark_lang/middleware.py +++ b/openedx/core/djangoapps/dark_lang/middleware.py @@ -10,7 +10,6 @@ the SessionMiddleware. from django.conf import settings -from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils.translation.trans_real import parse_accept_lang_header from django.utils.deprecation import MiddlewareMixin @@ -155,6 +154,5 @@ class DarkLangMiddleware(MiddlewareMixin): if not preview_lang: return - # Set the session key to the requested preview lang - request.session[LANGUAGE_SESSION_KEY] = preview_lang + # Set the response language_cookie to the requested preview lang set_language_cookie(request, response, preview_lang) diff --git a/openedx/core/djangoapps/dark_lang/tests.py b/openedx/core/djangoapps/dark_lang/tests.py index 836376ee7d..48362476a3 100644 --- a/openedx/core/djangoapps/dark_lang/tests.py +++ b/openedx/core/djangoapps/dark_lang/tests.py @@ -10,7 +10,6 @@ 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 from openedx.core.djangoapps.dark_lang.middleware import DarkLangMiddleware from openedx.core.djangoapps.dark_lang.models import DarkLangConfig @@ -50,23 +49,21 @@ class DarkLangMiddlewareTests(CacheIsolationTestCase): enabled=True ).save() - def process_middleware_request(self, language_session_key=UNSET, accept=UNSET): + def process_middleware_request(self, cookie_language=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] + cookie_language (str): The language code to set in self.client.cookies[LANGUAGE_COOKIE_NAME] 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) + self._set_client_cookie_language(cookie_language) request = Mock( spec=HttpRequest, - session=session, + session={}, META=meta, GET={}, method='GET', @@ -235,11 +232,17 @@ class DarkLangMiddlewareTests(CacheIsolationTestCase): self.process_middleware_request(accept=b'{};q=1.0, pt;q=0.5'.format(latin_america_code)) # pylint:disable=no-member ) - def assert_session_lang_equals(self, value, session): + def _set_client_cookie_language(self, cookie_language): """ - Assert that the LANGUAGE_SESSION_KEY set in session is equal to value + Set the cookie language in the Client """ - assert value == session.get(LANGUAGE_SESSION_KEY, UNSET) + self.client.cookies[settings.LANGUAGE_COOKIE_NAME] = cookie_language + + def assert_cookie_lang_equals(self, value): + """ + Assert that the language set in cookies is equal to value + """ + assert value == self.client.cookies.get(settings.LANGUAGE_COOKIE_NAME).value def _post_set_preview_lang(self, preview_language): """ @@ -253,77 +256,32 @@ class DarkLangMiddlewareTests(CacheIsolationTestCase): """ return self.client.post('/update_lang/', {'action': 'reset_preview_language'}) - 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): + self._set_client_cookie_language('rel') # 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 - ) + self.assert_cookie_lang_equals('rel') def test_preview_lang_with_dark_language(self): + self._set_client_cookie_language('unrel') self._post_set_preview_lang('unrel') self.client.get('/home') - self.assert_session_lang_equals( - 'unrel', - self.client.session - ) + self.assert_cookie_lang_equals('unrel') # 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 - ) + self.assert_cookie_lang_equals('unrel') 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._set_client_cookie_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 - ) + self.assert_cookie_lang_equals('rel') def test_disabled(self): DarkLangConfig(enabled=False, changed_by=self.user).save() @@ -334,22 +292,16 @@ class DarkLangMiddlewareTests(CacheIsolationTestCase): ) # With DarkLang disabled the clear should not change the session language - self._set_client_session_language('rel') + self._set_client_cookie_language('rel') self._post_clear_preview_lang() self.client.get('/home') - self.assert_session_lang_equals( - 'rel', - self.client.session - ) + self.assert_cookie_lang_equals('rel') # Test that setting the preview language with DarkLang disabled does nothing - self._set_client_session_language('unrel') + self._set_client_cookie_language('unrel') self._post_set_preview_lang('rel') self.client.get('/home') - self.assert_session_lang_equals( - 'unrel', - self.client.session - ) + self.assert_cookie_lang_equals('unrel') def test_accept_chinese_language_codes(self): DarkLangConfig( diff --git a/openedx/core/djangoapps/dark_lang/views.py b/openedx/core/djangoapps/dark_lang/views.py index 0865406408..4d0ce352b3 100644 --- a/openedx/core/djangoapps/dark_lang/views.py +++ b/openedx/core/djangoapps/dark_lang/views.py @@ -8,7 +8,6 @@ from django.http import Http404 from django.shortcuts import redirect from django.template.loader import render_to_string from django.utils.decorators import method_decorator -from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils.translation import gettext as _ from web_fragments.fragment import Fragment @@ -104,8 +103,6 @@ class PreviewLanguageFragmentView(EdxFragmentView): Clears the preview language for the current user. """ delete_user_preference(request.user, DARK_LANGUAGE_KEY) - if LANGUAGE_SESSION_KEY in request.session: - del request.session[LANGUAGE_SESSION_KEY] PageLevelMessages.register_success_message( request, _('Language reset to the default') diff --git a/openedx/core/djangoapps/lang_pref/middleware.py b/openedx/core/djangoapps/lang_pref/middleware.py index e36685fd0a..90b0535ec0 100644 --- a/openedx/core/djangoapps/lang_pref/middleware.py +++ b/openedx/core/djangoapps/lang_pref/middleware.py @@ -3,7 +3,6 @@ Middleware for Language Preferences """ from django.conf import settings 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 @@ -53,10 +52,6 @@ class LanguagePreferenceMiddleware(MiddlewareMixin): accept_header = cookie_lang request.META[LANGUAGE_HEADER] = accept_header - # Allow the new cookie setting to update the language in the user's session - if LANGUAGE_SESSION_KEY in request.session and request.session[LANGUAGE_SESSION_KEY] != cookie_lang: - del request.session[LANGUAGE_SESSION_KEY] - # Apply language specified in SiteConfiguration, ignoring user preferences. if language := get_value('LANGUAGE_CODE'): request.COOKIES[settings.LANGUAGE_COOKIE_NAME] = language diff --git a/openedx/core/djangoapps/lang_pref/tests/test_middleware.py b/openedx/core/djangoapps/lang_pref/tests/test_middleware.py index 6ab218b654..d0c047eeae 100644 --- a/openedx/core/djangoapps/lang_pref/tests/test_middleware.py +++ b/openedx/core/djangoapps/lang_pref/tests/test_middleware.py @@ -12,7 +12,6 @@ from django.contrib.sessions.middleware import SessionMiddleware from django.http import HttpResponse from django.test.client import Client, RequestFactory from django.urls import reverse -from django.utils.translation import LANGUAGE_SESSION_KEY from django.utils.translation.trans_real import parse_accept_lang_header from openedx.core.djangoapps.lang_pref import COOKIE_DURATION, LANGUAGE_KEY @@ -88,8 +87,6 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): domain=settings.SESSION_COOKIE_DOMAIN, ) - assert LANGUAGE_SESSION_KEY not in self.request.session - @ddt.data(*itertools.product( (None, 'eo', 'es'), # LANGUAGE_COOKIE_NAME (None, 'es', 'en'), # Language Preference In @@ -115,32 +112,26 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): (logged_in, ) + test_def for logged_in in (True, False) for test_def 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'), - (None, 'en', None, None, 'en'), - (None, 'eo', 'en', 'en', 'eo'), - (None, None, 'en', 'en', None), - ('en', None, None, 'en', None), - ('en', 'en', None, 'en', 'en'), - ('en', None, 'eo', 'en;q=1.0,eo', None), - ('en', None, 'en', 'en', None), - ('en', 'eo', 'en', 'en', None), - ('en', 'eo', 'eo', 'en;q=1.0,eo', None) + # (LANGUAGE_COOKIE_NAME, Accept-Language In, Accept-Language Out) + (None, None, None), + (None, None, None), + (None, None, None), + (None, 'en', 'en'), + (None, 'en', 'en'), + ('en', None, 'en'), + ('en', None, 'en'), + ('en', 'eo', 'en;q=1.0,eo'), + ('en', 'en', 'en'), + ('en', 'en', 'en'), + ('en', 'eo', 'en;q=1.0,eo') ] )) @ddt.unpack - def test_preference_cookie_overrides_browser( - self, logged_in, lang_cookie, lang_session_in, accept_lang_in, accept_lang_out, - lang_session_out, - ): + def test_preference_cookie_overrides_browser(self, logged_in, lang_cookie, accept_lang_in, accept_lang_out): if not logged_in: self.request.user = self.anonymous_user if 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: self.request.META['HTTP_ACCEPT_LANGUAGE'] = accept_lang_in else: @@ -160,8 +151,6 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): else: assert accept_lang_result == accept_lang_out - assert self.request.session.get(LANGUAGE_SESSION_KEY) == lang_session_out - @ddt.data(None, 'es', 'en') def test_logout_preserves_cookie(self, lang_cookie): if lang_cookie: @@ -293,10 +282,6 @@ class TestUserPreferenceMiddleware(CacheIsolationTestCase): assert get_user_preference(self.user, LANGUAGE_KEY) == user_preference assert response['Content-Language'] == 'eo' - # `LocaleMiddleware` no longer looks for language in the session since Django 3.2. It checks the cookie instead. - # See: https://docs.djangoproject.com/en/3.2/releases/3.0/#miscellaneous - assert self.client.session.get(LANGUAGE_SESSION_KEY) is None - # Clean up by making a request to a Site without specific configuration. with with_site_configuration_context(): self.client.get('/') diff --git a/openedx/core/djangoapps/lang_pref/tests/test_views.py b/openedx/core/djangoapps/lang_pref/tests/test_views.py deleted file mode 100644 index 7f3005a093..0000000000 --- a/openedx/core/djangoapps/lang_pref/tests/test_views.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Tests: lang pref views -""" - - -import json - -from django.contrib.sessions.middleware import SessionMiddleware -from django.test import TestCase -from django.test.client import RequestFactory -from django.urls import reverse -from django.utils.translation import LANGUAGE_SESSION_KEY, get_language -from common.djangoapps.student.tests.factories import UserFactory - - -class TestLangPrefView(TestCase): - """ - Language preference view tests. - """ - - def setUp(self): - super().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' - response = self.client.patch(reverse("session_language"), json.dumps({'pref-lang': 'eo'})) - assert response.status_code == 200 - self.client.get('/') - assert get_language() == 'eo' - - response = self.client.patch(reverse("session_language"), json.dumps({'pref-lang': 'en'})) - assert response.status_code == 200 - self.client.get('/') - assert get_language() == 'en' diff --git a/openedx/core/djangoapps/lang_pref/views.py b/openedx/core/djangoapps/lang_pref/views.py index b98b06a33f..a9c0ac6ba9 100644 --- a/openedx/core/djangoapps/lang_pref/views.py +++ b/openedx/core/djangoapps/lang_pref/views.py @@ -7,23 +7,21 @@ import json from django.conf import settings from django.http import HttpResponse -from django.utils.translation import LANGUAGE_SESSION_KEY from django.views.decorators.csrf import ensure_csrf_cookie from openedx.core.djangoapps.lang_pref import LANGUAGE_KEY -from openedx.core.djangoapps.lang_pref.helpers import set_language_cookie +from openedx.core.djangoapps.lang_pref.helpers import get_language_cookie, set_language_cookie @ensure_csrf_cookie -def update_session_language(request): +def update_cookie_language(request): """ - Update the language session key. + Update the language cookie. """ response = HttpResponse(200) if request.method == 'PATCH': data = json.loads(request.body.decode('utf8')) language = data.get(LANGUAGE_KEY, settings.LANGUAGE_CODE) - if request.session.get(LANGUAGE_SESSION_KEY, None) != language: - request.session[LANGUAGE_SESSION_KEY] = str(language) - set_language_cookie(request, response, language) + if get_language_cookie(request) != language: + set_language_cookie(request, response, language) return response