Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions lms/djangoapps/courseware/context_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"""


from openedx.core.djangoapps.site_configuration.helpers import get_value
from openedx.core.djangoapps.user_api.errors import UserAPIInternalError, UserNotFound
from openedx.core.djangoapps.user_api.preferences.api import get_user_preferences
from openedx.core.lib.cache_utils import get_cache
Expand All @@ -22,6 +23,8 @@ def user_timezone_locale_prefs(request):
"""
Checks if request has an authenticated user.
If so, sends set (or none if unset) time_zone and language prefs.
If site-wide language is set, that language is used over the language set
in user preferences.

This interacts with the DateUtils to either display preferred or attempt to determine
system/browser set time_zones and languages
Expand All @@ -43,6 +46,9 @@ def user_timezone_locale_prefs(request):
key: user_preferences.get(pref_name, None)
for key, pref_name in RETRIEVABLE_PREFERENCES.items()
}
site_wide_language = get_value('LANGUAGE_CODE', None)
if site_wide_language:
user_prefs['user_language'] = site_wide_language

cached_value.update(user_prefs)
return cached_value
9 changes: 8 additions & 1 deletion lms/djangoapps/courseware/tests/test_context_processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""


from unittest.mock import Mock
from unittest.mock import Mock, patch
from django.contrib.auth.models import AnonymousUser

from lms.djangoapps.courseware.context_processor import user_timezone_locale_prefs
Expand Down Expand Up @@ -43,3 +43,10 @@ def test_no_language_preference(self):
assert context['user_language'] is None
assert context['user_timezone'] is not None
assert context['user_timezone'] == 'Asia/Tokyo'

@patch("lms.djangoapps.courseware.context_processor.get_value")
def test_site_wide_language_set(self, mock_get_value):
mock_get_value.return_value = 'ar'
set_user_preference(self.user, 'pref-lang', 'en')
context = user_timezone_locale_prefs(self.request)
assert context['user_language'] == 'ar'
10 changes: 0 additions & 10 deletions openedx/core/djangoapps/dark_lang/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

from openedx.core.djangoapps.dark_lang import DARK_LANGUAGE_KEY
from openedx.core.djangoapps.dark_lang.models import DarkLangConfig
from openedx.core.djangoapps.site_configuration.helpers import get_value
from openedx.core.djangoapps.user_api.preferences.api import get_user_preference

# If django 1.7 or higher is used, the right-side can be updated with new-style codes.
Expand Down Expand Up @@ -91,17 +90,8 @@ def process_request(self, request):
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):
"""
Apply language specified in site configuration.
"""
language = get_value('LANGUAGE_CODE', None)
if language:
request.session[LANGUAGE_SESSION_KEY] = language

def _fuzzy_match(self, lang_code):
"""Returns a fuzzy match for lang_code"""
match = None
Expand Down
10 changes: 0 additions & 10 deletions openedx/core/djangoapps/dark_lang/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,16 +257,6 @@ def _set_client_session_language(self, session_language):
session[LANGUAGE_SESSION_KEY] = session_language
session.save()

@with_site_configuration(configuration={'LANGUAGE_CODE': 'rel'})
def test_site_configuration_language(self):
# `LANGUAGE_CODE` in site configuration should override session lang
self._set_client_session_language('notrel')
self.client.get('/home')
self.assert_session_lang_equals(
'rel',
self.client.session
)

def test_preview_lang_with_released_language(self):
# Preview lang should always override selection
self._post_set_preview_lang('rel')
Expand Down
135 changes: 81 additions & 54 deletions openedx/core/djangoapps/lang_pref/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from django.utils.translation.trans_real import parse_accept_lang_header

from openedx.core.djangoapps.lang_pref import COOKIE_DURATION, LANGUAGE_HEADER, LANGUAGE_KEY
from openedx.core.djangoapps.site_configuration.helpers import get_value
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
from openedx.core.lib.mobile_utils import is_request_from_mobile_app
Expand All @@ -26,63 +27,89 @@ def process_request(self, request):
"""
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.

If site-wide language is set, don't use the language from user's
preferences and don't set the value from the cookies as the user's
preffered language.
Instead use the value set as a site-wide language.
"""
site_wide_language = get_value('LANGUAGE_CODE', None)
if site_wide_language:
request.session[LANGUAGE_SESSION_KEY] = site_wide_language
self.update_accept_language(request, site_wide_language)
return

cookie_lang = request.COOKIES.get(settings.LANGUAGE_COOKIE, None)
if cookie_lang:
if request.user.is_authenticated:
set_user_preference(request.user, LANGUAGE_KEY, cookie_lang)
else:
request._anonymous_user_cookie_lang = cookie_lang # lint-amnesty, pylint: disable=protected-access

accept_header = request.META.get(LANGUAGE_HEADER, None)
if accept_header:
current_langs = parse_accept_lang_header(accept_header)
# Promote the cookie_lang over any language currently in the accept header
current_langs = [(lang, qvalue) for (lang, qvalue) in current_langs if lang != cookie_lang]
current_langs.insert(0, (cookie_lang, 1))
accept_header = ",".join(f"{lang};q={qvalue}" for (lang, qvalue) in current_langs)
else:
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]
if not cookie_lang:
return

if request.user.is_authenticated:
set_user_preference(request.user, LANGUAGE_KEY, cookie_lang)
else:
request._anonymous_user_cookie_lang = cookie_lang # lint-amnesty, pylint: disable=protected-access

self.update_accept_language(request, cookie_lang)

# 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]

def process_response(self, request, response): # lint-amnesty, pylint: disable=missing-function-docstring
# If the user is logged in, check for their language preference. Also check for real user
# if current user is a masquerading user,
user_pref = None
current_user = None
if hasattr(request, 'user'):
current_user = getattr(request.user, 'real_user', request.user)

if current_user and current_user.is_authenticated:
anonymous_cookie_lang = getattr(request, '_anonymous_user_cookie_lang', None)
if anonymous_cookie_lang:
user_pref = anonymous_cookie_lang
set_user_preference(current_user, LANGUAGE_KEY, anonymous_cookie_lang)
else:
# Get the user's language preference
try:
user_pref = get_user_preference(current_user, LANGUAGE_KEY)
except (UserAPIRequestError, UserAPIInternalError):
# 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 user_pref and not is_request_from_mobile_app(request):
response.set_cookie(
settings.LANGUAGE_COOKIE,
value=user_pref,
domain=settings.SESSION_COOKIE_DOMAIN,
max_age=COOKIE_DURATION,
secure=request.is_secure()
)
else:
response.delete_cookie(
settings.LANGUAGE_COOKIE,
domain=settings.SESSION_COOKIE_DOMAIN
)
site_wide_language = get_value('LANGUAGE_CODE', None)
if site_wide_language:
response.set_cookie(
settings.LANGUAGE_COOKIE,
value=site_wide_language,
domain=settings.SESSION_COOKIE_DOMAIN,
max_age=COOKIE_DURATION,
secure=request.is_secure()
)
else:
# If the user is logged in, check for their language preference. Also check for real user
# if current user is a masquerading user,
user_pref = None
current_user = None
if hasattr(request, 'user'):
current_user = getattr(request.user, 'real_user', request.user)

if current_user and current_user.is_authenticated:
anonymous_cookie_lang = getattr(request, '_anonymous_user_cookie_lang', None)
if anonymous_cookie_lang:
user_pref = anonymous_cookie_lang
set_user_preference(current_user, LANGUAGE_KEY, anonymous_cookie_lang)
else:
# Get the user's language preference
try:
user_pref = get_user_preference(current_user, LANGUAGE_KEY)
except (UserAPIRequestError, UserAPIInternalError):
# 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 user_pref and not is_request_from_mobile_app(request):
response.set_cookie(
settings.LANGUAGE_COOKIE,
value=user_pref,
domain=settings.SESSION_COOKIE_DOMAIN,
max_age=COOKIE_DURATION,
secure=request.is_secure()
)
else:
response.delete_cookie(
settings.LANGUAGE_COOKIE,
domain=settings.SESSION_COOKIE_DOMAIN
)

return response

def update_accept_language(self, request, new_lang):
accept_header = request.META.get(LANGUAGE_HEADER, None)
if accept_header:
current_langs = parse_accept_lang_header(accept_header)
# Promote the new_lang over any language currently in the accept header
current_langs = [(lang, qvalue) for (lang, qvalue) in current_langs if lang != new_lang]
current_langs.insert(0, (new_lang, 1))
accept_header = ",".join(f"{lang};q={qvalue}" for (lang, qvalue) in current_langs)
else:
accept_header = new_lang
request.META[LANGUAGE_HEADER] = accept_header
78 changes: 78 additions & 0 deletions openedx/core/djangoapps/lang_pref/tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -266,3 +266,81 @@ def test_remove_lang_cookie_for_mobile_app(self, mock_get_user_preference, mock_
mock_is_mobile_request.return_value = True
response = self.middleware.process_response(self.request, response)
response.delete_cookie.assert_called()

@mock.patch('openedx.core.djangoapps.lang_pref.middleware.get_value')
def test_process_request_uses_site_wide_language_over_user_preferences(
self,
mock_get_value,
):
"""
Test that process_request method uses site-wide language when set over
the language set in user preferences.
"""
mock_get_value.return_value = 'ar'

self.middleware.process_request(self.request)

assert self.request.session.get(LANGUAGE_SESSION_KEY, None) == 'ar'

@mock.patch('openedx.core.djangoapps.lang_pref.middleware.set_user_preference')
@mock.patch('openedx.core.djangoapps.lang_pref.middleware.get_value')
def test_process_request_does_not_save_lang_from_cookies_when_site_wide_language_is_set(
self,
mock_get_value,
mock_set_user_preference,
):
"""
Test that process_request method doesn't save language from cookies as
user preference when site-wide language is set.
"""
mock_get_value.return_value = 'ar'
self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'en'

self.middleware.process_request(self.request)

mock_set_user_preference.assert_not_called()

@mock.patch('openedx.core.djangoapps.lang_pref.middleware.LanguagePreferenceMiddleware.update_accept_language')
@mock.patch('openedx.core.djangoapps.lang_pref.middleware.set_user_preference')
@mock.patch('openedx.core.djangoapps.lang_pref.middleware.get_value')
def test_process_request_updates_accept_language(
self,
mock_get_value,
mock_set_user_preference,
mock_update_accept_language,
):
"""
Test that process_request method updates accept language meta request
header when site-wide language is set.
"""
mock_get_value.return_value = 'ar'
self.request.COOKIES[settings.LANGUAGE_COOKIE] = 'en'

self.middleware.process_request(self.request)

mock_update_accept_language.assert_called_with(self.request, 'ar')

@mock.patch('openedx.core.djangoapps.lang_pref.middleware.get_user_preference')
@mock.patch('openedx.core.djangoapps.lang_pref.middleware.get_value')
def test_process_response_uses_site_wide_language_over_user_preferences(
self,
mock_get_value,
mock_get_user_preference,
):
"""
Test that process_response method uses site-wide language when set over
the language set in user preferences.
"""
mock_get_user_preference.return_value = 'en'
mock_get_value.return_value = 'ar'
response = mock.Mock(spec=HttpResponse)

self.middleware.process_response(self.request, response)

response.set_cookie.assert_called_with(
settings.LANGUAGE_COOKIE,
value='ar',
domain=settings.SESSION_COOKIE_DOMAIN,
max_age=COOKIE_DURATION,
secure=self.request.is_secure(),
)