summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorClaude Paroz <claude@2xlibre.net>2016-08-14 22:42:49 +0200
committerTim Graham <timograham@gmail.com>2019-02-14 10:23:02 -0500
commita8e2a9bac6e548d6ab2e13af6171d2fdd3b8055b (patch)
treef49b5f5f14b5a8d8f907efee6079a4e3b51ce72c
parent76990cbbda5d93fda560c8a5ab019860f7efaab7 (diff)
Refs #15902 -- Deprecated storing user's language in the session.
-rw-r--r--django/contrib/auth/__init__.py9
-rw-r--r--django/contrib/sessions/backends/base.py10
-rw-r--r--django/utils/translation/trans_real.py11
-rw-r--r--django/views/i18n.py2
-rw-r--r--docs/internals/deprecation.txt3
-rw-r--r--docs/ref/utils.txt5
-rw-r--r--docs/releases/3.0.txt10
-rw-r--r--docs/topics/i18n/translation.txt25
-rw-r--r--tests/auth_tests/test_views.py15
-rw-r--r--tests/auth_tests/urls.py2
-rw-r--r--tests/view_tests/tests/test_i18n.py57
11 files changed, 93 insertions, 56 deletions
diff --git a/django/contrib/auth/__init__.py b/django/contrib/auth/__init__.py
index 6aa900fbe4..6d99aa0c9b 100644
--- a/django/contrib/auth/__init__.py
+++ b/django/contrib/auth/__init__.py
@@ -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()
diff --git a/django/contrib/sessions/backends/base.py b/django/contrib/sessions/backends/base.py
index 5c50f87e34..0e8ade3fbc 100644
--- a/django/contrib/sessions/backends/base.py
+++ b/django/contrib/sessions/backends/base.py
@@ -1,6 +1,7 @@
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):
diff --git a/django/utils/translation/trans_real.py b/django/utils/translation/trans_real.py
index d6f1f7f14f..f4985fb3c1 100644
--- a/django/utils/translation/trans_real.py
+++ b/django/utils/translation/trans_real.py
@@ -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)
diff --git a/django/views/i18n.py b/django/views/i18n.py
index f684da23dc..ce5691632c 100644
--- a/django/views/i18n.py
+++ b/django/views/i18n.py
@@ -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,
diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt
index 27dafd7b96..02935c01a2 100644
--- a/docs/internals/deprecation.txt
+++ b/docs/internals/deprecation.txt
@@ -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
diff --git a/docs/ref/utils.txt b/docs/ref/utils.txt
index cd7c7aca63..2becfea269 100644
--- a/docs/ref/utils.txt
+++ b/docs/ref/utils.txt
@@ -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.
diff --git a/docs/releases/3.0.txt b/docs/releases/3.0.txt
index b2e6b479e7..f036969c92 100644
--- a/docs/releases/3.0.txt
+++ b/docs/releases/3.0.txt
@@ -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
diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt
index 3b09e41617..00338100f9 100644
--- a/docs/topics/i18n/translation.txt
+++ b/docs/topics/i18n/translation.txt
@@ -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`
diff --git a/tests/auth_tests/test_views.py b/tests/auth_tests/test_views.py
index 4949ce1bf4..f738a73e64 100644
--- a/tests/auth_tests/test_views.py
+++ b/tests/auth_tests/test_views.py
@@ -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):
diff --git a/tests/auth_tests/urls.py b/tests/auth_tests/urls.py
index d0b259935b..142a2b49c2 100644
--- a/tests/auth_tests/urls.py
+++ b/tests/auth_tests/urls.py
@@ -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),
]
diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py
index a3463a1bab..7b36181800 100644
--- a/tests/view_tests/tests/test_i18n.py
+++ b/tests/view_tests/tests/test_i18n.py
@@ -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,7 +37,8 @@ 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, '/')
- self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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]
self.assertEqual(language_cookie.value, lang_code)
@@ -53,7 +55,9 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,11 +70,15 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,7 +89,9 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,7 +102,9 @@ class SetLanguageTests(TestCase):
post_data = {'language': lang_code}
response = self.client.post('/i18n/setlang/', post_data)
self.assertRedirects(response, '/')
- self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,7 +114,9 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,7 +128,9 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,7 +140,9 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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,7 +195,9 @@ 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.session[LANGUAGE_SESSION_KEY], lang_code)
+ 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={
'append': 'django.middleware.locale.LocaleMiddleware',
@@ -178,7 +207,9 @@ class SetLanguageTests(TestCase):
'/i18n/setlang/', data={'language': 'nl'},
follow=True, HTTP_REFERER='/en/translated/'
)
- self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'nl')
+ 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
response = self.client.post(