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:
parent
8f9862cd4d
commit
2cc8ffe258
|
@ -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=[])
|
||||||
|
|
|
@ -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'])
|
||||||
|
|
|
@ -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
|
||||||
^^^^^^
|
^^^^^^
|
||||||
|
|
||||||
|
|
|
@ -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()))
|
||||||
|
|
|
@ -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.
|
||||||
|
|
Loading…
Reference in New Issue