mirror of https://github.com/django/django.git
Fixed #30584 -- Fixed management command when using subparsers with dest parameter.
This commit is contained in:
parent
f03b7bd114
commit
2b03e8e9e8
|
@ -2,6 +2,7 @@ import functools
|
||||||
import os
|
import os
|
||||||
import pkgutil
|
import pkgutil
|
||||||
import sys
|
import sys
|
||||||
|
from argparse import _SubParsersAction
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from difflib import get_close_matches
|
from difflib import get_close_matches
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
|
@ -118,17 +119,28 @@ def call_command(command_name, *args, **options):
|
||||||
}
|
}
|
||||||
arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
|
arg_options = {opt_mapping.get(key, key): value for key, value in options.items()}
|
||||||
parse_args = [str(a) for a in args]
|
parse_args = [str(a) for a in args]
|
||||||
|
|
||||||
|
def get_actions(parser):
|
||||||
|
# Parser actions and actions from sub-parser choices.
|
||||||
|
for opt in parser._actions:
|
||||||
|
if isinstance(opt, _SubParsersAction):
|
||||||
|
for sub_opt in opt.choices.values():
|
||||||
|
yield from get_actions(sub_opt)
|
||||||
|
else:
|
||||||
|
yield opt
|
||||||
|
|
||||||
|
parser_actions = list(get_actions(parser))
|
||||||
# Any required arguments which are passed in via **options must be passed
|
# Any required arguments which are passed in via **options must be passed
|
||||||
# to parse_args().
|
# to parse_args().
|
||||||
parse_args += [
|
parse_args += [
|
||||||
'{}={}'.format(min(opt.option_strings), arg_options[opt.dest])
|
'{}={}'.format(min(opt.option_strings), arg_options[opt.dest])
|
||||||
for opt in parser._actions if opt.required and opt.dest in options
|
for opt in parser_actions if opt.required and opt.dest in options
|
||||||
]
|
]
|
||||||
defaults = parser.parse_args(args=parse_args)
|
defaults = parser.parse_args(args=parse_args)
|
||||||
defaults = dict(defaults._get_kwargs(), **arg_options)
|
defaults = dict(defaults._get_kwargs(), **arg_options)
|
||||||
# Raise an error if any unknown options were passed.
|
# Raise an error if any unknown options were passed.
|
||||||
stealth_options = set(command.base_stealth_options + command.stealth_options)
|
stealth_options = set(command.base_stealth_options + command.stealth_options)
|
||||||
dest_parameters = {action.dest for action in parser._actions}
|
dest_parameters = {action.dest for action in parser_actions}
|
||||||
valid_options = (dest_parameters | stealth_options).union(opt_mapping)
|
valid_options = (dest_parameters | stealth_options).union(opt_mapping)
|
||||||
unknown_options = set(options) - valid_options
|
unknown_options = set(options) - valid_options
|
||||||
if unknown_options:
|
if unknown_options:
|
||||||
|
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from django.utils.version import PY37
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
kwargs = {'required': True} if PY37 else {}
|
||||||
|
subparsers = parser.add_subparsers(dest='subcommand', **kwargs)
|
||||||
|
parser_foo = subparsers.add_parser('foo')
|
||||||
|
parser_foo.add_argument('--bar')
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.stdout.write(','.join(options))
|
|
@ -0,0 +1,13 @@
|
||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
def add_arguments(self, parser):
|
||||||
|
subparsers_1 = parser.add_subparsers(dest='subcommand_1')
|
||||||
|
parser_foo_1 = subparsers_1.add_parser('foo_1')
|
||||||
|
subparsers_2 = parser_foo_1.add_subparsers(dest='subcommand_2')
|
||||||
|
parser_foo_2 = subparsers_2.add_parser('foo_2')
|
||||||
|
parser_foo_2.add_argument('--bar', required=True)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.stdout.write(','.join(options))
|
|
@ -15,6 +15,7 @@ from django.db import connection
|
||||||
from django.test import SimpleTestCase, override_settings
|
from django.test import SimpleTestCase, override_settings
|
||||||
from django.test.utils import captured_stderr, extend_sys_path
|
from django.test.utils import captured_stderr, extend_sys_path
|
||||||
from django.utils import translation
|
from django.utils import translation
|
||||||
|
from django.utils.version import PY37
|
||||||
|
|
||||||
from .management.commands import dance
|
from .management.commands import dance
|
||||||
|
|
||||||
|
@ -218,10 +219,34 @@ class CommandTests(SimpleTestCase):
|
||||||
management.call_command('subparser', 'foo', 12, stdout=out)
|
management.call_command('subparser', 'foo', 12, stdout=out)
|
||||||
self.assertIn('bar', out.getvalue())
|
self.assertIn('bar', out.getvalue())
|
||||||
|
|
||||||
|
def test_subparser_dest_args(self):
|
||||||
|
out = StringIO()
|
||||||
|
management.call_command('subparser_dest', 'foo', bar=12, stdout=out)
|
||||||
|
self.assertIn('bar', out.getvalue())
|
||||||
|
|
||||||
|
def test_subparser_dest_required_args(self):
|
||||||
|
out = StringIO()
|
||||||
|
management.call_command('subparser_required', 'foo_1', 'foo_2', bar=12, stdout=out)
|
||||||
|
self.assertIn('bar', out.getvalue())
|
||||||
|
|
||||||
def test_subparser_invalid_option(self):
|
def test_subparser_invalid_option(self):
|
||||||
msg = "Error: invalid choice: 'test' (choose from 'foo')"
|
msg = "Error: invalid choice: 'test' (choose from 'foo')"
|
||||||
with self.assertRaisesMessage(CommandError, msg):
|
with self.assertRaisesMessage(CommandError, msg):
|
||||||
management.call_command('subparser', 'test', 12)
|
management.call_command('subparser', 'test', 12)
|
||||||
|
if PY37:
|
||||||
|
# "required" option requires Python 3.7 and later.
|
||||||
|
msg = 'Error: the following arguments are required: subcommand'
|
||||||
|
with self.assertRaisesMessage(CommandError, msg):
|
||||||
|
management.call_command('subparser_dest', subcommand='foo', bar=12)
|
||||||
|
else:
|
||||||
|
msg = (
|
||||||
|
'Unknown option(s) for subparser_dest command: subcommand. '
|
||||||
|
'Valid options are: bar, force_color, help, no_color, '
|
||||||
|
'pythonpath, settings, skip_checks, stderr, stdout, '
|
||||||
|
'traceback, verbosity, version.'
|
||||||
|
)
|
||||||
|
with self.assertRaisesMessage(TypeError, msg):
|
||||||
|
management.call_command('subparser_dest', subcommand='foo', bar=12)
|
||||||
|
|
||||||
def test_create_parser_kwargs(self):
|
def test_create_parser_kwargs(self):
|
||||||
"""BaseCommand.create_parser() passes kwargs to CommandParser."""
|
"""BaseCommand.create_parser() passes kwargs to CommandParser."""
|
||||||
|
|
Loading…
Reference in New Issue