From 6eb3f53bdd5089e3ab229780fe339fecfc30a7ee Mon Sep 17 00:00:00 2001 From: Hasan Ramezani Date: Tue, 29 Sep 2020 15:51:42 +0200 Subject: [PATCH] Fixed #32047 -- Fixed call_command() crash if a constant option from required mutually exclusive group is passed in options. --- django/core/management/__init__.py | 8 +++-- .../commands/mutually_exclusive_required.py | 9 +++++- tests/user_commands/tests.py | 30 ++++++++++++++++++- 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index cf6b60c93e..aff9be4e85 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -2,7 +2,9 @@ import functools import os import pkgutil import sys -from argparse import _SubParsersAction +from argparse import ( + _AppendConstAction, _CountAction, _StoreConstAction, _SubParsersAction, +) from collections import defaultdict from difflib import get_close_matches from importlib import import_module @@ -138,7 +140,9 @@ def call_command(command_name, *args, **options): # 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]) + min(opt.option_strings) + if isinstance(opt, (_AppendConstAction, _CountAction, _StoreConstAction)) + else '{}={}'.format(min(opt.option_strings), arg_options[opt.dest]) for opt in parser_actions if ( opt.dest in options and (opt.required or opt in mutually_exclusive_required_options) diff --git a/tests/user_commands/management/commands/mutually_exclusive_required.py b/tests/user_commands/management/commands/mutually_exclusive_required.py index e5df17edb0..3fbf514c4d 100644 --- a/tests/user_commands/management/commands/mutually_exclusive_required.py +++ b/tests/user_commands/management/commands/mutually_exclusive_required.py @@ -7,6 +7,13 @@ class Command(BaseCommand): group = parser.add_mutually_exclusive_group(required=True) group.add_argument('--foo-id', type=int, nargs='?', default=None) group.add_argument('--foo-name', type=str, nargs='?', default=None) + group.add_argument('--append_const', action='append_const', const=42) + group.add_argument('--const', action='store_const', const=31) + group.add_argument('--count', action='count') + group.add_argument('--flag_false', action='store_false') + group.add_argument('--flag_true', action='store_true') def handle(self, *args, **options): - self.stdout.write(','.join(options)) + for option, value in options.items(): + if value is not None: + self.stdout.write('%s=%s' % (option, value)) diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index f0627e8ae7..fe61a23ccd 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -243,10 +243,38 @@ class CommandTests(SimpleTestCase): self.assertIn('foo_id', out.getvalue()) management.call_command('mutually_exclusive_required', foo_name='foo', stdout=out) self.assertIn('foo_name', out.getvalue()) - msg = 'Error: one of the arguments --foo-id --foo-name is required' + msg = ( + 'Error: one of the arguments --foo-id --foo-name --append_const ' + '--const --count --flag_false --flag_true is required' + ) with self.assertRaisesMessage(CommandError, msg): management.call_command('mutually_exclusive_required', stdout=out) + def test_mutually_exclusive_group_required_const_options(self): + tests = [ + ('append_const', [42]), + ('const', 31), + ('count', 1), + ('flag_false', False), + ('flag_true', True), + ] + for arg, value in tests: + out = StringIO() + expected_output = '%s=%s' % (arg, value) + with self.subTest(arg=arg): + management.call_command( + 'mutually_exclusive_required', + '--%s' % arg, + stdout=out, + ) + self.assertIn(expected_output, out.getvalue()) + out.truncate(0) + management.call_command( + 'mutually_exclusive_required', + **{arg: value, 'stdout': out}, + ) + self.assertIn(expected_output, out.getvalue()) + def test_subparser(self): out = StringIO() management.call_command('subparser', 'foo', 12, stdout=out)