mirror of https://github.com/django/django.git
Fixed #22120 -- Documented persistent activation of languages and cleaned up language session key use
This commit is contained in:
parent
09b725f51b
commit
8cd32f0965
|
@ -5,6 +5,7 @@ from django.apps import apps as django_apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||||
from django.middleware.csrf import rotate_token
|
from django.middleware.csrf import rotate_token
|
||||||
|
|
||||||
from .signals import user_logged_in, user_logged_out, user_login_failed
|
from .signals import user_logged_in, user_logged_out, user_login_failed
|
||||||
|
@ -108,12 +109,12 @@ def logout(request):
|
||||||
|
|
||||||
# remember language choice saved to session
|
# remember language choice saved to session
|
||||||
# for backwards compatibility django_language is also checked (remove in 1.8)
|
# for backwards compatibility django_language is also checked (remove in 1.8)
|
||||||
language = request.session.get('_language', request.session.get('django_language'))
|
language = request.session.get(LANGUAGE_SESSION_KEY, request.session.get('django_language'))
|
||||||
|
|
||||||
request.session.flush()
|
request.session.flush()
|
||||||
|
|
||||||
if language is not None:
|
if language is not None:
|
||||||
request.session['_language'] = language
|
request.session[LANGUAGE_SESSION_KEY] = language
|
||||||
|
|
||||||
if hasattr(request, 'user'):
|
if hasattr(request, 'user'):
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
|
|
|
@ -14,6 +14,7 @@ from django.http import QueryDict, HttpRequest
|
||||||
from django.utils.encoding import force_text
|
from django.utils.encoding import force_text
|
||||||
from django.utils.http import urlquote
|
from django.utils.http import urlquote
|
||||||
from django.utils.six.moves.urllib.parse import urlparse, ParseResult
|
from django.utils.six.moves.urllib.parse import urlparse, ParseResult
|
||||||
|
from django.utils.translation import LANGUAGE_SESSION_KEY
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
from django.test import TestCase, override_settings
|
from django.test import TestCase, override_settings
|
||||||
from django.test.utils import patch_logger
|
from django.test.utils import patch_logger
|
||||||
|
@ -718,12 +719,12 @@ class LogoutTest(AuthViewsTestCase):
|
||||||
# Create a new session with language
|
# Create a new session with language
|
||||||
engine = import_module(settings.SESSION_ENGINE)
|
engine = import_module(settings.SESSION_ENGINE)
|
||||||
session = engine.SessionStore()
|
session = engine.SessionStore()
|
||||||
session['_language'] = 'pl'
|
session[LANGUAGE_SESSION_KEY] = 'pl'
|
||||||
session.save()
|
session.save()
|
||||||
self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
|
self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
|
||||||
|
|
||||||
self.client.get('/logout/')
|
self.client.get('/logout/')
|
||||||
self.assertEqual(self.client.session['_language'], 'pl')
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'pl')
|
||||||
|
|
||||||
|
|
||||||
@skipIfCustomUser
|
@skipIfCustomUser
|
||||||
|
|
|
@ -21,6 +21,7 @@ __all__ = [
|
||||||
'npgettext', 'npgettext_lazy',
|
'npgettext', 'npgettext_lazy',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
LANGUAGE_SESSION_KEY = '_language'
|
||||||
|
|
||||||
class TranslatorCommentWarning(SyntaxWarning):
|
class TranslatorCommentWarning(SyntaxWarning):
|
||||||
pass
|
pass
|
||||||
|
|
|
@ -18,7 +18,7 @@ from django.utils._os import upath
|
||||||
from django.utils.safestring import mark_safe, SafeData
|
from django.utils.safestring import mark_safe, SafeData
|
||||||
from django.utils import six, lru_cache
|
from django.utils import six, lru_cache
|
||||||
from django.utils.six import StringIO
|
from django.utils.six import StringIO
|
||||||
from django.utils.translation import TranslatorCommentWarning, trim_whitespace
|
from django.utils.translation import TranslatorCommentWarning, trim_whitespace, LANGUAGE_SESSION_KEY
|
||||||
|
|
||||||
|
|
||||||
# Translations are cached in a dictionary for every language+app tuple.
|
# Translations are cached in a dictionary for every language+app tuple.
|
||||||
|
@ -478,7 +478,7 @@ def get_language_from_request(request, check_path=False):
|
||||||
|
|
||||||
if hasattr(request, 'session'):
|
if hasattr(request, 'session'):
|
||||||
# for backwards compatibility django_language is also checked (remove in 1.8)
|
# for backwards compatibility django_language is also checked (remove in 1.8)
|
||||||
lang_code = request.session.get('_language', request.session.get('django_language'))
|
lang_code = request.session.get(LANGUAGE_SESSION_KEY, request.session.get('django_language'))
|
||||||
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
if lang_code in supported and lang_code is not None and check_for_language(lang_code):
|
||||||
return lang_code
|
return lang_code
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django import http
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.template import Context, Template
|
from django.template import Context, Template
|
||||||
from django.utils.translation import check_for_language, to_locale, get_language
|
from django.utils.translation import check_for_language, to_locale, get_language, LANGUAGE_SESSION_KEY
|
||||||
from django.utils.encoding import smart_text
|
from django.utils.encoding import smart_text
|
||||||
from django.utils.formats import get_format_modules, get_format
|
from django.utils.formats import get_format_modules, get_format
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
|
@ -36,7 +36,7 @@ def set_language(request):
|
||||||
lang_code = request.POST.get('language', None)
|
lang_code = request.POST.get('language', None)
|
||||||
if lang_code and check_for_language(lang_code):
|
if lang_code and check_for_language(lang_code):
|
||||||
if hasattr(request, 'session'):
|
if hasattr(request, 'session'):
|
||||||
request.session['_language'] = lang_code
|
request.session[LANGUAGE_SESSION_KEY] = lang_code
|
||||||
else:
|
else:
|
||||||
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
|
||||||
return response
|
return response
|
||||||
|
|
|
@ -937,6 +937,10 @@ For a complete discussion on the usage of the following see the
|
||||||
so by translating the Django translation tags into standard gettext function
|
so by translating the Django translation tags into standard gettext function
|
||||||
invocations.
|
invocations.
|
||||||
|
|
||||||
|
.. data:: LANGUAGE_SESSION_KEY
|
||||||
|
|
||||||
|
Session key under which the active language for the current session is stored.
|
||||||
|
|
||||||
.. _time-zone-selection-functions:
|
.. _time-zone-selection-functions:
|
||||||
|
|
||||||
``django.utils.timezone``
|
``django.utils.timezone``
|
||||||
|
|
|
@ -537,11 +537,13 @@ Internationalization
|
||||||
attribute allows you to customize the redirects issued by the middleware.
|
attribute allows you to customize the redirects issued by the middleware.
|
||||||
|
|
||||||
* The :class:`~django.middleware.locale.LocaleMiddleware` now stores the user's
|
* The :class:`~django.middleware.locale.LocaleMiddleware` now stores the user's
|
||||||
selected language with the session key ``_language``. Previously it was
|
selected language with the session key ``_language``. This should only be
|
||||||
stored with the key ``django_language``, but keys reserved for Django should
|
accessed using the :data:`~django.utils.translation.LANGUAGE_SESSION_KEY`
|
||||||
start with an underscore. For backwards compatibility ``django_language`` is
|
constant. Previously it was stored with the key ``django_language`` and the
|
||||||
still read from in 1.7. Sessions will be migrated to the new ``_language``
|
``LANGUAGE_SESSION_KEY`` constant did not exist, but keys reserved for Django
|
||||||
key as they are written.
|
should start with an underscore. For backwards compatibility ``django_language``
|
||||||
|
is still read from in 1.7. Sessions will be migrated to the new key
|
||||||
|
as they are written.
|
||||||
|
|
||||||
* The :ttag:`blocktrans` now supports a ``trimmed`` option. This
|
* The :ttag:`blocktrans` now supports a ``trimmed`` option. This
|
||||||
option will remove newline characters from the beginning and the end of the
|
option will remove newline characters from the beginning and the end of the
|
||||||
|
|
|
@ -1510,6 +1510,38 @@ Here's example HTML template code:
|
||||||
In this example, Django looks up the URL of the page to which the user will be
|
In this example, Django looks up the URL of the page to which the user will be
|
||||||
redirected in the ``redirect_to`` context variable.
|
redirected in the ``redirect_to`` context variable.
|
||||||
|
|
||||||
|
Explicitly setting the active language
|
||||||
|
--------------------------------------
|
||||||
|
|
||||||
|
.. highlightlang:: python
|
||||||
|
|
||||||
|
You may want to set the active language for the current session explicitly. Perhaps
|
||||||
|
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::
|
||||||
|
|
||||||
|
from django.utils import translation
|
||||||
|
from django import http
|
||||||
|
from django.conf import settings
|
||||||
|
user_language = 'fr'
|
||||||
|
translation.activate(user_language)
|
||||||
|
response = http.HttpResponse(...)
|
||||||
|
response.set_cookie(settings.LANGUAGE_COOKIE_NAME, user_language)
|
||||||
|
|
||||||
Using translations outside views and templates
|
Using translations outside views and templates
|
||||||
----------------------------------------------
|
----------------------------------------------
|
||||||
|
|
||||||
|
@ -1621,13 +1653,13 @@ following this algorithm:
|
||||||
root URLconf. See :ref:`url-internationalization` for more information
|
root URLconf. See :ref:`url-internationalization` for more information
|
||||||
about the language prefix and how to internationalize URL patterns.
|
about the language prefix and how to internationalize URL patterns.
|
||||||
|
|
||||||
* Failing that, it looks for a ``_language`` key in the current user's session.
|
* Failing that, it looks for the :data:`~django.utils.translation.LANGUAGE_SESSION_KEY`
|
||||||
|
key in the current user's session.
|
||||||
|
|
||||||
.. versionchanged:: 1.7
|
.. versionchanged:: 1.7
|
||||||
|
|
||||||
In previous versions, the key was named ``django_language`` but it was
|
In previous versions, the key was named ``django_language``, and the
|
||||||
renamed to start with an underscore to denote a Django reserved session
|
``LANGUAGE_SESSION_KEY`` constant did not exist.
|
||||||
key.
|
|
||||||
|
|
||||||
* Failing that, it looks for a cookie.
|
* Failing that, it looks for a cookie.
|
||||||
|
|
||||||
|
|
|
@ -31,7 +31,7 @@ from django.utils.translation import (activate, deactivate,
|
||||||
pgettext,
|
pgettext,
|
||||||
npgettext, npgettext_lazy,
|
npgettext, npgettext_lazy,
|
||||||
check_for_language,
|
check_for_language,
|
||||||
string_concat)
|
string_concat, LANGUAGE_SESSION_KEY)
|
||||||
|
|
||||||
from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
|
from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
|
||||||
from .models import Company, TestModel
|
from .models import Company, TestModel
|
||||||
|
@ -1267,7 +1267,7 @@ class LocaleMiddlewareTests(TestCase):
|
||||||
session on every request."""
|
session on every request."""
|
||||||
# Regression test for #21473
|
# Regression test for #21473
|
||||||
self.client.get('/fr/simple/')
|
self.client.get('/fr/simple/')
|
||||||
self.assertNotIn('_language', self.client.session)
|
self.assertNotIn(LANGUAGE_SESSION_KEY, self.client.session)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(
|
@override_settings(
|
||||||
|
|
|
@ -11,7 +11,7 @@ from django.test import (
|
||||||
LiveServerTestCase, TestCase, modify_settings, override_settings)
|
LiveServerTestCase, TestCase, modify_settings, override_settings)
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils._os import upath
|
from django.utils._os import upath
|
||||||
from django.utils.translation import override
|
from django.utils.translation import override, LANGUAGE_SESSION_KEY
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from selenium.webdriver.firefox import webdriver as firefox
|
from selenium.webdriver.firefox import webdriver as firefox
|
||||||
|
@ -35,7 +35,7 @@ class I18NTests(TestCase):
|
||||||
post_data = dict(language=lang_code, next='/')
|
post_data = dict(language=lang_code, next='/')
|
||||||
response = self.client.post('/i18n/setlang/', data=post_data)
|
response = self.client.post('/i18n/setlang/', data=post_data)
|
||||||
self.assertRedirects(response, 'http://testserver/')
|
self.assertRedirects(response, 'http://testserver/')
|
||||||
self.assertEqual(self.client.session['_language'], lang_code)
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
def test_setlang_unsafe_next(self):
|
def test_setlang_unsafe_next(self):
|
||||||
"""
|
"""
|
||||||
|
@ -46,7 +46,7 @@ class I18NTests(TestCase):
|
||||||
post_data = dict(language=lang_code, next='//unsafe/redirection/')
|
post_data = dict(language=lang_code, next='//unsafe/redirection/')
|
||||||
response = self.client.post('/i18n/setlang/', data=post_data)
|
response = self.client.post('/i18n/setlang/', data=post_data)
|
||||||
self.assertEqual(response.url, 'http://testserver/')
|
self.assertEqual(response.url, 'http://testserver/')
|
||||||
self.assertEqual(self.client.session['_language'], lang_code)
|
self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], lang_code)
|
||||||
|
|
||||||
def test_setlang_reversal(self):
|
def test_setlang_reversal(self):
|
||||||
self.assertEqual(reverse('set_language'), '/i18n/setlang/')
|
self.assertEqual(reverse('set_language'), '/i18n/setlang/')
|
||||||
|
|
Loading…
Reference in New Issue