mirror of https://github.com/django/django.git
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.
This commit is contained in:
parent
2c173ff3b4
commit
869c9ba306
|
@ -143,6 +143,22 @@ class BaseCommand(object):
|
||||||
``self.validate(app)`` from ``handle()``, where ``app`` is the
|
``self.validate(app)`` from ``handle()``, where ``app`` is the
|
||||||
application's Python module.
|
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.
|
# Metadata about this command.
|
||||||
option_list = (
|
option_list = (
|
||||||
|
@ -163,6 +179,7 @@ class BaseCommand(object):
|
||||||
can_import_settings = True
|
can_import_settings = True
|
||||||
requires_model_validation = True
|
requires_model_validation = True
|
||||||
output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
|
output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
|
||||||
|
leave_locale_alone = False
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.style = color_style()
|
self.style = color_style()
|
||||||
|
@ -235,18 +252,28 @@ class BaseCommand(object):
|
||||||
needed (as controlled by the attribute
|
needed (as controlled by the attribute
|
||||||
``self.requires_model_validation``, except if force-skipped).
|
``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.stdout = OutputWrapper(options.get('stdout', sys.stdout))
|
||||||
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
|
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
|
||||||
|
|
||||||
if self.can_import_settings:
|
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
|
from django.utils import translation
|
||||||
saved_lang = translation.get_language()
|
saved_locale = translation.get_language()
|
||||||
translation.activate('en-us')
|
translation.activate('en-us')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -265,8 +292,8 @@ class BaseCommand(object):
|
||||||
if self.output_transaction:
|
if self.output_transaction:
|
||||||
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
||||||
finally:
|
finally:
|
||||||
if saved_lang is not None:
|
if saved_locale is not None:
|
||||||
translation.activate(saved_lang)
|
translation.activate(saved_locale)
|
||||||
|
|
||||||
def validate(self, app=None, display_num_errors=False):
|
def validate(self, app=None, display_num_errors=False):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -63,7 +63,7 @@ class Command(BaseCommand):
|
||||||
help = 'Compiles .po files to .mo files for use with builtin gettext support.'
|
help = 'Compiles .po files to .mo files for use with builtin gettext support.'
|
||||||
|
|
||||||
requires_model_validation = False
|
requires_model_validation = False
|
||||||
can_import_settings = False
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle(self, **options):
|
def handle(self, **options):
|
||||||
locale = options.get('locale')
|
locale = options.get('locale')
|
||||||
|
|
|
@ -189,7 +189,7 @@ class Command(NoArgsCommand):
|
||||||
"--locale or --all options.")
|
"--locale or --all options.")
|
||||||
|
|
||||||
requires_model_validation = False
|
requires_model_validation = False
|
||||||
can_import_settings = False
|
leave_locale_alone = True
|
||||||
|
|
||||||
def handle_noargs(self, *args, **options):
|
def handle_noargs(self, *args, **options):
|
||||||
locale = options.get('locale')
|
locale = options.get('locale')
|
||||||
|
|
|
@ -61,6 +61,9 @@ class TemplateCommand(BaseCommand):
|
||||||
can_import_settings = False
|
can_import_settings = False
|
||||||
# The supported URL schemes
|
# The supported URL schemes
|
||||||
url_schemes = ['http', 'https', 'ftp']
|
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):
|
def handle(self, app_or_project, name, target=None, **options):
|
||||||
self.app_or_project = app_or_project
|
self.app_or_project = app_or_project
|
||||||
|
|
|
@ -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
|
:doc:`management commands</ref/django-admin>` can accept some
|
||||||
default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`.
|
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
|
Management commands and locales
|
||||||
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``).
|
|
||||||
|
|
||||||
If your custom management command uses another locale, you should manually
|
By default, the :meth:`BaseCommand.execute` method sets the hardcoded 'en-us'
|
||||||
activate and deactivate it in your :meth:`~BaseCommand.handle` or
|
locale because most of the commands shipped with Django perform several tasks
|
||||||
:meth:`~NoArgsCommand.handle_noargs` method using the functions provided by
|
(for example, user-facing content rendering and database population) that
|
||||||
the I18N support code:
|
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
|
.. code-block:: python
|
||||||
from django.utils import translation
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
...
|
from django.utils import translation
|
||||||
can_import_settings = True
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
class Command(BaseCommand):
|
||||||
|
...
|
||||||
|
can_import_settings = True
|
||||||
|
|
||||||
# Activate a fixed locale, e.g. Russian
|
def handle(self, *args, **options):
|
||||||
translation.activate('ru')
|
|
||||||
|
|
||||||
# Or you can activate the LANGUAGE_CODE
|
# Activate a fixed locale, e.g. Russian
|
||||||
# chosen in the settings:
|
translation.activate('ru')
|
||||||
#
|
|
||||||
#from django.conf import settings
|
|
||||||
#translation.activate(settings.LANGUAGE_CODE)
|
|
||||||
|
|
||||||
# Your command logic here
|
# Or you can activate the LANGUAGE_CODE # chosen in the settings:
|
||||||
# ...
|
#
|
||||||
|
#from django.conf import settings
|
||||||
|
#translation.activate(settings.LANGUAGE_CODE)
|
||||||
|
|
||||||
translation.deactivate()
|
# Your command logic here
|
||||||
|
# ...
|
||||||
|
|
||||||
Take into account though, that system management commands typically have to
|
translation.deactivate()
|
||||||
be very careful about running in non-uniform locales, so:
|
|
||||||
|
|
||||||
* Make sure the :setting:`USE_I18N` setting is always ``True`` when running
|
Another need might be that your command simply should use the locale set in
|
||||||
the command (this is one good example of the potential problems stemming
|
settings and Django should be kept from forcing it to 'en-us'. You can achieve
|
||||||
from a dynamic runtime environment that Django commands avoid offhand by
|
it by using the :data:`BaseCommand.leave_locale_alone` option.
|
||||||
always using a fixed locale).
|
|
||||||
|
|
||||||
* Review the code of your command and the code it calls for behavioral
|
When working on the scenarios described above though, take into account that
|
||||||
differences when locales are changed and evaluate its impact on
|
system management commands typically have to be very careful about running in
|
||||||
predictable behavior of your command.
|
non-uniform locales, so you might need to:
|
||||||
|
|
||||||
|
* 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.
|
||||||
|
|
||||||
Command objects
|
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
|
rather than all applications' models, call
|
||||||
:meth:`~BaseCommand.validate` from :meth:`~BaseCommand.handle`.
|
: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
|
Methods
|
||||||
-------
|
-------
|
||||||
|
|
||||||
|
|
|
@ -39,6 +39,14 @@ Minor features
|
||||||
<lazy-plural-translations>` can be provided at translation time rather than
|
<lazy-plural-translations>` can be provided at translation time rather than
|
||||||
at definition time.
|
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
|
Backwards incompatible changes in 1.6
|
||||||
=====================================
|
=====================================
|
||||||
|
|
||||||
|
|
|
@ -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()
|
|
@ -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()
|
|
@ -44,3 +44,17 @@ class CommandTests(TestCase):
|
||||||
finally:
|
finally:
|
||||||
sys.stderr = old_stderr
|
sys.stderr = old_stderr
|
||||||
self.assertIn("CommandError", err.getvalue())
|
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")
|
||||||
|
|
|
@ -110,6 +110,11 @@ class BasicExtractorTests(ExtractorTests):
|
||||||
self.assertMsgId('I think that 100%% is more that 50%% of %(obj)s.', po_contents)
|
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)
|
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):
|
def test_extraction_error(self):
|
||||||
os.chdir(self.test_dir)
|
os.chdir(self.test_dir)
|
||||||
self.assertRaises(SyntaxError, management.call_command, 'makemessages', locale=LOCALE, extensions=['tpl'], verbosity=0)
|
self.assertRaises(SyntaxError, management.call_command, 'makemessages', locale=LOCALE, extensions=['tpl'], verbosity=0)
|
||||||
|
|
Loading…
Reference in New Issue