Fixed #22120 -- Documented persistent activation of languages and cleaned up language session key use

This commit is contained in:
Erik Romijn 2014-02-22 14:27:57 +01:00 committed by Baptiste Mispelon
parent 09b725f51b
commit 8cd32f0965
10 changed files with 63 additions and 22 deletions

View File

@ -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

View File

@ -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

View File

@ -21,6 +21,7 @@ __all__ = [
'npgettext', 'npgettext_lazy', 'npgettext', 'npgettext_lazy',
] ]
LANGUAGE_SESSION_KEY = '_language'
class TranslatorCommentWarning(SyntaxWarning): class TranslatorCommentWarning(SyntaxWarning):
pass pass

View File

@ -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

View File

@ -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

View File

@ -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``

View File

@ -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

View File

@ -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.

View File

@ -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(

View File

@ -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/')