mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	Refs #15902 -- Deprecated storing user's language in the session.
This commit is contained in:
		| @@ -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() | ||||
|   | ||||
| @@ -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): | ||||
|   | ||||
| @@ -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,20 +1824,8 @@ 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:: | ||||
|  | ||||
|     from django.utils import translation | ||||
|     user_language = 'fr' | ||||
|     translation.activate(user_language) | ||||
|     request.session[translation.LANGUAGE_SESSION_KEY] = 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 | ||||
| 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:: | ||||
| session in a cookie, set the :setting:`LANGUAGE_COOKIE_NAME` cookie on the | ||||
| response:: | ||||
|  | ||||
|     from django.conf import settings | ||||
|     from django.http import HttpResponse | ||||
| @@ -1847,6 +1835,14 @@ is configured in :setting:`LANGUAGE_COOKIE_NAME`. For example:: | ||||
|     response = HttpResponse(...) | ||||
|     response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language) | ||||
|  | ||||
| You would typically want to use both: :func:`django.utils.translation.activate()` | ||||
| changes the language for this thread, and setting the cookie makes this | ||||
| preference persist in future requests. | ||||
|  | ||||
| .. versionchanged:: 3.0 | ||||
|  | ||||
|     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,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( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user