Ticket #18387: 18387-3.diff

File 18387-3.diff, 10.0 KB (added by Claude Paroz, 12 years ago)

Patch 2 + complementary docs

  • django/contrib/auth/tests/management.py

    commit b8b068ccaab7b3976e1e788daeb75ab6acff3e9b
    Author: Claude Paroz <claude@2xlibre.net>
    Date:   Sat May 26 20:50:44 2012 +0200
    
        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.
    
    diff --git a/django/contrib/auth/tests/management.py b/django/contrib/auth/tests/management.py
    index 5d31bd7..81ab0aa 100644
    a b from StringIO import StringIO  
    22
    33from django.contrib.auth import models, management
    44from django.contrib.auth.management.commands import changepassword
     5from django.core.management.base import CommandError
    56from django.test import TestCase
    67
    78
    class ChangepasswordManagementCommandTestCase(TestCase):  
    5657    def test_that_max_tries_exits_1(self):
    5758        """
    5859        A CommandError should be thrown by handle() if the user enters in
    59         mismatched passwords three times. This should be caught by execute() and
    60         converted to a SystemExit
     60        mismatched passwords three times.
    6161        """
    6262        command = changepassword.Command()
    6363        command._get_pass = lambda *args: args or 'foo'
    6464
    65         self.assertRaises(
    66             SystemExit,
    67             command.execute,
    68             "joe",
    69             stdout=self.stdout,
    70             stderr=self.stderr
    71         )
     65        with self.assertRaises(CommandError):
     66            command.execute("joe", stdout=self.stdout, stderr=self.stderr)
  • django/core/management/base.py

    diff --git a/django/core/management/base.py b/django/core/management/base.py
    index 6e06991..a204f6f 100644
    a b class BaseCommand(object):  
    9797       output and, if the command is intended to produce a block of
    9898       SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
    9999
    100     4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
    101        instead print an error message to ``stderr``.
     100    4. If ``handle()`` or ``execute()`` raised any exception (e.g.
     101       ``CommandError``), ``run_from_argv()`` will  instead print an error
     102       message to ``stderr``.
    102103
    103104    Thus, the ``handle()`` method is typically the starting point for
    104105    subclasses; many built-in commands and command types either place
    class BaseCommand(object):  
    210211    def run_from_argv(self, argv):
    211212        """
    212213        Set up any environment changes requested (e.g., Python path
    213         and Django settings), then run this command.
    214 
     214        and Django settings), then run this command. If the
     215        command raises a ``CommandError``, intercept it and print it sensibly
     216        to stderr.
    215217        """
    216218        parser = self.create_parser(argv[0], argv[1])
    217219        options, args = parser.parse_args(argv[2:])
    218220        handle_default_options(options)
    219         self.execute(*args, **options.__dict__)
     221        try:
     222            self.execute(*args, **options.__dict__)
     223        except Exception as e:
     224            if options.traceback:
     225                self.stderr.write(traceback.format_exc())
     226            self.stderr.write('%s: %s' % (e.__class__.__name__, e))
     227            sys.exit(1)
    220228
    221229    def execute(self, *args, **options):
    222230        """
    223231        Try to execute this command, performing model validation if
    224232        needed (as controlled by the attribute
    225         ``self.requires_model_validation``, except if force-skipped). If the
    226         command raises a ``CommandError``, intercept it and print it sensibly
    227         to stderr.
     233        ``self.requires_model_validation``, except if force-skipped).
    228234        """
    229         show_traceback = options.get('traceback', False)
    230235
    231236        # Switch to English, because django-admin.py creates database content
    232237        # like permissions, and those shouldn't contain any translations.
    class BaseCommand(object):  
    237242        self.stderr = OutputWrapper(options.get('stderr', sys.stderr), self.style.ERROR)
    238243
    239244        if self.can_import_settings:
    240             try:
    241                 from django.utils import translation
    242                 saved_lang = translation.get_language()
    243                 translation.activate('en-us')
    244             except ImportError as e:
    245                 # If settings should be available, but aren't,
    246                 # raise the error and quit.
    247                 if show_traceback:
    248                     traceback.print_exc()
    249                 else:
    250                     self.stderr.write('Error: %s' % e)
    251                 sys.exit(1)
     245            from django.utils import translation
     246            saved_lang = translation.get_language()
     247            translation.activate('en-us')
    252248
    253249        try:
    254250            if self.requires_model_validation and not options.get('skip_validation'):
    class BaseCommand(object):  
    265261                self.stdout.write(output)
    266262                if self.output_transaction:
    267263                    self.stdout.write('\n' + self.style.SQL_KEYWORD("COMMIT;"))
    268         except CommandError as e:
    269             if show_traceback:
    270                 traceback.print_exc()
    271             else:
    272                 self.stderr.write('Error: %s' % e)
    273             sys.exit(1)
    274264        finally:
    275265            if saved_lang is not None:
    276266                translation.activate(saved_lang)
  • docs/howto/custom-management-commands.txt

    diff --git a/docs/howto/custom-management-commands.txt b/docs/howto/custom-management-commands.txt
    index 2b34d35..4a27bdf 100644
    a b Exception class indicating a problem while executing a management  
    317317command.
    318318
    319319If this exception is raised during the execution of a management
    320 command, it will be caught and turned into a nicely-printed error
    321 message to the appropriate output stream (i.e., stderr); as a
    322 result, raising this exception (with a sensible description of the
     320command from a command line console, it will be caught and turned into a
     321nicely-printed error message to the appropriate output stream (i.e., stderr);
     322as a result, raising this exception (with a sensible description of the
    323323error) is the preferred way to indicate that something has gone
    324324wrong in the execution of a command.
     325
     326If a management command is called from code through
     327:ref:`call_command <call-command>`, it's up to you to catch the exception
     328when needed.
  • docs/releases/1.5.txt

    diff --git a/docs/releases/1.5.txt b/docs/releases/1.5.txt
    index 0fec53a..4b47255 100644
    a b Django 1.5 also includes several smaller improvements worth noting:  
    7575
    7676* The generic views support OPTIONS requests.
    7777
     78* Management commands do not raise SystemExit any more when called by code from
     79  :ref:`call_command <call-command>`. Any exception raised by the command
     80  (mostly CommandError) is propagated.
     81
    7882* The dumpdata management command outputs one row at a time, preventing
    7983  out-of-memory errors when dumping large datasets.
    8084
  • tests/modeltests/fixtures/tests.py

    diff --git a/tests/modeltests/fixtures/tests.py b/tests/modeltests/fixtures/tests.py
    index 48a5fe7..478bbe9 100644
    a b class FixtureLoadingTests(TestCase):  
    185185            exclude_list=['fixtures.Article', 'fixtures.Book', 'sites'])
    186186
    187187        # Excluding a bogus app should throw an error
    188         self.assertRaises(SystemExit,
     188        self.assertRaises(management.CommandError,
    189189                          self._dumpdata_assert,
    190190                          ['fixtures', 'sites'],
    191191                          '',
    192192                          exclude_list=['foo_app'])
    193193
    194194        # Excluding a bogus model should throw an error
    195         self.assertRaises(SystemExit,
     195        self.assertRaises(management.CommandError,
    196196                          self._dumpdata_assert,
    197197                          ['fixtures', 'sites'],
    198198                          '',
  • tests/modeltests/user_commands/management/commands/dance.py

    diff --git a/tests/modeltests/user_commands/management/commands/dance.py b/tests/modeltests/user_commands/management/commands/dance.py
    index 4ad5579..911530d 100644
    a b  
    11from optparse import make_option
    22
    3 from django.core.management.base import BaseCommand
     3from django.core.management.base import BaseCommand, CommandError
    44
    55
    66class Command(BaseCommand):
    class Command(BaseCommand):  
    88    args = ''
    99    requires_model_validation = True
    1010
    11     option_list =[
     11    option_list = BaseCommand.option_list + (
    1212        make_option("-s", "--style", default="Rock'n'Roll"),
    1313        make_option("-x", "--example")
    14     ]
     14    )
    1515
    1616    def handle(self, *args, **options):
    1717        example = options["example"]
     18        if example == "raise":
     19            raise CommandError()
    1820        self.stdout.write("I don't feel like dancing %s." % options["style"])
  • tests/modeltests/user_commands/tests.py

    diff --git a/tests/modeltests/user_commands/tests.py b/tests/modeltests/user_commands/tests.py
    index c1e2bf9..509f13f 100644
    a b  
     1import sys
    12from StringIO import StringIO
    23
    34from django.core import management
    class CommandTests(TestCase):  
    2627            self.assertEqual(translation.get_language(), 'fr')
    2728
    2829    def test_explode(self):
     30        """ Test that an unknown command raises CommandError """
    2931        self.assertRaises(CommandError, management.call_command, ('explode',))
     32
     33    def test_system_exit(self):
     34        """ Exception raised in a command should raise CommandError with
     35            call_command, but SystemExit when run from command line
     36        """
     37        with self.assertRaises(CommandError):
     38            management.call_command('dance', example="raise")
     39        old_stderr = sys.stderr
     40        sys.stderr = err = StringIO()
     41        try:
     42            with self.assertRaises(SystemExit):
     43                management.ManagementUtility(['manage.py', 'dance', '--example=raise']).execute()
     44        finally:
     45            sys.stderr = old_stderr
     46        self.assertIn("CommandError", err.getvalue())
Back to Top