From 8c98f39624a60c63a16e097b64e5f71ecc27271f Mon Sep 17 00:00:00 2001 From: Sergey Kolosov Date: Sun, 19 May 2013 12:20:34 +0200 Subject: [PATCH] Fixed #15318 -- Added settings for language cookie max-age, path, domain Introduced a number of settings to configure max-age, path, and domain for the language cookie: LANGUAGE_COOKIE_AGE, LANGUAGE_COOKIE_PATH and LANGUAGE_COOKIE_DOMAIN. Thanks sahid for the suggestion. --- django/conf/global_settings.py | 6 +++ django/views/i18n.py | 5 ++- docs/ref/settings.txt | 62 +++++++++++++++++++++++++++++ docs/releases/1.7.txt | 4 ++ docs/topics/i18n/translation.txt | 14 +++++++ tests/view_tests/tests/test_i18n.py | 19 +++++++++ 6 files changed, 109 insertions(+), 1 deletion(-) diff --git a/django/conf/global_settings.py b/django/conf/global_settings.py index 5e4a42370d..2bce9aa7d8 100644 --- a/django/conf/global_settings.py +++ b/django/conf/global_settings.py @@ -140,7 +140,13 @@ LANGUAGES_BIDI = ("he", "ar", "fa", "ur") # to load the internationalization machinery. USE_I18N = True LOCALE_PATHS = () + +# Settings for language cookie LANGUAGE_COOKIE_NAME = 'django_language' +LANGUAGE_COOKIE_AGE = None +LANGUAGE_COOKIE_DOMAIN = None +LANGUAGE_COOKIE_PATH = '/' + # If you set this to True, Django will format dates, numbers and calendars # according to user current locale. diff --git a/django/views/i18n.py b/django/views/i18n.py index a62ca29e54..7724a2eb42 100644 --- a/django/views/i18n.py +++ b/django/views/i18n.py @@ -38,7 +38,10 @@ def set_language(request): if hasattr(request, 'session'): request.session[LANGUAGE_SESSION_KEY] = lang_code else: - response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code) + response.set_cookie(settings.LANGUAGE_COOKIE_NAME, lang_code, + max_age=settings.LANGUAGE_COOKIE_AGE, + path=settings.LANGUAGE_COOKIE_PATH, + domain=settings.LANGUAGE_COOKIE_DOMAIN) return response diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt index 508ad4b56f..27bc2274dc 100644 --- a/docs/ref/settings.txt +++ b/docs/ref/settings.txt @@ -1362,6 +1362,40 @@ See :ref:`how-django-discovers-language-preference` for more details. .. _list of language identifiers: http://www.i18nguy.com/unicode/language-identifiers.html +.. setting:: LANGUAGE_COOKIE_AGE + +LANGUAGE_COOKIE_AGE +------------------- + +.. versionadded:: 1.7 + +Default: ``None`` (expires at browser close) + +The age of the language cookie, in seconds. + +.. setting:: LANGUAGE_COOKIE_DOMAIN + +LANGUAGE_COOKIE_DOMAIN +---------------------- + +.. versionadded:: 1.7 + +Default: ``None`` + +The domain to use for the language cookie. Set this to a string such as +``".example.com"`` (note the leading dot!) for cross-domain cookies, or use +``None`` for a standard domain cookie. + +Be cautious when updating this setting on a production site. If you update +this setting to enable cross-domain cookies on a site that previously used +standard domain cookies, existing user cookies that have the old domain +will not be updated. This will result in site users being unable to switch +the language as long as these cookies persist. The only safe and reliable +option to perform the switch is to change the language cookie name +permanently (via the :setting:`SESSION_COOKIE_NAME` setting) and to add +a middleware that copies the value from the old cookie to a new one and then +deletes the old one. + .. setting:: LANGUAGE_COOKIE_NAME LANGUAGE_COOKIE_NAME @@ -1373,6 +1407,31 @@ The name of the cookie to use for the language cookie. This can be whatever you want (but should be different from :setting:`SESSION_COOKIE_NAME`). See :doc:`/topics/i18n/index`. +.. setting:: LANGUAGE_COOKIE_PATH + +LANGUAGE_COOKIE_PATH +-------------------- + +.. versionadded:: 1.7 + +Default: ``/`` + +The path set on the language cookie. This should either match the URL path of your +Django installation or be a parent of that path. + +This is useful if you have multiple Django instances running under the same +hostname. They can use different cookie paths and each instance will only see +its own language cookie. + +Be cautious when updating this setting on a production site. If you update this +setting to use a deeper path than it previously used, existing user cookies that +have the old path will not be updated. This will result in site users being +unable to switch the language as long as these cookies persist. The only safe +and reliable option to perform the switch is to change the language cookie name +permanently (via the :setting:`SESSION_COOKIE_NAME` setting), and to add +a middleware that copies the value from the old cookie to a new one and then +deletes the one. + .. setting:: LANGUAGES LANGUAGES @@ -2801,7 +2860,10 @@ Globalization (i18n/l10n) * :setting:`FIRST_DAY_OF_WEEK` * :setting:`FORMAT_MODULE_PATH` * :setting:`LANGUAGE_CODE` +* :setting:`LANGUAGE_COOKIE_AGE` +* :setting:`LANGUAGE_COOKIE_DOMAIN` * :setting:`LANGUAGE_COOKIE_NAME` +* :setting:`LANGUAGE_COOKIE_PATH` * :setting:`LANGUAGES` * :setting:`LOCALE_PATHS` * :setting:`MONTH_DAY_FORMAT` diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt index 4d1890b3d9..98b336f630 100644 --- a/docs/releases/1.7.txt +++ b/docs/releases/1.7.txt @@ -559,6 +559,10 @@ Internationalization app or project message file. See :ref:`how-to-create-language-files` for details. +* The following settings to adjust the language cookie options were introduced: + :setting:`LANGUAGE_COOKIE_AGE`, :setting:`LANGUAGE_COOKIE_DOMAIN` + and :setting:`LANGUAGE_COOKIE_PATH`. + * Added :ref:`format definitions ` for Esperanto. Management Commands diff --git a/docs/topics/i18n/translation.txt b/docs/topics/i18n/translation.txt index 0a5fa925bc..2a798ee323 100644 --- a/docs/topics/i18n/translation.txt +++ b/docs/topics/i18n/translation.txt @@ -1575,6 +1575,20 @@ which returns the language used in the current thread, for the current thread, and ``django.utils.translation.check_for_language()`` which checks if the given language is supported by Django. +Language cookie +--------------- + +A number of settings can be used to adjust language cookie options: + +* :setting:`LANGUAGE_COOKIE_NAME` + +.. versionadded:: 1.7 + +* :setting:`LANGUAGE_COOKIE_AGE` +* :setting:`LANGUAGE_COOKIE_DOMAIN` +* :setting:`LANGUAGE_COOKIE_PATH` + + Implementation notes ==================== diff --git a/tests/view_tests/tests/test_i18n.py b/tests/view_tests/tests/test_i18n.py index 7d6466d44f..d837f4bc90 100644 --- a/tests/view_tests/tests/test_i18n.py +++ b/tests/view_tests/tests/test_i18n.py @@ -51,6 +51,25 @@ class I18NTests(TestCase): def test_setlang_reversal(self): self.assertEqual(reverse('set_language'), '/i18n/setlang/') + def test_setlang_cookie(self): + # we force saving language to a cookie rather than a session + # by excluding session middleware and those which do require it + test_settings = dict( + MIDDLEWARE_CLASSES=('django.middleware.common.CommonMiddleware',), + LANGUAGE_COOKIE_NAME='mylanguage', + LANGUAGE_COOKIE_AGE=3600 * 7 * 2, + LANGUAGE_COOKIE_DOMAIN='.example.com', + LANGUAGE_COOKIE_PATH='/test/', + ) + with self.settings(**test_settings): + post_data = dict(language='pl', next='/views/') + response = self.client.post('/i18n/setlang/', data=post_data) + language_cookie = response.cookies.get('mylanguage') + self.assertEqual(language_cookie.value, 'pl') + self.assertEqual(language_cookie['domain'], '.example.com') + self.assertEqual(language_cookie['path'], '/test/') + self.assertEqual(language_cookie['max-age'], 3600 * 7 * 2) + def test_jsi18n(self): """The javascript_catalog can be deployed with language settings""" for lang_code in ['es', 'fr', 'ru']: