Fixed #18387 -- Do not call sys.exit during call_command.
Moved sys.exit(1) so as failing management commands reach it only when running from command line.
This commit is contained in:
parent
4423757c0c
commit
f2b6763ad7
|
@ -2,6 +2,7 @@ from StringIO import StringIO
|
|||
|
||||
from django.contrib.auth import models, management
|
||||
from django.contrib.auth.management.commands import changepassword
|
||||
from django.core.management.base import CommandError
|
||||
from django.test import TestCase
|
||||
|
||||
|
||||
|
@ -56,16 +57,10 @@ class ChangepasswordManagementCommandTestCase(TestCase):
|
|||
def test_that_max_tries_exits_1(self):
|
||||
"""
|
||||
A CommandError should be thrown by handle() if the user enters in
|
||||
mismatched passwords three times. This should be caught by execute() and
|
||||
converted to a SystemExit
|
||||
mismatched passwords three times.
|
||||
"""
|
||||
command = changepassword.Command()
|
||||
command._get_pass = lambda *args: args or 'foo'
|
||||
|
||||
self.assertRaises(
|
||||
SystemExit,
|
||||
command.execute,
|
||||
"joe",
|
||||
stdout=self.stdout,
|
||||
stderr=self.stderr
|
||||
)
|
||||
with self.assertRaises(CommandError):
|
||||
command.execute("joe", stdout=self.stdout, stderr=self.stderr)
|
||||
|
|
|
@ -97,8 +97,9 @@ class BaseCommand(object):
|
|||
output and, if the command is intended to produce a block of
|
||||
SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
|
||||
|
||||
4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
|
||||
instead print an error message to ``stderr``.
|
||||
4. If ``handle()`` or ``execute()`` raised any exception (e.g.
|
||||
``CommandError``), ``run_from_argv()`` will instead print an error
|
||||
message to ``stderr``.
|
||||
|
||||
Thus, the ``handle()`` method is typically the starting point for
|
||||
subclasses; many built-in commands and command types either place
|
||||
|
@ -210,23 +211,27 @@ class BaseCommand(object):
|
|||
def run_from_argv(self, argv):
|
||||
"""
|
||||
Set up any environment changes requested (e.g., Python path
|
||||
and Django settings), then run this command.
|
||||
|
||||
and Django settings), then run this command. If the
|
||||
command raises a ``CommandError``, intercept it and print it sensibly
|
||||
to stderr.
|
||||
"""
|
||||
parser = self.create_parser(argv[0], argv[1])
|
||||
options, args = parser.parse_args(argv[2:])
|
||||
handle_default_options(options)
|
||||
try:
|
||||
self.execute(*args, **options.__dict__)
|
||||
except Exception as e:
|
||||
if options.traceback:
|
||||
self.stderr.write(traceback.format_exc())
|
||||
self.stderr.write('%s: %s' % (e.__class__.__name__, e))
|
||||
sys.exit(1)
|
||||
|
||||
def execute(self, *args, **options):
|
||||
"""
|
||||
Try to execute this command, performing model validation if
|
||||
needed (as controlled by the attribute
|
||||
``self.requires_model_validation``, except if force-skipped). If the
|
||||
command raises a ``CommandError``, intercept it and print it sensibly
|
||||
to stderr.
|
||||
``self.requires_model_validation``, except if force-skipped).
|
||||
"""
|
||||
show_traceback = options.get('traceback', False)
|
||||
|
||||
# Switch to English, because django-admin.py creates database content
|
||||
# like permissions, and those shouldn't contain any translations.
|
||||
|
@ -237,18 +242,9 @@ class BaseCommand(object):
|
|||
self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
|
||||
|
||||
if self.can_import_settings:
|
||||
try:
|
||||
from django.utils import translation
|
||||
saved_lang = translation.get_language()
|
||||
translation.activate('en-us')
|
||||
except ImportError as e:
|
||||
# If settings should be available, but aren't,
|
||||
# raise the error and quit.
|
||||
if show_traceback:
|
||||
traceback.print_exc()
|
||||
else:
|
||||
self.stderr.write('Error: %s' % e)
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
if self.requires_model_validation and not options.get('skip_validation'):
|
||||
|
@ -265,12 +261,6 @@ class BaseCommand(object):
|
|||
self.stdout.write(output)
|
||||
if self.output_transaction:
|
||||
self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
|
||||
except CommandError as e:
|
||||
if show_traceback:
|
||||
traceback.print_exc()
|
||||
else:
|
||||
self.stderr.write('Error: %s' % e)
|
||||
sys.exit(1)
|
||||
finally:
|
||||
if saved_lang is not None:
|
||||
translation.activate(saved_lang)
|
||||
|
|
|
@ -317,8 +317,12 @@ Exception class indicating a problem while executing a management
|
|||
command.
|
||||
|
||||
If this exception is raised during the execution of a management
|
||||
command, it will be caught and turned into a nicely-printed error
|
||||
message to the appropriate output stream (i.e., stderr); as a
|
||||
result, raising this exception (with a sensible description of the
|
||||
command from a command line console, it will be caught and turned into a
|
||||
nicely-printed error message to the appropriate output stream (i.e., stderr);
|
||||
as a result, raising this exception (with a sensible description of the
|
||||
error) is the preferred way to indicate that something has gone
|
||||
wrong in the execution of a command.
|
||||
|
||||
If a management command is called from code through
|
||||
:ref:`call_command <call-command>`, it's up to you to catch the exception
|
||||
when needed.
|
||||
|
|
|
@ -75,6 +75,10 @@ Django 1.5 also includes several smaller improvements worth noting:
|
|||
|
||||
* The generic views support OPTIONS requests.
|
||||
|
||||
* Management commands do not raise ``SystemExit`` any more when called by code
|
||||
from :ref:`call_command <call-command>`. Any exception raised by the command
|
||||
(mostly :ref:`CommandError <ref-command-exceptions>`) is propagated.
|
||||
|
||||
* The dumpdata management command outputs one row at a time, preventing
|
||||
out-of-memory errors when dumping large datasets.
|
||||
|
||||
|
|
|
@ -185,14 +185,14 @@ class FixtureLoadingTests(TestCase):
|
|||
exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
|
||||
|
||||
# Excluding a bogus app should throw an error
|
||||
self.assertRaises(SystemExit,
|
||||
self.assertRaises(management.CommandError,
|
||||
self._dumpdata_assert,
|
||||
['fixtures', 'sites'],
|
||||
'',
|
||||
exclude_list=['foo_app'])
|
||||
|
||||
# Excluding a bogus model should throw an error
|
||||
self.assertRaises(SystemExit,
|
||||
self.assertRaises(management.CommandError,
|
||||
self._dumpdata_assert,
|
||||
['fixtures', 'sites'],
|
||||
'',
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
from optparse import make_option
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.core.management.base import BaseCommand, CommandError
|
||||
|
||||
|
||||
class Command(BaseCommand):
|
||||
|
@ -8,11 +8,13 @@ class Command(BaseCommand):
|
|||
args = ''
|
||||
requires_model_validation = True
|
||||
|
||||
option_list =[
|
||||
option_list = BaseCommand.option_list + (
|
||||
make_option("-s", "--style", default="Rock'n'Roll"),
|
||||
make_option("-x", "--example")
|
||||
]
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
example = options["example"]
|
||||
if example == "raise":
|
||||
raise CommandError()
|
||||
self.stdout.write("I don't feel like dancing %s." % options["style"])
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
import sys
|
||||
from StringIO import StringIO
|
||||
|
||||
from django.core import management
|
||||
|
@ -26,4 +27,20 @@ class CommandTests(TestCase):
|
|||
self.assertEqual(translation.get_language(), 'fr')
|
||||
|
||||
def test_explode(self):
|
||||
""" Test that an unknown command raises CommandError """
|
||||
self.assertRaises(CommandError, management.call_command, ('explode',))
|
||||
|
||||
def test_system_exit(self):
|
||||
""" Exception raised in a command should raise CommandError with
|
||||
call_command, but SystemExit when run from command line
|
||||
"""
|
||||
with self.assertRaises(CommandError):
|
||||
management.call_command('dance', example="raise")
|
||||
old_stderr = sys.stderr
|
||||
sys.stderr = err = StringIO()
|
||||
try:
|
||||
with self.assertRaises(SystemExit):
|
||||
management.ManagementUtility(['manage.py', 'dance', '--example=raise']).execute()
|
||||
finally:
|
||||
sys.stderr = old_stderr
|
||||
self.assertIn("CommandError", err.getvalue())
|
||||
|
|
Loading…
Reference in New Issue