Documented optparse to argparse changes for management commands

This commit is contained in:
Claude Paroz 2014-06-06 17:55:56 +02:00
parent 8568638603
commit cbff097bd9
3 changed files with 93 additions and 14 deletions

View File

@ -50,13 +50,15 @@ look like this:
from polls.models import Poll from polls.models import Poll
class Command(BaseCommand): class Command(BaseCommand):
args = '<poll_id poll_id ...>'
help = 'Closes the specified poll for voting' 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): def handle(self, *args, **options):
for poll_id in args: for poll_id in options['poll_id']:
try: try:
poll = Poll.objects.get(pk=int(poll_id)) poll = Poll.objects.get(pk=poll_id)
except Poll.DoesNotExist: except Poll.DoesNotExist:
raise CommandError('Poll "%s" does not exist' % poll_id) 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) 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: .. _management-commands-output:
.. note:: .. note::
@ -81,28 +91,34 @@ look like this:
The new custom command can be called using ``python manage.py closepoll The new custom command can be called using ``python manage.py closepoll
<poll_id>``. <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 to ``False`` for each one. If the user referenced any nonexistent polls, a
:class:`CommandError` is raised. The ``poll.opened`` attribute does not exist :class:`CommandError` is raised. The ``poll.opened`` attribute does not exist
in the :doc:`tutorial</intro/tutorial01>` and was added to in the :doc:`tutorial</intro/tutorial01>` and was added to
``polls.models.Poll`` for this example. ``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 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 of closing it by accepting additional command line options. These custom
must be added to :attr:`~BaseCommand.option_list` like this: options can be added in the :meth:`~BaseCommand.add_arguments` method like this:
.. code-block:: python .. code-block:: python
from optparse import make_option
class Command(BaseCommand): class Command(BaseCommand):
option_list = BaseCommand.option_list + ( def add_arguments(self, parser):
make_option('--delete', # Positional arguments
parser.add_argument('poll_id', nargs='+', type=int)
# Named (optional) arguments
parser.add_argument('--delete',
action='store_true', action='store_true',
dest='delete', dest='delete',
default=False, default=False,
help='Delete poll instead of closing it'), help='Delete poll instead of closing it')
)
def handle(self, *args, **options): def handle(self, *args, **options):
# ... # ...
@ -110,9 +126,15 @@ must be added to :attr:`~BaseCommand.option_list` like this:
poll.delete() 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 The option (``delete`` in our example) is available in the options dict
parameter of the handle method. See the :py:mod:`optparse` Python documentation parameter of the handle method. See the :py:mod:`argparse` Python documentation
for more about ``make_option`` usage. for more about ``add_argument`` usage.
In addition to being able to add custom command line options, all In addition to being able to add custom command line options, all
:doc:`management commands</ref/django-admin>` can accept some :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 a list of application names might set this to '<app_label
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 .. attribute:: BaseCommand.can_import_settings
A boolean indicating whether the command needs to be able to 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 help message when the user runs the command
``python manage.py help <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 .. attribute:: BaseCommand.option_list
This is the list of ``optparse`` options which will be fed This is the list of ``optparse`` options which will be fed
into the command's ``OptionParser`` for parsing arguments. 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 .. attribute:: BaseCommand.output_transaction
A boolean indicating whether the command outputs SQL 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) 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() .. method:: BaseCommand.get_version()
Return the Django version, which should be correct for all Return the Django version, which should be correct for all

View File

@ -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 * The ability to :func:`~django.core.urlresolvers.reverse` URLs using a dotted
Python path will be removed. 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: .. _deprecation-removed-in-1.9:
1.9 1.9

View File

@ -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. 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 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'``. 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 .. _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.