mirror of
https://github.com/django/django.git
synced 2025-10-26 15:16:09 +00:00
Fixed #19973 -- Replaced optparse by argparse in management commands
Thanks Tim Graham for the review.
This commit is contained in:
@@ -11,13 +11,14 @@ import os
|
||||
import sys
|
||||
import warnings
|
||||
|
||||
from optparse import make_option, OptionParser
|
||||
from argparse import ArgumentParser
|
||||
from optparse import OptionParser
|
||||
|
||||
import django
|
||||
from django.core import checks
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
from django.core.management.color import color_style, no_style
|
||||
from django.utils.deprecation import RemovedInDjango19Warning
|
||||
from django.utils.deprecation import RemovedInDjango19Warning, RemovedInDjango20Warning
|
||||
from django.utils.encoding import force_str
|
||||
|
||||
|
||||
@@ -37,6 +38,27 @@ class CommandError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class CommandParser(ArgumentParser):
|
||||
"""
|
||||
Customized ArgumentParser class to improve some error messages and prevent
|
||||
SystemExit in several occasions, as SystemExit is unacceptable when a
|
||||
command is called programmatically.
|
||||
"""
|
||||
def __init__(self, cmd, **kwargs):
|
||||
self.cmd = cmd
|
||||
super(CommandParser, self).__init__(**kwargs)
|
||||
|
||||
def parse_args(self, args=None, namespace=None):
|
||||
# Catch missing argument for a better error message
|
||||
if (hasattr(self.cmd, 'missing_args_message') and
|
||||
not (args or any([not arg.startswith('-') for arg in args]))):
|
||||
raise CommandError("Error: %s" % self.cmd.missing_args_message)
|
||||
return super(CommandParser, self).parse_args(args, namespace)
|
||||
|
||||
def error(self, message):
|
||||
raise CommandError("Error: %s" % message)
|
||||
|
||||
|
||||
def handle_default_options(options):
|
||||
"""
|
||||
Include any default options that all commands should accept here
|
||||
@@ -91,7 +113,7 @@ class BaseCommand(object):
|
||||
and calls its ``run_from_argv()`` method.
|
||||
|
||||
2. The ``run_from_argv()`` method calls ``create_parser()`` to get
|
||||
an ``OptionParser`` for the arguments, parses them, performs
|
||||
an ``ArgumentParser`` for the arguments, parses them, performs
|
||||
any environment changes requested by options like
|
||||
``pythonpath``, and then calls the ``execute()`` method,
|
||||
passing the parsed arguments.
|
||||
@@ -133,6 +155,7 @@ class BaseCommand(object):
|
||||
``option_list``
|
||||
This is the list of ``optparse`` options which will be fed
|
||||
into the command's ``OptionParser`` for parsing arguments.
|
||||
Deprecated and will be removed in Django 2.0.
|
||||
|
||||
``output_transaction``
|
||||
A boolean indicating whether the command outputs SQL
|
||||
@@ -180,19 +203,7 @@ class BaseCommand(object):
|
||||
settings. This condition will generate a CommandError.
|
||||
"""
|
||||
# Metadata about this command.
|
||||
option_list = (
|
||||
make_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
|
||||
type='choice', choices=['0', '1', '2', '3'],
|
||||
help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output'),
|
||||
make_option('--settings',
|
||||
help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.'),
|
||||
make_option('--pythonpath',
|
||||
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
|
||||
make_option('--traceback', action='store_true',
|
||||
help='Raise on exception'),
|
||||
make_option('--no-color', action='store_true', dest='no_color', default=False,
|
||||
help="Don't colorize the command output."),
|
||||
)
|
||||
option_list = ()
|
||||
help = ''
|
||||
args = ''
|
||||
|
||||
@@ -232,6 +243,10 @@ class BaseCommand(object):
|
||||
self.requires_model_validation if has_old_option else
|
||||
True)
|
||||
|
||||
@property
|
||||
def use_argparse(self):
|
||||
return not bool(self.option_list)
|
||||
|
||||
def get_version(self):
|
||||
"""
|
||||
Return the Django version, which should be correct for all
|
||||
@@ -255,14 +270,56 @@ class BaseCommand(object):
|
||||
|
||||
def create_parser(self, prog_name, subcommand):
|
||||
"""
|
||||
Create and return the ``OptionParser`` which will be used to
|
||||
Create and return the ``ArgumentParser`` which will be used to
|
||||
parse the arguments to this command.
|
||||
|
||||
"""
|
||||
return OptionParser(prog=prog_name,
|
||||
usage=self.usage(subcommand),
|
||||
version=self.get_version(),
|
||||
option_list=self.option_list)
|
||||
if not self.use_argparse:
|
||||
# Backwards compatibility: use deprecated optparse module
|
||||
warnings.warn("OptionParser usage for Django management commands "
|
||||
"is deprecated, use ArgumentParser instead",
|
||||
RemovedInDjango20Warning)
|
||||
parser = OptionParser(prog=prog_name,
|
||||
usage=self.usage(subcommand),
|
||||
version=self.get_version())
|
||||
parser.add_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
|
||||
type='choice', choices=['0', '1', '2', '3'],
|
||||
help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
|
||||
parser.add_option('--settings',
|
||||
help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
|
||||
parser.add_option('--pythonpath',
|
||||
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
|
||||
parser.add_option('--traceback', action='store_true',
|
||||
help='Raise on exception')
|
||||
parser.add_option('--no-color', action='store_true', dest='no_color', default=False,
|
||||
help="Don't colorize the command output.")
|
||||
for opt in self.option_list:
|
||||
parser.add_option(opt)
|
||||
else:
|
||||
parser = CommandParser(self, prog="%s %s" % (prog_name, subcommand), description=self.help or None)
|
||||
parser.add_argument('--version', action='version', version=self.get_version())
|
||||
parser.add_argument('-v', '--verbosity', action='store', dest='verbosity', default='1',
|
||||
type=int, choices=[0, 1, 2, 3],
|
||||
help='Verbosity level; 0=minimal output, 1=normal output, 2=verbose output, 3=very verbose output')
|
||||
parser.add_argument('--settings',
|
||||
help='The Python path to a settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.')
|
||||
parser.add_argument('--pythonpath',
|
||||
help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".')
|
||||
parser.add_argument('--traceback', action='store_true',
|
||||
help='Raise on exception')
|
||||
parser.add_argument('--no-color', action='store_true', dest='no_color', default=False,
|
||||
help="Don't colorize the command output.")
|
||||
if self.args:
|
||||
# Keep compatibility and always accept positional arguments, like optparse when args is set
|
||||
parser.add_argument('args', nargs='*')
|
||||
self.add_arguments(parser)
|
||||
return parser
|
||||
|
||||
def add_arguments(self, parser):
|
||||
"""
|
||||
Entry point for subclassed commands to add custom arguments.
|
||||
"""
|
||||
pass
|
||||
|
||||
def print_help(self, prog_name, subcommand):
|
||||
"""
|
||||
@@ -282,10 +339,22 @@ class BaseCommand(object):
|
||||
``Exception`` is not ``CommandError``, raise it.
|
||||
"""
|
||||
parser = self.create_parser(argv[0], argv[1])
|
||||
options, args = parser.parse_args(argv[2:])
|
||||
|
||||
if self.use_argparse:
|
||||
options = parser.parse_args(argv[2:])
|
||||
cmd_options = vars(options)
|
||||
# Move positional args out of options to mimic legacy optparse
|
||||
if 'args' in options:
|
||||
args = options.args
|
||||
del cmd_options['args']
|
||||
else:
|
||||
args = ()
|
||||
else:
|
||||
options, args = parser.parse_args(argv[2:])
|
||||
cmd_options = vars(options)
|
||||
handle_default_options(options)
|
||||
try:
|
||||
self.execute(*args, **options.__dict__)
|
||||
self.execute(*args, **cmd_options)
|
||||
except Exception as e:
|
||||
if options.traceback or not isinstance(e, CommandError):
|
||||
raise
|
||||
@@ -433,12 +502,14 @@ class AppCommand(BaseCommand):
|
||||
Rather than implementing ``handle()``, subclasses must implement
|
||||
``handle_app_config()``, which will be called once for each application.
|
||||
"""
|
||||
args = '<app_label app_label ...>'
|
||||
missing_args_message = "Enter at least one application label."
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('args', metavar='app_label', nargs='+',
|
||||
help='One or more application label.')
|
||||
|
||||
def handle(self, *app_labels, **options):
|
||||
from django.apps import apps
|
||||
if not app_labels:
|
||||
raise CommandError("Enter at least one application label.")
|
||||
try:
|
||||
app_configs = [apps.get_app_config(app_label) for app_label in app_labels]
|
||||
except (LookupError, ImportError) as e:
|
||||
@@ -490,13 +561,13 @@ class LabelCommand(BaseCommand):
|
||||
``AppCommand`` instead.
|
||||
|
||||
"""
|
||||
args = '<label label ...>'
|
||||
label = 'label'
|
||||
missing_args_message = "Enter at least one %s." % label
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument('args', metavar=self.label, nargs='+')
|
||||
|
||||
def handle(self, *labels, **options):
|
||||
if not labels:
|
||||
raise CommandError('Enter at least one %s.' % self.label)
|
||||
|
||||
output = []
|
||||
for label in labels:
|
||||
label_output = self.handle_label(label, **options)
|
||||
|
||||
Reference in New Issue
Block a user