From ce3351b9508896afdf87d11bd64fd6b5ad928228 Mon Sep 17 00:00:00 2001 From: Claude Paroz Date: Mon, 21 May 2018 11:32:51 +0200 Subject: [PATCH] Fixed #29301 -- Added custom help formatter to BaseCommand class This partially reverts c3055242c81812278ebdc93dd109f30d2cbd1610. Thanks Adam Johnson and Carlton Gibson for the reviews. --- django/core/management/base.py | 30 ++++++++++++++++--- docs/releases/2.1.txt | 5 ++++ .../management/commands/common_args.py | 16 ++++++++++ tests/user_commands/tests.py | 5 ++++ 4 files changed, 52 insertions(+), 4 deletions(-) create mode 100644 tests/user_commands/management/commands/common_args.py diff --git a/django/core/management/base.py b/django/core/management/base.py index d6b4e27b78..e3374f678b 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -4,7 +4,7 @@ be executed through ``django-admin`` or ``manage.py``). """ import os import sys -from argparse import ArgumentParser +from argparse import ArgumentParser, HelpFormatter from io import TextIOBase import django @@ -88,6 +88,29 @@ def no_translations(handle_func): return wrapped +class DjangoHelpFormatter(HelpFormatter): + """ + Customized formatter so that command-specific arguments appear in the + --help output before arguments common to all commands. + """ + show_last = { + '--version', '--verbosity', '--traceback', '--settings', '--pythonpath', + '--no-color', + } + + def _reordered_actions(self, actions): + return sorted( + actions, + key=lambda a: set(a.option_strings) & self.show_last != set() + ) + + def add_usage(self, usage, actions, *args, **kwargs): + super().add_usage(usage, self._reordered_actions(actions), *args, **kwargs) + + def add_arguments(self, actions): + super().add_arguments(self._reordered_actions(actions)) + + class OutputWrapper(TextIOBase): """ Wrapper around stdout/stderr @@ -229,12 +252,10 @@ class BaseCommand: parser = CommandParser( prog='%s %s' % (os.path.basename(prog_name), subcommand), description=self.help or None, + formatter_class=DjangoHelpFormatter, missing_args_message=getattr(self, 'missing_args_message', None), called_from_command_line=getattr(self, '_called_from_command_line', None), ) - # Add command-specific arguments first so that they appear in the - # --help output before arguments common to all commands. - self.add_arguments(parser) parser.add_argument('--version', action='version', version=self.get_version()) parser.add_argument( '-v', '--verbosity', action='store', dest='verbosity', default=1, @@ -258,6 +279,7 @@ class BaseCommand: '--no-color', action='store_true', dest='no_color', help="Don't colorize the command output.", ) + self.add_arguments(parser) return parser def add_arguments(self, parser): diff --git a/docs/releases/2.1.txt b/docs/releases/2.1.txt index cf2dbd6920..81f112b464 100644 --- a/docs/releases/2.1.txt +++ b/docs/releases/2.1.txt @@ -145,6 +145,11 @@ Management Commands * The new :option:`inspectdb --include-views` option allows creating models for database views. +* The :class:`~django.core.management.BaseCommand` class now uses a custom help + formatter so that the standard options like ``--verbosity`` or ``--settings`` + appear last in the help output, giving a more prominent position to subclassed + command's options. + Migrations ~~~~~~~~~~ diff --git a/tests/user_commands/management/commands/common_args.py b/tests/user_commands/management/commands/common_args.py new file mode 100644 index 0000000000..d7b288a267 --- /dev/null +++ b/tests/user_commands/management/commands/common_args.py @@ -0,0 +1,16 @@ +from argparse import ArgumentError + +from django.core.management.base import BaseCommand, CommandError + + +class Command(BaseCommand): + def add_arguments(self, parser): + try: + parser.add_argument('--version', action='version', version='A.B.C') + except ArgumentError: + pass + else: + raise CommandError('--version argument does no yet exist') + + def handle(self, *args, **options): + return 'Detected that --version already exists' diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index 92263f58d6..e90d29bb0f 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -205,6 +205,11 @@ class CommandTests(SimpleTestCase): self.assertIn('need_me', out.getvalue()) self.assertIn('needme2', out.getvalue()) + def test_command_add_arguments_after_common_arguments(self): + out = StringIO() + management.call_command('common_args', stdout=out) + self.assertIn('Detected that --version already exists', out.getvalue()) + def test_subparser(self): out = StringIO() management.call_command('subparser', 'foo', 12, stdout=out)