Fixed #29301 -- Added custom help formatter to BaseCommand class

This partially reverts c3055242c8.
Thanks Adam Johnson and Carlton Gibson for the reviews.
This commit is contained in:
Claude Paroz 2018-05-21 11:32:51 +02:00
parent e9bd1a3e12
commit ce3351b950
4 changed files with 52 additions and 4 deletions

View File

@ -4,7 +4,7 @@ be executed through ``django-admin`` or ``manage.py``).
""" """
import os import os
import sys import sys
from argparse import ArgumentParser from argparse import ArgumentParser, HelpFormatter
from io import TextIOBase from io import TextIOBase
import django import django
@ -88,6 +88,29 @@ def no_translations(handle_func):
return wrapped 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): class OutputWrapper(TextIOBase):
""" """
Wrapper around stdout/stderr Wrapper around stdout/stderr
@ -229,12 +252,10 @@ class BaseCommand:
parser = CommandParser( parser = CommandParser(
prog='%s %s' % (os.path.basename(prog_name), subcommand), prog='%s %s' % (os.path.basename(prog_name), subcommand),
description=self.help or None, description=self.help or None,
formatter_class=DjangoHelpFormatter,
missing_args_message=getattr(self, 'missing_args_message', None), missing_args_message=getattr(self, 'missing_args_message', None),
called_from_command_line=getattr(self, '_called_from_command_line', 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('--version', action='version', version=self.get_version())
parser.add_argument( parser.add_argument(
'-v', '--verbosity', action='store', dest='verbosity', default=1, '-v', '--verbosity', action='store', dest='verbosity', default=1,
@ -258,6 +279,7 @@ class BaseCommand:
'--no-color', action='store_true', dest='no_color', '--no-color', action='store_true', dest='no_color',
help="Don't colorize the command output.", help="Don't colorize the command output.",
) )
self.add_arguments(parser)
return parser return parser
def add_arguments(self, parser): def add_arguments(self, parser):

View File

@ -145,6 +145,11 @@ Management Commands
* The new :option:`inspectdb --include-views` option allows creating models * The new :option:`inspectdb --include-views` option allows creating models
for database views. 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 Migrations
~~~~~~~~~~ ~~~~~~~~~~

View File

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

View File

@ -205,6 +205,11 @@ class CommandTests(SimpleTestCase):
self.assertIn('need_me', out.getvalue()) self.assertIn('need_me', out.getvalue())
self.assertIn('needme2', 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): def test_subparser(self):
out = StringIO() out = StringIO()
management.call_command('subparser', 'foo', 12, stdout=out) management.call_command('subparser', 'foo', 12, stdout=out)