Kaydet (Commit) a8e2a9ba authored tarafından Claude Paroz's avatar Claude Paroz Kaydeden (comit) Tim Graham

Refs #15902 -- Deprecated storing user's language in the session.

üst 76990cbb
......@@ -7,7 +7,6 @@ from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.middleware.csrf import rotate_token
from django.utils.crypto import constant_time_compare
from django.utils.module_loading import import_string
from django.utils.translation import LANGUAGE_SESSION_KEY
from .signals import user_logged_in, user_logged_out, user_login_failed
......@@ -143,15 +142,7 @@ def logout(request):
if not getattr(user, 'is_authenticated', True):
user = None
user_logged_out.send(sender=user.__class__, request=request, user=user)
# remember language choice saved to session
language = request.session.get(LANGUAGE_SESSION_KEY)
request.session.flush()
if language is not None:
request.session[LANGUAGE_SESSION_KEY] = language
if hasattr(request, 'user'):
from django.contrib.auth.models import AnonymousUser
request.user = AnonymousUser()
......
import base64
import logging
import string
import warnings
from datetime import datetime, timedelta
from django.conf import settings
......@@ -10,7 +11,9 @@ from django.utils import timezone
from django.utils.crypto import (
constant_time_compare, get_random_string, salted_hmac,
)
from django.utils.deprecation import RemovedInDjango40Warning
from django.utils.module_loading import import_string
from django.utils.translation import LANGUAGE_SESSION_KEY
# session_key should not be case sensitive because some backends can store it
# on case insensitive file systems.
......@@ -51,6 +54,13 @@ class SessionBase:
return key in self._session
def __getitem__(self, key):
if key == LANGUAGE_SESSION_KEY:
warnings.warn(
'The user language will no longer be stored in '
'request.session in Django 4.0. Read it from '
'request.COOKIES[settings.LANGUAGE_COOKIE_NAME] instead.',
RemovedInDjango40Warning, stacklevel=2,
)
return self._session[key]
def __setitem__(self, key, value):
......
......@@ -15,7 +15,7 @@ from django.core.signals import setting_changed
from django.dispatch import receiver
from django.utils.safestring import SafeData, mark_safe
from . import LANGUAGE_SESSION_KEY, to_language, to_locale
from . import to_language, to_locale
# Translations are cached in a dictionary for every language.
# The active translations are stored by threadid to make them thread local.
......@@ -456,14 +456,9 @@ def get_language_from_request(request, check_path=False):
if lang_code is not None:
return lang_code
supported_lang_codes = get_languages()
if hasattr(request, 'session'):
lang_code = request.session.get(LANGUAGE_SESSION_KEY)
if lang_code in supported_lang_codes and lang_code is not None and check_for_language(lang_code):
return lang_code
lang_code = request.COOKIES.get(settings.LANGUAGE_COOKIE_NAME)
if lang_code is not None and lang_code in get_languages() and check_for_language(lang_code):
return lang_code
try:
return get_supported_language_variant(lang_code)
......
......@@ -47,6 +47,8 @@ def set_language(request):
if next_trans != next:
response = HttpResponseRedirect(next_trans)
if hasattr(request, 'session'):
# Storing the language in the session is deprecated.
# (RemovedInDjango40Warning)
request.session[LANGUAGE_SESSION_KEY] = lang_code
response.set_cookie(
settings.LANGUAGE_COOKIE_NAME, lang_code,
......
......@@ -24,6 +24,9 @@ details on these changes.
``ugettext_noop()``, ``ungettext()``, and ``ungettext_lazy()`` will be
removed.
* ``django.views.i18n.set_language()`` will no longer set the user language in
``request.session`` (key ``django.utils.translation.LANGUAGE_SESSION_KEY``).
.. _deprecation-removed-in-3.1:
3.1
......
......@@ -1106,3 +1106,8 @@ functions without the ``u``.
Session key under which the active language for the current session is
stored.
.. deprecated:: 3.0
The language won't be stored in the session in Django 4.0. Use the
:setting:`LANGUAGE_COOKIE_NAME` cookie instead.
......@@ -302,6 +302,11 @@ Miscellaneous
* ``ContentType.__str__()`` now includes the model's ``app_label`` to
disambiguate model's with the same name in different apps.
* Because accessing the language in the session rather than in the cookie is
deprecated, ``LocaleMiddleware`` no longer looks for the user's language in
the session and :func:`django.contrib.auth.logout` no longer preserves the
session's language after logout.
.. _deprecated-features-3.0:
Features deprecated in 3.0
......@@ -332,6 +337,11 @@ Miscellaneous
:func:`~django.utils.translation.ngettext`, and
:func:`~django.utils.translation.ngettext_lazy`.
* To limit creation of sessions and hence favor some caching strategies,
:func:`django.views.i18n.set_language` will stop setting the user's language
in the session in Django 4.0. Since Django 2.1, the language is always stored
in the :setting:`LANGUAGE_COOKIE_NAME` cookie.
.. _removed-features-3.0:
Features removed in 3.0
......
......@@ -1824,28 +1824,24 @@ You may want to set the active language for the current session explicitly. Perh
a user's language preference is retrieved from another system, for example.
You've already been introduced to :func:`django.utils.translation.activate()`. That
applies to the current thread only. To persist the language for the entire
session, also modify :data:`~django.utils.translation.LANGUAGE_SESSION_KEY`
in the session::
session in a cookie, set the :setting:`LANGUAGE_COOKIE_NAME` cookie on the
response::
from django.conf import settings
from django.http import HttpResponse
from django.utils import translation
user_language = 'fr'
translation.activate(user_language)
request.session[translation.LANGUAGE_SESSION_KEY] = user_language
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
You would typically want to use both: :func:`django.utils.translation.activate()`
will change the language for this thread, and modifying the session makes this
changes the language for this thread, and setting the cookie makes this
preference persist in future requests.
If you are not using sessions, the language will persist in a cookie, whose name
is configured in :setting:`LANGUAGE_COOKIE_NAME`. For example::
.. versionchanged:: 3.0
from django.conf import settings
from django.http import HttpResponse
from django.utils import translation
user_language = 'fr'
translation.activate(user_language)
response = HttpResponse(...)
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
In older versions, you could set the language in the current session.
Using translations outside views and templates
----------------------------------------------
......@@ -1980,9 +1976,6 @@ following this algorithm:
root URLconf. See :ref:`url-internationalization` for more information
about the language prefix and how to internationalize URL patterns.
* Failing that, it looks for the :data:`~django.utils.translation.LANGUAGE_SESSION_KEY`
key in the current user's session.
* Failing that, it looks for a cookie.
The name of the cookie used is set by the :setting:`LANGUAGE_COOKIE_NAME`
......
......@@ -31,7 +31,6 @@ from django.test import Client, TestCase, override_settings
from django.test.client import RedirectCycleError
from django.urls import NoReverseMatch, reverse, reverse_lazy
from django.utils.http import urlsafe_base64_encode
from django.utils.translation import LANGUAGE_SESSION_KEY
from .client import PasswordResetConfirmClient
from .models import CustomUser, UUIDUser
......@@ -1075,16 +1074,12 @@ class LogoutTest(AuthViewsTestCase):
self.confirm_logged_out()
def test_logout_preserve_language(self):
"""Language stored in session is preserved after logout"""
# Create a new session with language
engine = import_module(settings.SESSION_ENGINE)
session = engine.SessionStore()
session[LANGUAGE_SESSION_KEY] = 'pl'
session.save()
self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
"""Language is preserved after logout."""
self.login()
self.client.post('/setlang/', {'language': 'pl'})
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, 'pl')
self.client.get('/logout/')
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'pl')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, 'pl')
@override_settings(LOGOUT_REDIRECT_URL='/custom/')
def test_logout_redirect_url_setting(self):
......
......@@ -9,6 +9,7 @@ from django.shortcuts import render
from django.template import RequestContext, Template
from django.urls import path, re_path, reverse_lazy
from django.views.decorators.cache import never_cache
from django.views.i18n import set_language
class CustomRequestAuthenticationForm(AuthenticationForm):
......@@ -148,6 +149,7 @@ urlpatterns = auth_urlpatterns + [
path('permission_required_exception/', permission_required_exception),
path('login_and_permission_required_exception/', login_and_permission_required_exception),
path('setlang/', set_language, name='set_language'),
# This line is only required to render the password reset with is_admin=True
path('admin/', admin.site.urls),
]
......@@ -4,11 +4,12 @@ from os import path
from django.conf import settings
from django.test import (
RequestFactory, SimpleTestCase, TestCase, modify_settings,
RequestFactory, SimpleTestCase, TestCase, ignore_warnings, modify_settings,
override_settings,
)
from django.test.selenium import SeleniumTestCase
from django.urls import reverse
from django.utils.deprecation import RemovedInDjango40Warning
from django.utils.translation import (
LANGUAGE_SESSION_KEY, get_language, override,
)
......@@ -36,6 +37,7 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code, 'next': '/'}
response = self.client.post('/i18n/setlang/', post_data, HTTP_REFERER='/i_should_not_be_used/')
self.assertRedirects(response, '/')
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
# The language is set in a cookie.
language_cookie = self.client.cookies[settings.LANGUAGE_COOKIE_NAME]
......@@ -53,6 +55,8 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code, 'next': '//unsafe/redirection/'}
response = self.client.post('/i18n/setlang/', data=post_data)
self.assertEqual(response.url, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_http_next(self):
......@@ -66,10 +70,14 @@ class SetLanguageTests(TestCase):
# Insecure URL in POST data.
response = self.client.post('/i18n/setlang/', data=post_data, secure=True)
self.assertEqual(response.url, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
# Insecure URL in HTTP referer.
response = self.client.post('/i18n/setlang/', secure=True, HTTP_REFERER=non_https_next_url)
self.assertEqual(response.url, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_redirect_to_referer(self):
......@@ -81,6 +89,8 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code}
response = self.client.post('/i18n/setlang/', post_data, HTTP_REFERER='/i18n/')
self.assertRedirects(response, '/i18n/', fetch_redirect_response=False)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_default_redirect(self):
......@@ -92,6 +102,8 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code}
response = self.client.post('/i18n/setlang/', post_data)
self.assertRedirects(response, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_performs_redirect_for_ajax_if_explicitly_requested(self):
......@@ -102,6 +114,8 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code, 'next': '/'}
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertRedirects(response, '/')
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_doesnt_perform_a_redirect_to_referer_for_ajax(self):
......@@ -114,6 +128,8 @@ class SetLanguageTests(TestCase):
headers = {'HTTP_REFERER': '/', 'HTTP_X_REQUESTED_WITH': 'XMLHttpRequest'}
response = self.client.post('/i18n/setlang/', post_data, **headers)
self.assertEqual(response.status_code, 204)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_doesnt_perform_a_default_redirect_for_ajax(self):
......@@ -124,6 +140,8 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code}
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.status_code, 204)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
def test_setlang_unsafe_next_for_ajax(self):
......@@ -134,7 +152,16 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code, 'next': '//unsafe/redirection/'}
response = self.client.post('/i18n/setlang/', post_data, HTTP_X_REQUESTED_WITH='XMLHttpRequest')
self.assertEqual(response.url, '/')
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
def test_session_langauge_deprecation(self):
msg = (
'The user language will no longer be stored in request.session '
'in Django 4.0. Read it from '
'request.COOKIES[settings.LANGUAGE_COOKIE_NAME] instead.'
)
with self.assertRaisesMessage(RemovedInDjango40Warning, msg):
self.client.session[LANGUAGE_SESSION_KEY]
def test_setlang_reversal(self):
self.assertEqual(reverse('set_language'), '/i18n/setlang/')
......@@ -168,6 +195,8 @@ class SetLanguageTests(TestCase):
encoded_url = '/test-setlang/%C3%A4/' # (%C3%A4 decodes to ä)
response = self.client.post('/i18n/setlang/', {'language': lang_code}, HTTP_REFERER=encoded_url)
self.assertRedirects(response, encoded_url, fetch_redirect_response=False)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, lang_code)
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
@modify_settings(MIDDLEWARE={
......@@ -178,6 +207,8 @@ class SetLanguageTests(TestCase):
'/i18n/setlang/', data={'language': 'nl'},
follow=True, HTTP_REFERER='/en/translated/'
)
self.assertEqual(self.client.cookies[settings.LANGUAGE_COOKIE_NAME].value, 'nl')
with ignore_warnings(category=RemovedInDjango40Warning):
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl')
self.assertRedirects(response, '/nl/vertaald/')
# And reverse
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment