Fixed #22985 -- Made call_command accept option name parameter

Thanks giulettamasina for the report and Tim Graham for the review.
This commit is contained in:
Claude Paroz 2014-08-09 21:03:19 +02:00
parent 8f9862cd4d
commit 2cc8ffe258
5 changed files with 48 additions and 6 deletions

View File

@ -102,8 +102,12 @@ def call_command(name, *args, **options):
# Simulate argument parsing to get the option defaults (see #10080 for details). # Simulate argument parsing to get the option defaults (see #10080 for details).
parser = command.create_parser('', name) parser = command.create_parser('', name)
if command.use_argparse: if command.use_argparse:
# Use the `dest` option name from the parser option
opt_mapping = dict((sorted(s_opt.option_strings)[0].lstrip('-').replace('-', '_'), s_opt.dest)
for s_opt in parser._actions if s_opt.option_strings)
arg_options = dict((opt_mapping.get(key, key), value) for key, value in options.items())
defaults = parser.parse_args(args=args) defaults = parser.parse_args(args=args)
defaults = dict(defaults._get_kwargs(), **options) defaults = dict(defaults._get_kwargs(), **arg_options)
else: else:
# Legacy optparse method # Legacy optparse method
defaults, _ = parser.parse_args(args=[]) defaults, _ = parser.parse_args(args=[])

View File

@ -1824,10 +1824,27 @@ Examples::
management.call_command('loaddata', 'test_data', verbosity=0) management.call_command('loaddata', 'test_data', verbosity=0)
Note that command options that take no arguments are passed as keywords Note that command options that take no arguments are passed as keywords
with ``True`` or ``False``:: with ``True`` or ``False``, as you can see with the ``interactive`` option above.
Named arguments can be passed by using either one of the following syntaxes::
# Similar to the command line
management.call_command('dumpdata', '--natural')
# Named argument similar to the command line minus the initial dashes and
# with internal dashes replaced by underscores
management.call_command('dumpdata', natural=True)
# `use_natural_keys` is the option destination variable
management.call_command('dumpdata', use_natural_keys=True) management.call_command('dumpdata', use_natural_keys=True)
.. versionchanged:: 1.8
The first syntax is now supported thanks to management commands using the
:py:mod:`argparse` module. For the second syntax, Django previously passed
the option name as-is to the command, now it is always using the ``dest``
variable name (which may or may not be the same as the option name).
Command options which take multiple options are passed a list:: Command options which take multiple options are passed a list::
management.call_command('dumpdata', exclude=['contenttypes', 'auth']) management.call_command('dumpdata', exclude=['contenttypes', 'auth'])

View File

@ -196,6 +196,13 @@ Management Commands
* :djadmin:`inspectdb` now outputs ``Meta.unique_together``. * :djadmin:`inspectdb` now outputs ``Meta.unique_together``.
* When calling management commands from code through :ref:`call_command
<call-command>` and passing options, the option name can match the command
line option name (without the initial dashes) or the final option destination
variable name, but in either case, the resulting option received by the
command is now always the ``dest`` name specified in the command option
definition (as long as the command uses the new :py:mod:`argparse` module).
Models Models
^^^^^^ ^^^^^^

View File

@ -9,9 +9,11 @@ class Command(BaseCommand):
def add_arguments(self, parser): def add_arguments(self, parser):
parser.add_argument("-s", "--style", default="Rock'n'Roll") parser.add_argument("-s", "--style", default="Rock'n'Roll")
parser.add_argument("-x", "--example") parser.add_argument("-x", "--example")
parser.add_argument("--opt-3", action='store_true', dest='option3')
def handle(self, *args, **options): def handle(self, *args, **options):
example = options["example"] example = options["example"]
if example == "raise": if example == "raise":
raise CommandError() raise CommandError()
self.stdout.write("I don't feel like dancing %s." % options["style"]) self.stdout.write("I don't feel like dancing %s." % options["style"])
self.stdout.write(','.join(options.keys()))

View File

@ -15,14 +15,15 @@ class CommandTests(SimpleTestCase):
def test_command(self): def test_command(self):
out = StringIO() out = StringIO()
management.call_command('dance', stdout=out) management.call_command('dance', stdout=out)
self.assertEqual(out.getvalue(), self.assertIn("I don't feel like dancing Rock'n'Roll.\n", out.getvalue())
"I don't feel like dancing Rock'n'Roll.\n")
def test_command_style(self): def test_command_style(self):
out = StringIO() out = StringIO()
management.call_command('dance', style='Jive', stdout=out) management.call_command('dance', style='Jive', stdout=out)
self.assertEqual(out.getvalue(), self.assertIn("I don't feel like dancing Jive.\n", out.getvalue())
"I don't feel like dancing Jive.\n") # Passing options as arguments also works (thanks argparse)
management.call_command('dance', '--style', 'Jive', stdout=out)
self.assertIn("I don't feel like dancing Jive.\n", out.getvalue())
def test_language_preserved(self): def test_language_preserved(self):
out = StringIO() out = StringIO()
@ -76,6 +77,17 @@ class CommandTests(SimpleTestCase):
if current_path is not None: if current_path is not None:
os.environ['PATH'] = current_path os.environ['PATH'] = current_path
def test_call_command_option_parsing(self):
"""
When passing the long option name to call_command, the available option
key is the option dest name (#22985).
"""
out = StringIO()
management.call_command('dance', stdout=out, opt_3=True)
self.assertIn("option3", out.getvalue())
self.assertNotIn("opt_3", out.getvalue())
self.assertNotIn("opt-3", out.getvalue())
def test_optparse_compatibility(self): def test_optparse_compatibility(self):
""" """
optparse should be supported during Django 1.8/1.9 releases. optparse should be supported during Django 1.8/1.9 releases.