Commit cbff097b authored by Claude Paroz's avatar Claude Paroz
Browse files

Documented optparse to argparse changes for management commands

parent 85686386
Loading
Loading
Loading
Loading
+65 −14
Original line number Diff line number Diff line
@@ -50,13 +50,15 @@ look like this:
    from polls.models import Poll

    class Command(BaseCommand):
        args = '<poll_id poll_id ...>'
        help = 'Closes the specified poll for voting'

        def add_arguments(self, parser):
            parser.add_argument('poll_id', nargs='+', type=int)

        def handle(self, *args, **options):
            for poll_id in args:
            for poll_id in options['poll_id']:
                try:
                    poll = Poll.objects.get(pk=int(poll_id))
                    poll = Poll.objects.get(pk=poll_id)
                except Poll.DoesNotExist:
                    raise CommandError('Poll "%s" does not exist' % poll_id)

@@ -65,6 +67,14 @@ look like this:

                self.stdout.write('Successfully closed poll "%s"' % poll_id)

Before Django 1.8, management commands were based on the :py:mod:`optparse`
module, and positional arguments were passed in ``*args`` while optional
arguments were passed in ``**options``. Now that management commands use
:py:mod:`argparse` for argument parsing, all arguments are passed in
``**options`` by default, unless you name your positional arguments to ``args``
(compatibility mode). You are encouraged to exclusively use ``**options`` for
new commands.

.. _management-commands-output:

.. note::
@@ -81,28 +91,34 @@ look like this:
The new custom command can be called using ``python manage.py closepoll
<poll_id>``.

The ``handle()`` method takes zero or more ``poll_ids`` and sets ``poll.opened``
The ``handle()`` method takes one or more ``poll_ids`` and sets ``poll.opened``
to ``False`` for each one. If the user referenced any nonexistent polls, a
:class:`CommandError` is raised. The ``poll.opened`` attribute does not exist
in the :doc:`tutorial</intro/tutorial01>` and was added to
``polls.models.Poll`` for this example.

.. _custom-commands-options:

Accepting optional arguments
============================

The same ``closepoll`` could be easily modified to delete a given poll instead
of closing it by accepting additional command line options. These custom options
must be added to :attr:`~BaseCommand.option_list` like this:
of closing it by accepting additional command line options. These custom
options can be added in the :meth:`~BaseCommand.add_arguments` method like this:

.. code-block:: python

    from optparse import make_option

    class Command(BaseCommand):
        option_list = BaseCommand.option_list + (
            make_option('--delete',
        def add_arguments(self, parser):
            # Positional arguments
            parser.add_argument('poll_id', nargs='+', type=int)

            # Named (optional) arguments
            parser.add_argument('--delete',
                action='store_true',
                dest='delete',
                default=False,
                help='Delete poll instead of closing it'),
            )
                help='Delete poll instead of closing it')

        def handle(self, *args, **options):
            # ...
@@ -110,9 +126,15 @@ must be added to :attr:`~BaseCommand.option_list` like this:
                poll.delete()
            # ...

.. versionchanged:: 1.8

    Previously, only the standard :py:mod:`optparse` library was supported and
    you would have to extend the command ``option_list`` variable with
    ``optparse.make_option()``.

The option (``delete`` in our example) is available in the options dict
parameter of the handle method. See the :py:mod:`optparse` Python documentation
for more about ``make_option`` usage.
parameter of the handle method. See the :py:mod:`argparse` Python documentation
for more about ``add_argument`` usage.

In addition to being able to add custom command line options, all
:doc:`management commands</ref/django-admin>` can accept some
@@ -202,6 +224,12 @@ All attributes can be set in your derived class and can be used in
  a list of application names might set this to '<app_label
  app_label ...>'.

  .. deprecated:: 1.8

      This should be done now in the :meth:`~BaseCommand.add_arguments()`
      method, by calling the ``parser.add_argument()`` method. See the
      ``closepoll`` example above.

.. attribute:: BaseCommand.can_import_settings

  A boolean indicating whether the command needs to be able to
@@ -215,11 +243,25 @@ All attributes can be set in your derived class and can be used in
  help message when the user runs the command
  ``python manage.py help <command>``.

.. attribute:: BaseCommand.missing_args_message

.. versionadded:: 1.8

  If your command defines mandatory positional arguments, you can customize
  the message error returned in the case of missing arguments. The default is
  output by :py:mod:`argparse` ("too few arguments").

.. attribute:: BaseCommand.option_list

  This is the list of ``optparse`` options which will be fed
  into the command's ``OptionParser`` for parsing arguments.

  .. deprecated:: 1.8

      You should now override the :meth:`~BaseCommand.add_arguments` method to
      add custom arguments accepted by your command.
      See :ref:`the example above <custom-commands-options>`.

.. attribute:: BaseCommand.output_transaction

  A boolean indicating whether the command outputs SQL
@@ -287,6 +329,15 @@ the :meth:`~BaseCommand.handle` method must be implemented.
            super(Command, self).__init__(*args, **kwargs)
            # ...

.. method:: BaseCommand.add_arguments(parser)

.. versionadded:: 1.8

    Entry point to add parser arguments to handle command line arguments passed
    to the command. Custom commands should override this method to add both
    positional and optional arguments accepted by the command. Calling
    ``super()`` is not needed when directly subclassing ``BaseCommand``.

.. method:: BaseCommand.get_version()

    Return the Django version, which should be correct for all
+3 −0
Original line number Diff line number Diff line
@@ -35,6 +35,9 @@ about each item can often be found in the release notes of two versions prior.
* The ability to :func:`~django.core.urlresolvers.reverse` URLs using a dotted
  Python path will be removed.

* Support for :py:mod:`optparse` will be dropped for custom management commands
  (replaced by :py:mod:`argparse`).

.. _deprecation-removed-in-1.9:

1.9
+25 −0
Original line number Diff line number Diff line
@@ -280,6 +280,20 @@ Now, an error will be raised to prevent data loss::
    ...
    ValueError: Cannot assign "<Author: John>": "Author" instance isn't saved in the database.

Management commands that only accept positional arguments
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

If you have written a custom management command that only accepts positional
arguments and you didn't specify the
:attr:`~django.core.management.BaseCommand.args` command variable, you might
get an error like ``Error: unrecognized arguments: ...``, as variable parsing
is now based on :py:mod:`argparse` which doesn't implicitly accept positional
arguments. You can make your command backwards compatible by simply setting the
:attr:`~django.core.management.BaseCommand.args` class variable. However, if
you don't have to keep compatibility with older Django versions, it's better to
implement the new :meth:`~django.core.management.BaseCommand.add_arguments`
method as described in :doc:`/howto/custom-management-commands`.

Miscellaneous
~~~~~~~~~~~~~

@@ -409,3 +423,14 @@ Similarly for GIS sitemaps, add ``name='django.contrib.gis.sitemaps.views.kml'``
or ``name='django.contrib.gis.sitemaps.views.kmz'``.

.. _security issue: https://www.djangoproject.com/weblog/2014/apr/21/security/#s-issue-unexpected-code-execution-using-reverse

Extending management command arguments through ``Command.option_list``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Management commands now use :py:mod:`argparse` instead of :py:mod:`optparse` to
parse command-line arguments passed to commands. This also means that the way
to add custom arguments to commands has changed: instead of extending the
``option_list`` class list, you should now override the
:meth:`~django.core.management.BaseCommand.add_arguments` method and add
arguments through ``argparse.add_argument()``. See
:ref:`this example <custom-commands-options>` for more details.