Commit 2cc8ffe2 authored by Claude Paroz's avatar Claude Paroz
Browse files

Fixed #22985 -- Made call_command accept option name parameter

Thanks giulettamasina for the report and Tim Graham for the review.
parent 8f9862cd
Loading
Loading
Loading
Loading
+5 −1
Original line number Diff line number Diff line
@@ -102,8 +102,12 @@ def call_command(name, *args, **options):
    # Simulate argument parsing to get the option defaults (see #10080 for details).
    parser = command.create_parser('', name)
    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 = dict(defaults._get_kwargs(), **options)
        defaults = dict(defaults._get_kwargs(), **arg_options)
    else:
        # Legacy optparse method
        defaults, _ = parser.parse_args(args=[])
+18 −1
Original line number Diff line number Diff line
@@ -1824,10 +1824,27 @@ Examples::
      management.call_command('loaddata', 'test_data', verbosity=0)

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)

.. 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::

      management.call_command('dumpdata', exclude=['contenttypes', 'auth'])
+7 −0
Original line number Diff line number Diff line
@@ -196,6 +196,13 @@ Management Commands

* :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
^^^^^^

+2 −0
Original line number Diff line number Diff line
@@ -9,9 +9,11 @@ class Command(BaseCommand):
    def add_arguments(self, parser):
        parser.add_argument("-s", "--style", default="Rock'n'Roll")
        parser.add_argument("-x", "--example")
        parser.add_argument("--opt-3", action='store_true', dest='option3')

    def handle(self, *args, **options):
        example = options["example"]
        if example == "raise":
            raise CommandError()
        self.stdout.write("I don't feel like dancing %s." % options["style"])
        self.stdout.write(','.join(options.keys()))
+16 −4
Original line number Diff line number Diff line
@@ -15,14 +15,15 @@ class CommandTests(SimpleTestCase):
    def test_command(self):
        out = StringIO()
        management.call_command('dance', stdout=out)
        self.assertEqual(out.getvalue(),
            "I don't feel like dancing Rock'n'Roll.\n")
        self.assertIn("I don't feel like dancing Rock'n'Roll.\n", out.getvalue())

    def test_command_style(self):
        out = StringIO()
        management.call_command('dance', style='Jive', stdout=out)
        self.assertEqual(out.getvalue(),
            "I don't feel like dancing Jive.\n")
        self.assertIn("I don't feel like dancing Jive.\n", out.getvalue())
        # 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):
        out = StringIO()
@@ -76,6 +77,17 @@ class CommandTests(SimpleTestCase):
            if current_path is not None:
                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):
        """
        optparse should be supported during Django 1.8/1.9 releases.