From a1a3e515616da102fc48a1e1af8a5b2f429f747e Mon Sep 17 00:00:00 2001 From: Alex Tomic Date: Fri, 2 Mar 2018 12:25:08 -0500 Subject: [PATCH] Fixed #29133 -- Fixed call_command() crash if a required option is passed in options. --- django/core/management/__init__.py | 9 ++++++++- .../management/commands/required_option.py | 11 +++++++++++ tests/user_commands/tests.py | 12 ++++++++++++ 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 tests/user_commands/management/commands/required_option.py diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 4d26c98d09..688340e49d 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -117,7 +117,14 @@ def call_command(command_name, *args, **options): for s_opt in parser._actions if s_opt.option_strings } arg_options = {opt_mapping.get(key, key): value for key, value in options.items()} - defaults = parser.parse_args(args=[str(a) for a in args]) + parse_args = [str(a) for a in args] + # Any required arguments which are passed in via **options must 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 + ] + 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) diff --git a/tests/user_commands/management/commands/required_option.py b/tests/user_commands/management/commands/required_option.py new file mode 100644 index 0000000000..3b30ed942e --- /dev/null +++ b/tests/user_commands/management/commands/required_option.py @@ -0,0 +1,11 @@ +from django.core.management.base import BaseCommand + + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument('-n', '--need-me', required=True) + parser.add_argument('-t', '--need-me-too', required=True, dest='needme2') + + 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 3900a58247..ae05bcfe25 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -194,6 +194,18 @@ class CommandTests(SimpleTestCase): with self.assertRaisesMessage(TypeError, msg): management.call_command('dance', unrecognized=1, unrecognized2=1) + def test_call_command_with_required_parameters_in_options(self): + out = StringIO() + management.call_command('required_option', need_me='foo', needme2='bar', stdout=out) + self.assertIn('need_me', out.getvalue()) + self.assertIn('needme2', out.getvalue()) + + def test_call_command_with_required_parameters_in_mixed_options(self): + out = StringIO() + management.call_command('required_option', '--need-me=foo', needme2='bar', stdout=out) + self.assertIn('need_me', out.getvalue()) + self.assertIn('needme2', out.getvalue()) + class CommandRunTests(AdminScriptTestCase): """