summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRamiro Morales <cramm0@gmail.com>2013-02-03 20:53:48 -0300
committerRamiro Morales <cramm0@gmail.com>2013-02-03 23:40:38 -0300
commit869c9ba30615cb24fb5786787a2db8655f2f0d2b (patch)
tree58051b8ce8ab9531a64e241798b7703cc450736b
parent2c173ff3b45e0a123147e3633082bbf9a7624536 (diff)
Fixed #19730 -- Don't validate importability of settings by using i18n in management commands.
They are handled independently now and the latter can be influenced by the new BaseCommand.leave_locale_alone internal option. Thanks chrischambers for the report, Claude, lpiatek, neaf and gabooo for their work on a patch, originally on refs. #17379.
-rw-r--r--django/core/management/base.py45
-rw-r--r--django/core/management/commands/compilemessages.py2
-rw-r--r--django/core/management/commands/makemessages.py2
-rw-r--r--django/core/management/templates.py3
-rw-r--r--docs/howto/custom-management-commands.txt100
-rw-r--r--docs/releases/1.6.txt8
-rw-r--r--tests/modeltests/user_commands/management/commands/leave_locale_alone_false.py10
-rw-r--r--tests/modeltests/user_commands/management/commands/leave_locale_alone_true.py10
-rw-r--r--tests/modeltests/user_commands/tests.py14
-rw-r--r--tests/regressiontests/i18n/commands/extraction.py5
10 files changed, 153 insertions, 46 deletions
diff --git a/django/core/management/base.py b/django/core/management/base.py
index 7b9001ed14..9dc321a061 100644
--- a/django/core/management/base.py
+++ b/django/core/management/base.py
@@ -143,6 +143,22 @@ class BaseCommand(object):
``self.validate(app)`` from ``handle()``, where ``app`` is the
application's Python module.
+ ``leave_locale_alone``
+ A boolean indicating whether the locale set in settings should be
+ preserved during the execution of the command instead of being
+ forcibly set to 'en-us'.
+
+ Default value is ``False``.
+
+ Make sure you know what you are doing if you decide to change the value
+ of this option in your custom command because many of them create
+ database content that is locale-sensitive (like permissions) and that
+ content shouldn't contain any translations so making the locale differ
+ from the de facto default 'en-us' can cause unintended effects.
+
+ This option can't be False when the can_import_settings option is set
+ to False too because attempting to set the locale needs access to
+ settings. This condition will generate a CommandError.
"""
# Metadata about this command.
option_list = (
@@ -163,6 +179,7 @@ class BaseCommand(object):
can_import_settings = True
requires_model_validation = True
output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
+ leave_locale_alone = False
def __init__(self):
self.style = color_style()
@@ -235,18 +252,28 @@ class BaseCommand(object):
needed (as controlled by the attribute
``self.requires_model_validation``, except if force-skipped).
"""
-
- # Switch to English, because django-admin.py creates database content
- # like permissions, and those shouldn't contain any translations.
- # But only do this if we can assume we have a working settings file,
- # because django.utils.translation requires settings.
- saved_lang = None
self.stdout = OutputWrapper(options.get('stdout', sys.stdout))
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
if self.can_import_settings:
+ from django.conf import settings
+
+ saved_locale = None
+ if not self.leave_locale_alone:
+ # Only mess with locales if we can assume we have a working
+ # settings file, because django.utils.translation requires settings
+ # (The final saying about whether the i18n machinery is active will be
+ # found in the value of the USE_I18N setting)
+ if not self.can_import_settings:
+ raise CommandError("Incompatible values of 'leave_locale_alone' "
+ "(%s) and 'can_import_settings' (%s) command "
+ "options." % (self.leave_locale_alone,
+ self.can_import_settings))
+ # Switch to US English, because django-admin.py creates database
+ # content like permissions, and those shouldn't contain any
+ # translations.
from django.utils import translation
- saved_lang = translation.get_language()
+ saved_locale = translation.get_language()
translation.activate('en-us')
try:
@@ -265,8 +292,8 @@ class BaseCommand(object):
if self.output_transaction:
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
finally:
- if saved_lang is not None:
- translation.activate(saved_lang)
+ if saved_locale is not None:
+ translation.activate(saved_locale)
def validate(self, app=None, display_num_errors=False):
"""
diff --git a/django/core/management/commands/compilemessages.py b/django/core/management/commands/compilemessages.py
index 684ef3514c..8f2c1ff771 100644
--- a/django/core/management/commands/compilemessages.py
+++ b/django/core/management/commands/compilemessages.py
@@ -63,7 +63,7 @@ class Command(BaseCommand):
help = 'Compiles .po files to .mo files for use with builtin gettext support.'
requires_model_validation = False
- can_import_settings = False
+ leave_locale_alone = True
def handle(self, **options):
locale = options.get('locale')
diff --git a/django/core/management/commands/makemessages.py b/django/core/management/commands/makemessages.py
index 4550605af2..daa4c5f023 100644
--- a/django/core/management/commands/makemessages.py
+++ b/django/core/management/commands/makemessages.py
@@ -189,7 +189,7 @@ class Command(NoArgsCommand):
"--locale or --all options.")
requires_model_validation = False
- can_import_settings = False
+ leave_locale_alone = True
def handle_noargs(self, *args, **options):
locale = options.get('locale')
diff --git a/django/core/management/templates.py b/django/core/management/templates.py
index 927dbc13ac..5ce50b4dfd 100644
--- a/django/core/management/templates.py
+++ b/django/core/management/templates.py
@@ -61,6 +61,9 @@ class TemplateCommand(BaseCommand):
can_import_settings = False
# The supported URL schemes
url_schemes = ['http', 'https', 'ftp']
+ # Can't perform any active locale changes during this command, because
+ # setting might not be available at all.
+ leave_locale_alone = True
def handle(self, app_or_project, name, target=None, **options):
self.app_or_project = app_or_project
diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt
index 6a7f644218..eba187bb37 100644
--- a/docs/howto/custom-management-commands.txt
+++ b/docs/howto/custom-management-commands.txt
@@ -112,54 +112,61 @@ In addition to being able to add custom command line options, all
:doc:`management commands</ref/django-admin>` can accept some
default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`.
-.. admonition:: Management commands and locales
+.. _management-commands-and-locales:
- The :meth:`BaseCommand.execute` method sets the hardcoded ``en-us`` locale
- because the commands shipped with Django perform several tasks
- (for example, user-facing content rendering and database population) that
- require a system-neutral string language (for which we use ``en-us``).
+Management commands and locales
+===============================
- If your custom management command uses another locale, you should manually
- activate and deactivate it in your :meth:`~BaseCommand.handle` or
- :meth:`~NoArgsCommand.handle_noargs` method using the functions provided by
- the I18N support code:
+By default, the :meth:`BaseCommand.execute` method sets the hardcoded 'en-us'
+locale because most of the commands shipped with Django perform several tasks
+(for example, user-facing content rendering and database population) that
+require a system-neutral string language (for which we use 'en-us').
- .. code-block:: python
+If, for some reason, your custom management command needs to use a fixed locale
+different from 'en-us', you should manually activate and deactivate it in your
+:meth:`~BaseCommand.handle` or :meth:`~NoArgsCommand.handle_noargs` method using
+the functions provided by the I18N support code:
- from django.core.management.base import BaseCommand, CommandError
- from django.utils import translation
+.. code-block:: python
- class Command(BaseCommand):
- ...
- can_import_settings = True
+ from django.core.management.base import BaseCommand, CommandError
+ from django.utils import translation
- def handle(self, *args, **options):
+ class Command(BaseCommand):
+ ...
+ can_import_settings = True
- # Activate a fixed locale, e.g. Russian
- translation.activate('ru')
+ def handle(self, *args, **options):
- # Or you can activate the LANGUAGE_CODE
- # chosen in the settings:
- #
- #from django.conf import settings
- #translation.activate(settings.LANGUAGE_CODE)
+ # Activate a fixed locale, e.g. Russian
+ translation.activate('ru')
+
+ # Or you can activate the LANGUAGE_CODE # chosen in the settings:
+ #
+ #from django.conf import settings
+ #translation.activate(settings.LANGUAGE_CODE)
+
+ # Your command logic here
+ # ...
- # Your command logic here
- # ...
+ translation.deactivate()
- translation.deactivate()
+Another need might be that your command simply should use the locale set in
+settings and Django should be kept from forcing it to 'en-us'. You can achieve
+it by using the :data:`BaseCommand.leave_locale_alone` option.
- Take into account though, that system management commands typically have to
- be very careful about running in non-uniform locales, so:
+When working on the scenarios described above though, take into account that
+system management commands typically have to be very careful about running in
+non-uniform locales, so you might need to:
- * Make sure the :setting:`USE_I18N` setting is always ``True`` when running
- the command (this is one good example of the potential problems stemming
- from a dynamic runtime environment that Django commands avoid offhand by
- always using a fixed locale).
+* Make sure the :setting:`USE_I18N` setting is always ``True`` when running
+ the command (this is a good example of the potential problems stemming
+ from a dynamic runtime environment that Django commands avoid offhand by
+ always using a fixed locale).
- * Review the code of your command and the code it calls for behavioral
- differences when locales are changed and evaluate its impact on
- predictable behavior of your command.
+* Review the code of your command and the code it calls for behavioral
+ differences when locales are changed and evaluate its impact on
+ predictable behavior of your command.
Command objects
===============
@@ -222,6 +229,29 @@ All attributes can be set in your derived class and can be used in
rather than all applications' models, call
:meth:`~BaseCommand.validate` from :meth:`~BaseCommand.handle`.
+.. attribute:: BaseCommand.leave_locale_alone
+
+ A boolean indicating whether the locale set in settings should be preserved
+ during the execution of the command instead of being forcibly set to 'en-us'.
+
+ Default value is ``False``.
+
+ Make sure you know what you are doing if you decide to change the value of
+ this option in your custom command because many of them create database
+ content that is locale-sensitive (like permissions) and that content
+ shouldn't contain any translations so making the locale differ from the de
+ facto default 'en-us' can cause unintended effects. See the `Management
+ commands and locales`_ section above for further details.
+
+ This option can't be ``False`` when the
+ :data:`~BaseCommand.can_import_settings` option is set to ``False`` too
+ because attempting to set the locale needs access to settings. This condition
+ will generate a :class:`CommandError`.
+
+.. versionadded:: 1.6
+
+ The ``leave_locale_alone`` option was added in Django 1.6.
+
Methods
-------
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 5e1d959f60..acc273cef4 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -39,6 +39,14 @@ Minor features
<lazy-plural-translations>` can be provided at translation time rather than
at definition time.
+* For custom managemente commands: Validation of the presence of valid settings
+ in managements commands that ask for it by using the
+ :attr:`~django.core.management.BaseCommand.can_import_settings` internal
+ option is now performed independently from handling of the locale that should
+ active during the execution of the command. The latter can now be influenced
+ by the new :attr:`~django.core.management.BaseCommand.leave_locale_alone`
+ internal option. See :ref:`management-commands-and-locales` for more details.
+
Backwards incompatible changes in 1.6
=====================================
diff --git a/tests/modeltests/user_commands/management/commands/leave_locale_alone_false.py b/tests/modeltests/user_commands/management/commands/leave_locale_alone_false.py
new file mode 100644
index 0000000000..8ebb607d5a
--- /dev/null
+++ b/tests/modeltests/user_commands/management/commands/leave_locale_alone_false.py
@@ -0,0 +1,10 @@
+from django.core.management.base import BaseCommand
+from django.utils import translation
+
+class Command(BaseCommand):
+
+ can_import_settings = True
+ leave_locale_alone = False
+
+ def handle(self, *args, **options):
+ return translation.get_language()
diff --git a/tests/modeltests/user_commands/management/commands/leave_locale_alone_true.py b/tests/modeltests/user_commands/management/commands/leave_locale_alone_true.py
new file mode 100644
index 0000000000..e0f923591e
--- /dev/null
+++ b/tests/modeltests/user_commands/management/commands/leave_locale_alone_true.py
@@ -0,0 +1,10 @@
+from django.core.management.base import BaseCommand
+from django.utils import translation
+
+class Command(BaseCommand):
+
+ can_import_settings = True
+ leave_locale_alone = True
+
+ def handle(self, *args, **options):
+ return translation.get_language()
diff --git a/tests/modeltests/user_commands/tests.py b/tests/modeltests/user_commands/tests.py
index d25911c09f..c8740577a5 100644
--- a/tests/modeltests/user_commands/tests.py
+++ b/tests/modeltests/user_commands/tests.py
@@ -44,3 +44,17 @@ class CommandTests(TestCase):
finally:
sys.stderr = old_stderr
self.assertIn("CommandError", err.getvalue())
+
+ def test_default_en_us_locale_set(self):
+ # Forces en_us when set to true
+ out = StringIO()
+ with translation.override('pl'):
+ management.call_command('leave_locale_alone_false', stdout=out)
+ self.assertEqual(out.getvalue(), "en-us\n")
+
+ def test_configured_locale_preserved(self):
+ # Leaves locale from settings when set to false
+ out = StringIO()
+ with translation.override('pl'):
+ management.call_command('leave_locale_alone_true', stdout=out)
+ self.assertEqual(out.getvalue(), "pl\n")
diff --git a/tests/regressiontests/i18n/commands/extraction.py b/tests/regressiontests/i18n/commands/extraction.py
index 0367d23ec6..ca89f30de8 100644
--- a/tests/regressiontests/i18n/commands/extraction.py
+++ b/tests/regressiontests/i18n/commands/extraction.py
@@ -110,6 +110,11 @@ class BasicExtractorTests(ExtractorTests):
self.assertMsgId('I think that 100%% is more that 50%% of %(obj)s.', po_contents)
self.assertMsgId("Blocktrans extraction shouldn't double escape this: %%, a=%(a)s", po_contents)
+ def test_force_en_us_locale(self):
+ """Value of locale-munging option used by the command is the right one"""
+ from django.core.management.commands.makemessages import Command
+ self.assertTrue(Command.leave_locale_alone)
+
def test_extraction_error(self):
os.chdir(self.test_dir)
self.assertRaises(SyntaxError, management.call_command, 'makemessages', locale=LOCALE, extensions=['tpl'], verbosity=0)