Browse Source

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

Erik Romijn 11 years ago
parent
commit
8cd32f0965

+ 3 - 2
django/contrib/auth/__init__.py

@@ -5,6 +5,7 @@ from django.apps import apps as django_apps
 from django.conf import settings
 from django.core.exceptions import ImproperlyConfigured, PermissionDenied
 from django.utils.module_loading import import_string
+from django.utils.translation import LANGUAGE_SESSION_KEY
 from django.middleware.csrf import rotate_token
 
 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
     # 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()
 
     if language is not None:
-        request.session['_language'] = language
+        request.session[LANGUAGE_SESSION_KEY] = language
 
     if hasattr(request, 'user'):
         from django.contrib.auth.models import AnonymousUser

+ 3 - 2
django/contrib/auth/tests/test_views.py

@@ -14,6 +14,7 @@ from django.http import QueryDict, HttpRequest
 from django.utils.encoding import force_text
 from django.utils.http import urlquote
 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.test import TestCase, override_settings
 from django.test.utils import patch_logger
@@ -718,12 +719,12 @@ class LogoutTest(AuthViewsTestCase):
         # Create a new session with language
         engine = import_module(settings.SESSION_ENGINE)
         session = engine.SessionStore()
-        session['_language'] = 'pl'
+        session[LANGUAGE_SESSION_KEY] = 'pl'
         session.save()
         self.client.cookies[settings.SESSION_COOKIE_NAME] = session.session_key
 
         self.client.get('/logout/')
-        self.assertEqual(self.client.session['_language'], 'pl')
+        self.assertEqual(self.client.session[LANGUAGE_SESSION_KEY], 'pl')
 
 
 @skipIfCustomUser

+ 1 - 0
django/utils/translation/__init__.py

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

+ 2 - 2
django/utils/translation/trans_real.py

@@ -18,7 +18,7 @@ from django.utils._os import upath
 from django.utils.safestring import mark_safe, SafeData
 from django.utils import six, lru_cache
 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.
@@ -478,7 +478,7 @@ def get_language_from_request(request, check_path=False):
 
     if hasattr(request, 'session'):
         # 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):
             return lang_code
 

+ 2 - 2
django/views/i18n.py

@@ -7,7 +7,7 @@ from django import http
 from django.apps import apps
 from django.conf import settings
 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.formats import get_format_modules, get_format
 from django.utils._os import upath
@@ -36,7 +36,7 @@ def set_language(request):
         lang_code = request.POST.get('language', None)
         if lang_code and check_for_language(lang_code):
             if hasattr(request, 'session'):
-                request.session['_language'] = lang_code
+                request.session[LANGUAGE_SESSION_KEY] = lang_code
             else:
                 response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code)
     return response

+ 4 - 0
docs/ref/utils.txt

@@ -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
     invocations.
 
+.. data:: LANGUAGE_SESSION_KEY
+
+    Session key under which the active language for the current session is stored.
+
 .. _time-zone-selection-functions:
 
 ``django.utils.timezone``

+ 7 - 5
docs/releases/1.7.txt

@@ -537,11 +537,13 @@ Internationalization
   attribute allows you to customize the redirects issued by the middleware.
 
 * The :class:`~django.middleware.locale.LocaleMiddleware` now stores the user's
-  selected language with the session key ``_language``. Previously it was
-  stored with the key ``django_language``, but keys reserved for Django should
-  start with an underscore. For backwards compatibility ``django_language`` is
-  still read from in 1.7. Sessions will be migrated to the new ``_language``
-  key as they are written.
+  selected language with the session key ``_language``. This should only be
+  accessed using the :data:`~django.utils.translation.LANGUAGE_SESSION_KEY`
+  constant. Previously it was stored with the key ``django_language`` and the
+  ``LANGUAGE_SESSION_KEY`` constant did not exist, but keys reserved for Django
+  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
   option will remove newline characters from the beginning and the end of the

+ 36 - 4
docs/topics/i18n/translation.txt

@@ -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
 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
 ----------------------------------------------
 
@@ -1621,13 +1653,13 @@ 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 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
 
-      In previous versions, the key was named ``django_language`` but it was
-      renamed to start with an underscore to denote a Django reserved session
-      key.
+      In previous versions, the key was named ``django_language``, and the
+      ``LANGUAGE_SESSION_KEY`` constant did not exist.
 
 * Failing that, it looks for a cookie.
 

+ 2 - 2
tests/i18n/tests.py

@@ -31,7 +31,7 @@ from django.utils.translation import (activate, deactivate,
     pgettext,
     npgettext, npgettext_lazy,
     check_for_language,
-    string_concat)
+    string_concat, LANGUAGE_SESSION_KEY)
 
 from .forms import I18nForm, SelectDateForm, SelectDateWidget, CompanyForm
 from .models import Company, TestModel
@@ -1267,7 +1267,7 @@ class LocaleMiddlewareTests(TestCase):
         session on every request."""
         # Regression test for #21473
         self.client.get('/fr/simple/')
-        self.assertNotIn('_language', self.client.session)
+        self.assertNotIn(LANGUAGE_SESSION_KEY, self.client.session)
 
 
 @override_settings(

+ 3 - 3
tests/view_tests/tests/test_i18n.py

@@ -11,7 +11,7 @@ from django.test import (
     LiveServerTestCase, TestCase, modify_settings, override_settings)
 from django.utils import six
 from django.utils._os import upath
-from django.utils.translation import override
+from django.utils.translation import override, LANGUAGE_SESSION_KEY
 
 try:
     from selenium.webdriver.firefox import webdriver as firefox
@@ -35,7 +35,7 @@ class I18NTests(TestCase):
             post_data = dict(language=lang_code, next='/')
             response = self.client.post('/i18n/setlang/', data=post_data)
             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):
         """
@@ -46,7 +46,7 @@ class I18NTests(TestCase):
         post_data = dict(language=lang_code, next='//unsafe/redirection/')
         response = self.client.post('/i18n/setlang/', data=post_data)
         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):
         self.assertEqual(reverse('set_language'), '/i18n/setlang/')