Fixed #17379 -- Removed management commands deactivation of the locale.
This commit is contained in:
parent
1e0cbc72e5
commit
d65b0f72de
|
@ -124,6 +124,11 @@ class Apps:
|
||||||
def check_apps_ready(self):
|
def check_apps_ready(self):
|
||||||
"""Raise an exception if all apps haven't been imported yet."""
|
"""Raise an exception if all apps haven't been imported yet."""
|
||||||
if not self.apps_ready:
|
if not self.apps_ready:
|
||||||
|
from django.conf import settings
|
||||||
|
# If "not ready" is due to unconfigured settings, accessing
|
||||||
|
# INSTALLED_APPS raises a more helpful ImproperlyConfigured
|
||||||
|
# exception.
|
||||||
|
settings.INSTALLED_APPS
|
||||||
raise AppRegistryNotReady("Apps aren't loaded yet.")
|
raise AppRegistryNotReady("Apps aren't loaded yet.")
|
||||||
|
|
||||||
def check_models_ready(self):
|
def check_models_ready(self):
|
||||||
|
|
|
@ -73,6 +73,21 @@ def handle_default_options(options):
|
||||||
sys.path.insert(0, options.pythonpath)
|
sys.path.insert(0, options.pythonpath)
|
||||||
|
|
||||||
|
|
||||||
|
def no_translations(handle_func):
|
||||||
|
"""Decorator that forces a command to run with translations deactivated."""
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
from django.utils import translation
|
||||||
|
saved_locale = translation.get_language()
|
||||||
|
translation.deactivate_all()
|
||||||
|
try:
|
||||||
|
res = handle_func(*args, **kwargs)
|
||||||
|
finally:
|
||||||
|
if saved_locale is not None:
|
||||||
|
translation.activate(saved_locale)
|
||||||
|
return res
|
||||||
|
return wrapped
|
||||||
|
|
||||||
|
|
||||||
class OutputWrapper(TextIOBase):
|
class OutputWrapper(TextIOBase):
|
||||||
"""
|
"""
|
||||||
Wrapper around stdout/stderr
|
Wrapper around stdout/stderr
|
||||||
|
@ -171,19 +186,6 @@ class BaseCommand:
|
||||||
is the list of application's configuration provided by the
|
is the list of application's configuration provided by the
|
||||||
app registry.
|
app registry.
|
||||||
|
|
||||||
``leave_locale_alone``
|
|
||||||
A boolean indicating whether the locale set in settings should be
|
|
||||||
preserved during the execution of the command instead of translations
|
|
||||||
being deactivated.
|
|
||||||
|
|
||||||
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 if it creates database content
|
|
||||||
that is locale-sensitive and such content shouldn't contain any
|
|
||||||
translations (like it happens e.g. with django.contrib.auth
|
|
||||||
permissions) as activating any locale might cause unintended effects.
|
|
||||||
|
|
||||||
``stealth_options``
|
``stealth_options``
|
||||||
A tuple of any options the command uses which aren't defined by the
|
A tuple of any options the command uses which aren't defined by the
|
||||||
argument parser.
|
argument parser.
|
||||||
|
@ -194,7 +196,6 @@ class BaseCommand:
|
||||||
# Configuration shortcuts that alter various logic.
|
# Configuration shortcuts that alter various logic.
|
||||||
_called_from_command_line = False
|
_called_from_command_line = False
|
||||||
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
|
|
||||||
requires_migrations_checks = False
|
requires_migrations_checks = False
|
||||||
requires_system_checks = True
|
requires_system_checks = True
|
||||||
# Arguments, common to all commands, which aren't defined by the argument
|
# Arguments, common to all commands, which aren't defined by the argument
|
||||||
|
@ -323,16 +324,6 @@ class BaseCommand:
|
||||||
if options.get('stderr'):
|
if options.get('stderr'):
|
||||||
self.stderr = OutputWrapper(options['stderr'], self.stderr.style_func)
|
self.stderr = OutputWrapper(options['stderr'], self.stderr.style_func)
|
||||||
|
|
||||||
saved_locale = None
|
|
||||||
if not self.leave_locale_alone:
|
|
||||||
# Deactivate translations, because django-admin creates database
|
|
||||||
# content like permissions, and those shouldn't contain any
|
|
||||||
# translations.
|
|
||||||
from django.utils import translation
|
|
||||||
saved_locale = translation.get_language()
|
|
||||||
translation.deactivate_all()
|
|
||||||
|
|
||||||
try:
|
|
||||||
if self.requires_system_checks and not options.get('skip_checks'):
|
if self.requires_system_checks and not options.get('skip_checks'):
|
||||||
self.check()
|
self.check()
|
||||||
if self.requires_migrations_checks:
|
if self.requires_migrations_checks:
|
||||||
|
@ -347,9 +338,6 @@ class BaseCommand:
|
||||||
self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
|
self.style.SQL_KEYWORD(connection.ops.end_transaction_sql()),
|
||||||
)
|
)
|
||||||
self.stdout.write(output)
|
self.stdout.write(output)
|
||||||
finally:
|
|
||||||
if saved_locale is not None:
|
|
||||||
translation.activate(saved_locale)
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def _run_checks(self, **kwargs):
|
def _run_checks(self, **kwargs):
|
||||||
|
|
|
@ -27,7 +27,6 @@ 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_system_checks = False
|
requires_system_checks = False
|
||||||
leave_locale_alone = True
|
|
||||||
|
|
||||||
program = 'msgfmt'
|
program = 'msgfmt'
|
||||||
program_options = ['--check-format']
|
program_options = ['--check-format']
|
||||||
|
|
|
@ -207,7 +207,6 @@ class Command(BaseCommand):
|
||||||
build_file_class = BuildFile
|
build_file_class = BuildFile
|
||||||
|
|
||||||
requires_system_checks = False
|
requires_system_checks = False
|
||||||
leave_locale_alone = True
|
|
||||||
|
|
||||||
msgmerge_options = ['-q', '--previous']
|
msgmerge_options = ['-q', '--previous']
|
||||||
msguniq_options = ['--to-code=utf-8']
|
msguniq_options = ['--to-code=utf-8']
|
||||||
|
|
|
@ -4,7 +4,9 @@ from itertools import takewhile
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import (
|
||||||
|
BaseCommand, CommandError, no_translations,
|
||||||
|
)
|
||||||
from django.db import DEFAULT_DB_ALIAS, connections, router
|
from django.db import DEFAULT_DB_ALIAS, connections, router
|
||||||
from django.db.migrations import Migration
|
from django.db.migrations import Migration
|
||||||
from django.db.migrations.autodetector import MigrationAutodetector
|
from django.db.migrations.autodetector import MigrationAutodetector
|
||||||
|
@ -51,6 +53,7 @@ class Command(BaseCommand):
|
||||||
help='Exit with a non-zero status if model changes are missing migrations.',
|
help='Exit with a non-zero status if model changes are missing migrations.',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@no_translations
|
||||||
def handle(self, *app_labels, **options):
|
def handle(self, *app_labels, **options):
|
||||||
self.verbosity = options['verbosity']
|
self.verbosity = options['verbosity']
|
||||||
self.interactive = options['interactive']
|
self.interactive = options['interactive']
|
||||||
|
|
|
@ -4,7 +4,9 @@ from importlib import import_module
|
||||||
|
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.core.checks import Tags, run_checks
|
from django.core.checks import Tags, run_checks
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import (
|
||||||
|
BaseCommand, CommandError, no_translations,
|
||||||
|
)
|
||||||
from django.core.management.sql import (
|
from django.core.management.sql import (
|
||||||
emit_post_migrate_signal, emit_pre_migrate_signal,
|
emit_post_migrate_signal, emit_pre_migrate_signal,
|
||||||
)
|
)
|
||||||
|
@ -58,6 +60,7 @@ class Command(BaseCommand):
|
||||||
issues.extend(super()._run_checks(**kwargs))
|
issues.extend(super()._run_checks(**kwargs))
|
||||||
return issues
|
return issues
|
||||||
|
|
||||||
|
@no_translations
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
self.verbosity = options['verbosity']
|
self.verbosity = options['verbosity']
|
||||||
|
|
|
@ -25,7 +25,6 @@ class Command(BaseCommand):
|
||||||
|
|
||||||
# Validation is called explicitly each time the server is reloaded.
|
# Validation is called explicitly each time the server is reloaded.
|
||||||
requires_system_checks = False
|
requires_system_checks = False
|
||||||
leave_locale_alone = True
|
|
||||||
stealth_options = ('shutdown_message',)
|
stealth_options = ('shutdown_message',)
|
||||||
|
|
||||||
default_addr = '127.0.0.1'
|
default_addr = '127.0.0.1'
|
||||||
|
|
|
@ -32,9 +32,6 @@ class TemplateCommand(BaseCommand):
|
||||||
requires_system_checks = False
|
requires_system_checks = 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
|
|
||||||
# Rewrite the following suffixes when determining the target filename.
|
# Rewrite the following suffixes when determining the target filename.
|
||||||
rewrite_template_suffixes = (
|
rewrite_template_suffixes = (
|
||||||
# Allow shipping invalid .py files without byte-compilation.
|
# Allow shipping invalid .py files without byte-compilation.
|
||||||
|
|
|
@ -126,52 +126,30 @@ such as :option:`--verbosity` and :option:`--traceback`.
|
||||||
Management commands and locales
|
Management commands and locales
|
||||||
===============================
|
===============================
|
||||||
|
|
||||||
By default, the :meth:`BaseCommand.execute` method deactivates translations
|
By default, management commands are executed with the current active locale.
|
||||||
because some commands shipped with Django perform several tasks (for example,
|
|
||||||
user-facing content rendering and database population) that require a
|
|
||||||
project-neutral string language.
|
|
||||||
|
|
||||||
If, for some reason, your custom management command needs to use a fixed locale,
|
If, for some reason, your custom management command must run without an active
|
||||||
you should manually activate and deactivate it in your
|
locale (for example, to prevent translated content from being inserted into
|
||||||
:meth:`~BaseCommand.handle` method using the functions provided by the I18N
|
the database), deactivate translations using the ``@no_translations``
|
||||||
support code::
|
decorator on your :meth:`~BaseCommand.handle` method::
|
||||||
|
|
||||||
from django.core.management.base import BaseCommand, CommandError
|
from django.core.management.base import BaseCommand, no_translations
|
||||||
from django.utils import translation
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
...
|
...
|
||||||
|
|
||||||
|
@no_translations
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
|
|
||||||
# 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
|
|
||||||
...
|
...
|
||||||
|
|
||||||
translation.deactivate()
|
Since translation deactivation requires access to configured settings, the
|
||||||
|
decorator can't be used for commands that work without configured settings.
|
||||||
|
|
||||||
Another need might be that your command simply should use the locale set in
|
.. versionchanged:: 2.1
|
||||||
settings and Django should be kept from deactivating it. You can achieve
|
|
||||||
it by using the :data:`BaseCommand.leave_locale_alone` option.
|
|
||||||
|
|
||||||
When working on the scenarios described above though, take into account that
|
The ``@no_translations`` decorator is new. In older versions, translations
|
||||||
system management commands typically have to be very careful about running in
|
are deactivated before running a command unless the command's
|
||||||
non-uniform locales, so you might need to:
|
``leave_locale_alone`` attribute (now removed) is set to ``True``.
|
||||||
|
|
||||||
* 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
|
|
||||||
deactivating translations).
|
|
||||||
|
|
||||||
* 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.
|
|
||||||
|
|
||||||
Testing
|
Testing
|
||||||
=======
|
=======
|
||||||
|
@ -247,21 +225,6 @@ All attributes can be set in your derived class and can be used in
|
||||||
A boolean; if ``True``, the entire Django project will be checked for
|
A boolean; if ``True``, the entire Django project will be checked for
|
||||||
potential problems prior to executing the command. Default value is ``True``.
|
potential problems prior to executing the command. Default value is ``True``.
|
||||||
|
|
||||||
.. 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 translations being
|
|
||||||
deactivated.
|
|
||||||
|
|
||||||
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 if it creates database content that
|
|
||||||
is locale-sensitive and such content shouldn't contain any translations
|
|
||||||
(like it happens e.g. with :mod:`django.contrib.auth` permissions) as
|
|
||||||
activating any locale might cause unintended effects. See the `Management
|
|
||||||
commands and locales`_ section above for further details.
|
|
||||||
|
|
||||||
.. attribute:: BaseCommand.style
|
.. attribute:: BaseCommand.style
|
||||||
|
|
||||||
An instance attribute that helps create colored output when writing to
|
An instance attribute that helps create colored output when writing to
|
||||||
|
|
|
@ -193,7 +193,7 @@ Minor features
|
||||||
option is now performed independently from handling of the locale that
|
option is now performed independently from handling of the locale that
|
||||||
should be active during the execution of the command. The latter can now be
|
should be active during the execution of the command. The latter can now be
|
||||||
influenced by the new
|
influenced by the new
|
||||||
:attr:`~django.core.management.BaseCommand.leave_locale_alone` internal
|
``BaseCommand.leave_locale_alone`` internal
|
||||||
option. See :ref:`management-commands-and-locales` for more details.
|
option. See :ref:`management-commands-and-locales` for more details.
|
||||||
|
|
||||||
* The :attr:`~django.views.generic.edit.DeletionMixin.success_url` of
|
* The :attr:`~django.views.generic.edit.DeletionMixin.success_url` of
|
||||||
|
|
|
@ -1166,7 +1166,7 @@ Miscellaneous
|
||||||
that Django includes) will no longer convert null values back to an empty
|
that Django includes) will no longer convert null values back to an empty
|
||||||
string. This is consistent with other backends.
|
string. This is consistent with other backends.
|
||||||
|
|
||||||
* When the :attr:`~django.core.management.BaseCommand.leave_locale_alone`
|
* When the ``BaseCommand.leave_locale_alone``
|
||||||
attribute is ``False``, translations are now deactivated instead of forcing
|
attribute is ``False``, translations are now deactivated instead of forcing
|
||||||
the "en-us" locale. In the case your models contained non-English strings and
|
the "en-us" locale. In the case your models contained non-English strings and
|
||||||
you counted on English translations to be activated in management commands,
|
you counted on English translations to be activated in management commands,
|
||||||
|
|
|
@ -418,6 +418,11 @@ Miscellaneous
|
||||||
* The database router :meth:`allow_relation` method is called in more cases.
|
* The database router :meth:`allow_relation` method is called in more cases.
|
||||||
Improperly written routers may need to be updated accordingly.
|
Improperly written routers may need to be updated accordingly.
|
||||||
|
|
||||||
|
* Translations are no longer deactivated before running management commands.
|
||||||
|
If your custom command requires translations to be deactivated (for example,
|
||||||
|
to insert untranslated content into the database), use the new
|
||||||
|
:ref:`@no_translations decorator <management-commands-and-locales>`.
|
||||||
|
|
||||||
.. _deprecated-features-2.1:
|
.. _deprecated-features-2.1:
|
||||||
|
|
||||||
Features deprecated in 2.1
|
Features deprecated in 2.1
|
||||||
|
|
|
@ -194,10 +194,6 @@ class BasicExtractorTests(ExtractorTests):
|
||||||
self.assertMsgId("Get my line number", po_contents)
|
self.assertMsgId("Get my line number", po_contents)
|
||||||
self.assertLocationCommentPresent(self.PO_FILE, 'Get my line number', 'templates', 'test.html')
|
self.assertLocationCommentPresent(self.PO_FILE, 'Get my line number', 'templates', 'test.html')
|
||||||
|
|
||||||
def test_force_en_us_locale(self):
|
|
||||||
"""Value of locale-munging option used by the command is the right one"""
|
|
||||||
self.assertTrue(MakeMessagesCommand.leave_locale_alone)
|
|
||||||
|
|
||||||
def test_extraction_error(self):
|
def test_extraction_error(self):
|
||||||
msg = (
|
msg = (
|
||||||
'Translation blocks must not include other block tags: blocktrans '
|
'Translation blocks must not include other block tags: blocktrans '
|
||||||
|
|
|
@ -1,10 +0,0 @@
|
||||||
from django.core.management.base import BaseCommand
|
|
||||||
from django.utils import translation
|
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
|
||||||
|
|
||||||
leave_locale_alone = True
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
return translation.get_language()
|
|
|
@ -1,10 +1,9 @@
|
||||||
from django.core.management.base import BaseCommand
|
from django.core.management.base import BaseCommand, no_translations
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
|
||||||
|
|
||||||
class Command(BaseCommand):
|
class Command(BaseCommand):
|
||||||
|
|
||||||
leave_locale_alone = False
|
@no_translations
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
def handle(self, *args, **options):
|
||||||
return translation.get_language()
|
return translation.get_language()
|
|
@ -63,17 +63,16 @@ class CommandTests(SimpleTestCase):
|
||||||
dance.Command.requires_system_checks = True
|
dance.Command.requires_system_checks = True
|
||||||
self.assertIn("CommandError", stderr.getvalue())
|
self.assertIn("CommandError", stderr.getvalue())
|
||||||
|
|
||||||
def test_deactivate_locale_set(self):
|
def test_no_translations_deactivate_translations(self):
|
||||||
# Deactivate translation when set to true
|
"""
|
||||||
|
When the Command handle method is decorated with @no_translations,
|
||||||
|
translations are deactivated inside the command.
|
||||||
|
"""
|
||||||
|
current_locale = translation.get_language()
|
||||||
with translation.override('pl'):
|
with translation.override('pl'):
|
||||||
result = management.call_command('leave_locale_alone_false', stdout=StringIO())
|
result = management.call_command('no_translations', stdout=StringIO())
|
||||||
self.assertIsNone(result)
|
self.assertIsNone(result)
|
||||||
|
self.assertEqual(translation.get_language(), current_locale)
|
||||||
def test_configured_locale_preserved(self):
|
|
||||||
# Leaves locale from settings when set to false
|
|
||||||
with translation.override('pl'):
|
|
||||||
result = management.call_command('leave_locale_alone_true', stdout=StringIO())
|
|
||||||
self.assertEqual(result, "pl")
|
|
||||||
|
|
||||||
def test_find_command_without_PATH(self):
|
def test_find_command_without_PATH(self):
|
||||||
"""
|
"""
|
||||||
|
|
Loading…
Reference in New Issue