Commit 869c9ba3 authored by Ramiro Morales's avatar Ramiro Morales
Browse files

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.
parent 2c173ff3
Loading
Loading
Loading
Loading
+36 −9
Original line number Diff line number Diff line
@@ -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):
        """
+1 −1
Original line number Diff line number Diff line
@@ -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')
+1 −1
Original line number Diff line number Diff line
@@ -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')
+3 −0
Original line number Diff line number Diff line
@@ -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
+65 −35
Original line number Diff line number Diff line
@@ -112,17 +112,20 @@ 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
Management commands and locales
===============================

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``).
require a system-neutral string language (for which we use 'en-us').

    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:
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:

.. code-block:: python

@@ -138,8 +141,7 @@ default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`.
            # Activate a fixed locale, e.g. Russian
            translation.activate('ru')

                # Or you can activate the LANGUAGE_CODE
                # chosen in the settings:
            # Or you can activate the LANGUAGE_CODE # chosen in the settings:
            #
            #from django.conf import settings
            #translation.activate(settings.LANGUAGE_CODE)
@@ -149,11 +151,16 @@ default options such as :djadminopt:`--verbosity` and :djadminopt:`--traceback`.

            translation.deactivate()

    Take into account though, that system management commands typically have to
    be very careful about running in non-uniform locales, so:
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.

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
  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).

@@ -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
-------

Loading