summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorHasan Ramezani <hasan.r67@gmail.com>2019-06-26 22:04:58 +0200
committerMariusz Felisiak <felisiak.mariusz@gmail.com>2019-06-28 12:51:26 +0200
commit2b03e8e9e8205ae3a3aa128764277e70b7c30803 (patch)
tree95f21a900a1a0eab7ebb819023c9e1c2d2375608
parentf03b7bd11461e8b525c27d5344f8cd3a21c9565e (diff)
Fixed #30584 -- Fixed management command when using subparsers with dest parameter.
-rw-r--r--django/core/management/__init__.py16
-rw-r--r--tests/user_commands/management/commands/subparser_dest.py13
-rw-r--r--tests/user_commands/management/commands/subparser_required.py13
-rw-r--r--tests/user_commands/tests.py25
4 files changed, 65 insertions, 2 deletions
diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py
index b8f47fa4a4..5f2e64761e 100644
--- a/django/core/management/__init__.py
+++ b/django/core/management/__init__.py
@@ -2,6 +2,7 @@ import functools
import os
import pkgutil
import sys
+from argparse import _SubParsersAction
from collections import defaultdict
from difflib import get_close_matches
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()}
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
# to parse_args().
parse_args += [
'{}={}'.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 = dict(defaults._get_kwargs(), **arg_options)
# Raise an error if any unknown options were passed.
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)
unknown_options = set(options) - valid_options
if unknown_options:
diff --git a/tests/user_commands/management/commands/subparser_dest.py b/tests/user_commands/management/commands/subparser_dest.py
new file mode 100644
index 0000000000..ffea7efac7
--- /dev/null
+++ b/tests/user_commands/management/commands/subparser_dest.py
@@ -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))
diff --git a/tests/user_commands/management/commands/subparser_required.py b/tests/user_commands/management/commands/subparser_required.py
new file mode 100644
index 0000000000..995b0dce66
--- /dev/null
+++ b/tests/user_commands/management/commands/subparser_required.py
@@ -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))
diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py
index 76991b1122..a53c781ac6 100644
--- a/tests/user_commands/tests.py
+++ b/tests/user_commands/tests.py
@@ -15,6 +15,7 @@ from django.db import connection
from django.test import SimpleTestCase, override_settings
from django.test.utils import captured_stderr, extend_sys_path
from django.utils import translation
+from django.utils.version import PY37
from .management.commands import dance
@@ -218,10 +219,34 @@ class CommandTests(SimpleTestCase):
management.call_command('subparser', 'foo', 12, stdout=out)
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):
msg = "Error: invalid choice: 'test' (choose from 'foo')"
with self.assertRaisesMessage(CommandError, msg):
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):
"""BaseCommand.create_parser() passes kwargs to CommandParser."""