Fixed #19973 -- Replaced optparse by argparse in management commands
Thanks Tim Graham for the review.
This commit is contained in:
parent
79956d0694
commit
8568638603
|
@ -1,6 +1,7 @@
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import collections
|
import collections
|
||||||
from importlib import import_module
|
from importlib import import_module
|
||||||
from optparse import OptionParser, NO_DEFAULT
|
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
|
@ -8,7 +9,8 @@ import django
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.management.base import BaseCommand, CommandError, handle_default_options
|
from django.core.management.base import (BaseCommand, CommandError,
|
||||||
|
CommandParser, handle_default_options)
|
||||||
from django.core.management.color import color_style
|
from django.core.management.color import color_style
|
||||||
from django.utils import lru_cache
|
from django.utils import lru_cache
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
|
@ -93,78 +95,21 @@ def call_command(name, *args, **options):
|
||||||
|
|
||||||
if isinstance(app_name, BaseCommand):
|
if isinstance(app_name, BaseCommand):
|
||||||
# If the command is already loaded, use it directly.
|
# If the command is already loaded, use it directly.
|
||||||
klass = app_name
|
command = app_name
|
||||||
else:
|
else:
|
||||||
klass = load_command_class(app_name, name)
|
command = load_command_class(app_name, name)
|
||||||
|
|
||||||
# Grab out a list of defaults from the options. optparse does this for us
|
# Simulate argument parsing to get the option defaults (see #10080 for details).
|
||||||
# when the script runs from the command line, but since call_command can
|
parser = command.create_parser('', name)
|
||||||
# be called programmatically, we need to simulate the loading and handling
|
if command.use_argparse:
|
||||||
# of defaults (see #10080 for details).
|
defaults = parser.parse_args(args=args)
|
||||||
defaults = {}
|
defaults = dict(defaults._get_kwargs(), **options)
|
||||||
for opt in klass.option_list:
|
|
||||||
if opt.default is NO_DEFAULT:
|
|
||||||
defaults[opt.dest] = None
|
|
||||||
else:
|
else:
|
||||||
defaults[opt.dest] = opt.default
|
# Legacy optparse method
|
||||||
defaults.update(options)
|
defaults, _ = parser.parse_args(args=[])
|
||||||
|
defaults = dict(defaults.__dict__, **options)
|
||||||
|
|
||||||
return klass.execute(*args, **defaults)
|
return command.execute(*args, **defaults)
|
||||||
|
|
||||||
|
|
||||||
class LaxOptionParser(OptionParser):
|
|
||||||
"""
|
|
||||||
An option parser that doesn't raise any errors on unknown options.
|
|
||||||
|
|
||||||
This is needed because the --settings and --pythonpath options affect
|
|
||||||
the commands (and thus the options) that are available to the user.
|
|
||||||
"""
|
|
||||||
def error(self, msg):
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_help(self):
|
|
||||||
"""Output nothing.
|
|
||||||
|
|
||||||
The lax options are included in the normal option parser, so under
|
|
||||||
normal usage, we don't need to print the lax options.
|
|
||||||
"""
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_lax_help(self):
|
|
||||||
"""Output the basic options available to every command.
|
|
||||||
|
|
||||||
This just redirects to the default print_help() behavior.
|
|
||||||
"""
|
|
||||||
OptionParser.print_help(self)
|
|
||||||
|
|
||||||
def _process_args(self, largs, rargs, values):
|
|
||||||
"""
|
|
||||||
Overrides OptionParser._process_args to exclusively handle default
|
|
||||||
options and ignore args and other options.
|
|
||||||
|
|
||||||
This overrides the behavior of the super class, which stop parsing
|
|
||||||
at the first unrecognized option.
|
|
||||||
"""
|
|
||||||
while rargs:
|
|
||||||
arg = rargs[0]
|
|
||||||
try:
|
|
||||||
if arg[0:2] == "--" and len(arg) > 2:
|
|
||||||
# process a single long option (possibly with value(s))
|
|
||||||
# the superclass code pops the arg off rargs
|
|
||||||
self._process_long_opt(rargs, values)
|
|
||||||
elif arg[:1] == "-" and len(arg) > 1:
|
|
||||||
# process a cluster of short options (possibly with
|
|
||||||
# value(s) for the last one only)
|
|
||||||
# the superclass code pops the arg off rargs
|
|
||||||
self._process_short_opts(rargs, values)
|
|
||||||
else:
|
|
||||||
# it's either a non-default option or an arg
|
|
||||||
# either way, add it to the args list so we can keep
|
|
||||||
# dealing with options
|
|
||||||
del rargs[0]
|
|
||||||
raise Exception
|
|
||||||
except: # Needed because we might need to catch a SystemExit
|
|
||||||
largs.append(arg)
|
|
||||||
|
|
||||||
|
|
||||||
class ManagementUtility(object):
|
class ManagementUtility(object):
|
||||||
|
@ -296,8 +241,13 @@ class ManagementUtility(object):
|
||||||
# Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
|
# Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
|
||||||
# user will find out once they execute the command.
|
# user will find out once they execute the command.
|
||||||
pass
|
pass
|
||||||
|
parser = subcommand_cls.create_parser('', cwords[0])
|
||||||
|
if subcommand_cls.use_argparse:
|
||||||
|
options += [(sorted(s_opt.option_strings)[0], s_opt.nargs != 0) for s_opt in
|
||||||
|
parser._actions if s_opt.option_strings]
|
||||||
|
else:
|
||||||
options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in
|
options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in
|
||||||
subcommand_cls.option_list]
|
parser.option_list]
|
||||||
# filter out previously specified options from available options
|
# filter out previously specified options from available options
|
||||||
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
prev_opts = [x.split('=')[0] for x in cwords[1:cword - 1]]
|
||||||
options = [opt for opt in options if opt[0] not in prev_opts]
|
options = [opt for opt in options if opt[0] not in prev_opts]
|
||||||
|
@ -317,23 +267,24 @@ class ManagementUtility(object):
|
||||||
Given the command-line arguments, this figures out which subcommand is
|
Given the command-line arguments, this figures out which subcommand is
|
||||||
being run, creates a parser appropriate to that command, and runs it.
|
being run, creates a parser appropriate to that command, and runs it.
|
||||||
"""
|
"""
|
||||||
# Preprocess options to extract --settings and --pythonpath.
|
|
||||||
# These options could affect the commands that are available, so they
|
|
||||||
# must be processed early.
|
|
||||||
parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
|
|
||||||
version=django.get_version(),
|
|
||||||
option_list=BaseCommand.option_list)
|
|
||||||
try:
|
|
||||||
options, args = parser.parse_args(self.argv)
|
|
||||||
handle_default_options(options)
|
|
||||||
except: # Needed because parser.parse_args can raise SystemExit
|
|
||||||
pass # Ignore any option errors at this point.
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
subcommand = self.argv[1]
|
subcommand = self.argv[1]
|
||||||
except IndexError:
|
except IndexError:
|
||||||
subcommand = 'help' # Display help if no arguments were given.
|
subcommand = 'help' # Display help if no arguments were given.
|
||||||
|
|
||||||
|
# Preprocess options to extract --settings and --pythonpath.
|
||||||
|
# These options could affect the commands that are available, so they
|
||||||
|
# must be processed early.
|
||||||
|
parser = CommandParser(None, usage="%(prog)s subcommand [options] [args]", add_help=False)
|
||||||
|
parser.add_argument('--settings')
|
||||||
|
parser.add_argument('--pythonpath')
|
||||||
|
parser.add_argument('args', nargs='*') # catch-all
|
||||||
|
try:
|
||||||
|
options, args = parser.parse_known_args(self.argv[2:])
|
||||||
|
handle_default_options(options)
|
||||||
|
except CommandError:
|
||||||
|
pass # Ignore any option errors at this point.
|
||||||
|
|
||||||
no_settings_commands = [
|
no_settings_commands = [
|
||||||
'help', 'version', '--help', '--version', '-h',
|
'help', 'version', '--help', '--version', '-h',
|
||||||
'compilemessages', 'makemessages',
|
'compilemessages', 'makemessages',
|
||||||
|
@ -355,22 +306,17 @@ class ManagementUtility(object):
|
||||||
self.autocomplete()
|
self.autocomplete()
|
||||||
|
|
||||||
if subcommand == 'help':
|
if subcommand == 'help':
|
||||||
if len(args) <= 2:
|
if '--commands' in args:
|
||||||
parser.print_lax_help()
|
|
||||||
sys.stdout.write(self.main_help_text() + '\n')
|
|
||||||
elif args[2] == '--commands':
|
|
||||||
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
|
sys.stdout.write(self.main_help_text(commands_only=True) + '\n')
|
||||||
|
elif len(options.args) < 1:
|
||||||
|
sys.stdout.write(self.main_help_text() + '\n')
|
||||||
else:
|
else:
|
||||||
self.fetch_command(args[2]).print_help(self.prog_name, args[2])
|
self.fetch_command(options.args[0]).print_help(self.prog_name, options.args[0])
|
||||||
elif subcommand == 'version':
|
|
||||||
sys.stdout.write(parser.get_version() + '\n')
|
|
||||||
# Special-cases: We want 'django-admin.py --version' and
|
# Special-cases: We want 'django-admin.py --version' and
|
||||||
# 'django-admin.py --help' to work, for backwards compatibility.
|
# 'django-admin.py --help' to work, for backwards compatibility.
|
||||||
elif self.argv[1:] == ['--version']:
|
elif subcommand == 'version' or self.argv[1:] == ['--version']:
|
||||||
# LaxOptionParser already takes care of printing the version.
|
sys.stdout.write(django.get_version() + '\n')
|
||||||
pass
|
|
||||||
elif self.argv[1:] in (['--help'], ['-h']):
|
elif self.argv[1:] in (['--help'], ['-h']):
|
||||||
parser.print_lax_help()
|
|
||||||
sys.stdout.write(self.main_help_text() + '\n')
|
sys.stdout.write(self.main_help_text() + '\n')
|
||||||
else:
|
else:
|
||||||
self.fetch_command(subcommand).run_from_argv(self.argv)
|
self.fetch_command(subcommand).run_from_argv(self.argv)
|
||||||
|
|
|
@ -11,13 +11,14 @@ import os
|
||||||
import sys
|
import sys
|
||||||
import warnings
|
import warnings
|
||||||
|
|
||||||
from optparse import make_option, OptionParser
|
from argparse import ArgumentParser
|
||||||
|
from optparse import OptionParser
|
||||||
|
|
||||||
import django
|
import django
|
||||||
from django.core import checks
|
from django.core import checks
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.management.color import color_style, no_style
|
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
|
from django.utils.encoding import force_str
|
||||||
|
|
||||||
|
|
||||||
|
@ -37,6 +38,27 @@ class CommandError(Exception):
|
||||||
pass
|
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):
|
def handle_default_options(options):
|
||||||
"""
|
"""
|
||||||
Include any default options that all commands should accept here
|
Include any default options that all commands should accept here
|
||||||
|
@ -91,7 +113,7 @@ class BaseCommand(object):
|
||||||
and calls its ``run_from_argv()`` method.
|
and calls its ``run_from_argv()`` method.
|
||||||
|
|
||||||
2. The ``run_from_argv()`` method calls ``create_parser()`` to get
|
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
|
any environment changes requested by options like
|
||||||
``pythonpath``, and then calls the ``execute()`` method,
|
``pythonpath``, and then calls the ``execute()`` method,
|
||||||
passing the parsed arguments.
|
passing the parsed arguments.
|
||||||
|
@ -133,6 +155,7 @@ class BaseCommand(object):
|
||||||
``option_list``
|
``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 and will be removed in Django 2.0.
|
||||||
|
|
||||||
``output_transaction``
|
``output_transaction``
|
||||||
A boolean indicating whether the command outputs SQL
|
A boolean indicating whether the command outputs SQL
|
||||||
|
@ -180,19 +203,7 @@ class BaseCommand(object):
|
||||||
settings. This condition will generate a CommandError.
|
settings. This condition will generate a CommandError.
|
||||||
"""
|
"""
|
||||||
# Metadata about this command.
|
# Metadata about this command.
|
||||||
option_list = (
|
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."),
|
|
||||||
)
|
|
||||||
help = ''
|
help = ''
|
||||||
args = ''
|
args = ''
|
||||||
|
|
||||||
|
@ -232,6 +243,10 @@ class BaseCommand(object):
|
||||||
self.requires_model_validation if has_old_option else
|
self.requires_model_validation if has_old_option else
|
||||||
True)
|
True)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def use_argparse(self):
|
||||||
|
return not bool(self.option_list)
|
||||||
|
|
||||||
def get_version(self):
|
def get_version(self):
|
||||||
"""
|
"""
|
||||||
Return the Django version, which should be correct for all
|
Return the Django version, which should be correct for all
|
||||||
|
@ -255,14 +270,56 @@ class BaseCommand(object):
|
||||||
|
|
||||||
def create_parser(self, prog_name, subcommand):
|
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.
|
parse the arguments to this command.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return OptionParser(prog=prog_name,
|
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),
|
usage=self.usage(subcommand),
|
||||||
version=self.get_version(),
|
version=self.get_version())
|
||||||
option_list=self.option_list)
|
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):
|
def print_help(self, prog_name, subcommand):
|
||||||
"""
|
"""
|
||||||
|
@ -282,10 +339,22 @@ class BaseCommand(object):
|
||||||
``Exception`` is not ``CommandError``, raise it.
|
``Exception`` is not ``CommandError``, raise it.
|
||||||
"""
|
"""
|
||||||
parser = self.create_parser(argv[0], argv[1])
|
parser = self.create_parser(argv[0], argv[1])
|
||||||
|
|
||||||
|
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:])
|
options, args = parser.parse_args(argv[2:])
|
||||||
|
cmd_options = vars(options)
|
||||||
handle_default_options(options)
|
handle_default_options(options)
|
||||||
try:
|
try:
|
||||||
self.execute(*args, **options.__dict__)
|
self.execute(*args, **cmd_options)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if options.traceback or not isinstance(e, CommandError):
|
if options.traceback or not isinstance(e, CommandError):
|
||||||
raise
|
raise
|
||||||
|
@ -433,12 +502,14 @@ class AppCommand(BaseCommand):
|
||||||
Rather than implementing ``handle()``, subclasses must implement
|
Rather than implementing ``handle()``, subclasses must implement
|
||||||
``handle_app_config()``, which will be called once for each application.
|
``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):
|
def handle(self, *app_labels, **options):
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
if not app_labels:
|
|
||||||
raise CommandError("Enter at least one application label.")
|
|
||||||
try:
|
try:
|
||||||
app_configs = [apps.get_app_config(app_label) for app_label in app_labels]
|
app_configs = [apps.get_app_config(app_label) for app_label in app_labels]
|
||||||
except (LookupError, ImportError) as e:
|
except (LookupError, ImportError) as e:
|
||||||
|
@ -490,13 +561,13 @@ class LabelCommand(BaseCommand):
|
||||||
``AppCommand`` instead.
|
``AppCommand`` instead.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
args = '<label label ...>'
|
|
||||||
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):
|
def handle(self, *labels, **options):
|
||||||
if not labels:
|
|
||||||
raise CommandError('Enter at least one %s.' % self.label)
|
|
||||||
|
|
||||||
output = []
|
output = []
|
||||||
for label in labels:
|
for label in labels:
|
||||||
label_output = self.handle_label(label, **options)
|
label_output = self.handle_label(label, **options)
|
||||||
|
|
|
@ -50,13 +50,11 @@ class Command(BaseCommand):
|
||||||
super(Command, self).run_from_argv(argv)
|
super(Command, self).run_from_argv(argv)
|
||||||
|
|
||||||
def create_parser(self, prog_name, subcommand):
|
def create_parser(self, prog_name, subcommand):
|
||||||
|
parser = super(Command, self).create_parser(prog_name, subcommand)
|
||||||
test_runner_class = get_runner(settings, self.test_runner)
|
test_runner_class = get_runner(settings, self.test_runner)
|
||||||
options = self.option_list + getattr(
|
for opt in getattr(test_runner_class, 'option_list', ()):
|
||||||
test_runner_class, 'option_list', ())
|
parser.add_option(opt)
|
||||||
return OptionParser(prog=prog_name,
|
return parser
|
||||||
usage=self.usage(subcommand),
|
|
||||||
version=self.get_version(),
|
|
||||||
option_list=options)
|
|
||||||
|
|
||||||
def execute(self, *args, **options):
|
def execute(self, *args, **options):
|
||||||
if int(options['verbosity']) > 0:
|
if int(options['verbosity']) > 0:
|
||||||
|
|
|
@ -4,7 +4,6 @@ from django.core.management.base import AppCommand
|
||||||
class Command(AppCommand):
|
class Command(AppCommand):
|
||||||
help = 'Test Application-based commands'
|
help = 'Test Application-based commands'
|
||||||
requires_system_checks = False
|
requires_system_checks = False
|
||||||
args = '[app_label ...]'
|
|
||||||
|
|
||||||
def handle_app_config(self, app_config, **options):
|
def handle_app_config(self, app_config, **options):
|
||||||
print('EXECUTE:AppCommand name=%s, options=%s' % (app_config.name, sorted(options.items())))
|
print('EXECUTE:AppCommand name=%s, options=%s' % (app_config.name, sorted(options.items())))
|
||||||
|
|
|
@ -930,21 +930,21 @@ class ManageAlternateSettings(AdminScriptTestCase):
|
||||||
"alternate: manage.py can execute user commands if settings are provided as argument"
|
"alternate: manage.py can execute user commands if settings are provided as argument"
|
||||||
args = ['noargs_command', '--settings=alternate_settings']
|
args = ['noargs_command', '--settings=alternate_settings']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:NoArgsCommand options=[('no_color', False), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:NoArgsCommand options=[('no_color', False), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', False), ('verbosity', 1)]")
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
|
|
||||||
def test_custom_command_with_environment(self):
|
def test_custom_command_with_environment(self):
|
||||||
"alternate: manage.py can execute user commands if settings are provided in environment"
|
"alternate: manage.py can execute user commands if settings are provided in environment"
|
||||||
args = ['noargs_command']
|
args = ['noargs_command']
|
||||||
out, err = self.run_manage(args, 'alternate_settings')
|
out, err = self.run_manage(args, 'alternate_settings')
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:NoArgsCommand options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:NoArgsCommand options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
|
|
||||||
def test_custom_command_output_color(self):
|
def test_custom_command_output_color(self):
|
||||||
"alternate: manage.py output syntax color can be deactivated with the `--no-color` option"
|
"alternate: manage.py output syntax color can be deactivated with the `--no-color` option"
|
||||||
args = ['noargs_command', '--no-color', '--settings=alternate_settings']
|
args = ['noargs_command', '--no-color', '--settings=alternate_settings']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:NoArgsCommand options=[('no_color', True), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:NoArgsCommand options=[('no_color', True), ('pythonpath', None), ('settings', 'alternate_settings'), ('traceback', False), ('verbosity', 1)]")
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
|
|
||||||
|
|
||||||
|
@ -1340,13 +1340,13 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
def test_version_alternative(self):
|
def test_version_alternative(self):
|
||||||
"--version is equivalent to version"
|
"--version is equivalent to version"
|
||||||
args1, args2 = ['version'], ['--version']
|
args1, args2 = ['version'], ['--version']
|
||||||
self.assertEqual(self.run_manage(args1), self.run_manage(args2))
|
# It's possible one outputs on stderr and the other on stdout, hence the set
|
||||||
|
self.assertEqual(set(self.run_manage(args1)), set(self.run_manage(args2)))
|
||||||
|
|
||||||
def test_help(self):
|
def test_help(self):
|
||||||
"help is handled as a special case"
|
"help is handled as a special case"
|
||||||
args = ['help']
|
args = ['help']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertOutput(out, "Usage: manage.py subcommand [options] [args]")
|
|
||||||
self.assertOutput(out, "Type 'manage.py help <subcommand>' for help on a specific subcommand.")
|
self.assertOutput(out, "Type 'manage.py help <subcommand>' for help on a specific subcommand.")
|
||||||
self.assertOutput(out, '[django]')
|
self.assertOutput(out, '[django]')
|
||||||
self.assertOutput(out, 'startapp')
|
self.assertOutput(out, 'startapp')
|
||||||
|
@ -1356,7 +1356,7 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
"help --commands shows the list of all available commands"
|
"help --commands shows the list of all available commands"
|
||||||
args = ['help', '--commands']
|
args = ['help', '--commands']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNotInOutput(out, 'Usage:')
|
self.assertNotInOutput(out, 'usage:')
|
||||||
self.assertNotInOutput(out, 'Options:')
|
self.assertNotInOutput(out, 'Options:')
|
||||||
self.assertNotInOutput(out, '[django]')
|
self.assertNotInOutput(out, '[django]')
|
||||||
self.assertOutput(out, 'startapp')
|
self.assertOutput(out, 'startapp')
|
||||||
|
@ -1489,13 +1489,13 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
args = ['noargs_command']
|
args = ['noargs_command']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:NoArgsCommand options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:NoArgsCommand options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
|
|
||||||
def test_noargs_with_args(self):
|
def test_noargs_with_args(self):
|
||||||
"NoArg Commands raise an error if an argument is provided"
|
"NoArg Commands raise an error if an argument is provided"
|
||||||
args = ['noargs_command', 'argument']
|
args = ['noargs_command', 'argument']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertOutput(err, "Error: Command doesn't accept any arguments")
|
self.assertOutput(err, "Error: unrecognized arguments: argument")
|
||||||
|
|
||||||
def test_app_command(self):
|
def test_app_command(self):
|
||||||
"User AppCommands can execute when a single app name is provided"
|
"User AppCommands can execute when a single app name is provided"
|
||||||
|
@ -1503,7 +1503,7 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
||||||
self.assertOutput(out, str_prefix(", options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, ", options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
|
|
||||||
def test_app_command_no_apps(self):
|
def test_app_command_no_apps(self):
|
||||||
"User AppCommands raise an error when no app name is provided"
|
"User AppCommands raise an error when no app name is provided"
|
||||||
|
@ -1517,9 +1517,9 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.auth, options=")
|
||||||
self.assertOutput(out, str_prefix(", options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, ", options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.contenttypes, options=")
|
self.assertOutput(out, "EXECUTE:AppCommand name=django.contrib.contenttypes, options=")
|
||||||
self.assertOutput(out, str_prefix(", options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, ", options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
|
|
||||||
def test_app_command_invalid_app_label(self):
|
def test_app_command_invalid_app_label(self):
|
||||||
"User AppCommands can execute when a single app name is provided"
|
"User AppCommands can execute when a single app name is provided"
|
||||||
|
@ -1538,7 +1538,7 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
args = ['label_command', 'testlabel']
|
args = ['label_command', 'testlabel']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
|
|
||||||
def test_label_command_no_label(self):
|
def test_label_command_no_label(self):
|
||||||
"User LabelCommands raise an error if no label is provided"
|
"User LabelCommands raise an error if no label is provided"
|
||||||
|
@ -1551,8 +1551,8 @@ class CommandTypes(AdminScriptTestCase):
|
||||||
args = ['label_command', 'testlabel', 'anotherlabel']
|
args = ['label_command', 'testlabel', 'anotherlabel']
|
||||||
out, err = self.run_manage(args)
|
out, err = self.run_manage(args)
|
||||||
self.assertNoOutput(err)
|
self.assertNoOutput(err)
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:LabelCommand label=testlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
self.assertOutput(out, str_prefix("EXECUTE:LabelCommand label=anotherlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', None), ('verbosity', %(_)s'1')]"))
|
self.assertOutput(out, "EXECUTE:LabelCommand label=anotherlabel, options=[('no_color', False), ('pythonpath', None), ('settings', None), ('traceback', False), ('verbosity', 1)]")
|
||||||
|
|
||||||
def test_requires_model_validation_and_requires_system_checks_both_defined(self):
|
def test_requires_model_validation_and_requires_system_checks_both_defined(self):
|
||||||
with warnings.catch_warnings(record=True):
|
with warnings.catch_warnings(record=True):
|
||||||
|
@ -1587,8 +1587,8 @@ class ArgumentOrder(AdminScriptTestCase):
|
||||||
"""Tests for 2-stage argument parsing scheme.
|
"""Tests for 2-stage argument parsing scheme.
|
||||||
|
|
||||||
django-admin command arguments are parsed in 2 parts; the core arguments
|
django-admin command arguments are parsed in 2 parts; the core arguments
|
||||||
(--settings, --traceback and --pythonpath) are parsed using a Lax parser.
|
(--settings, --traceback and --pythonpath) are parsed using a basic parser,
|
||||||
This Lax parser ignores any unknown options. Then the full settings are
|
ignoring any unknown options. Then the full settings are
|
||||||
passed to the command parser, which extracts commands of interest to the
|
passed to the command parser, which extracts commands of interest to the
|
||||||
individual command.
|
individual command.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -46,13 +46,13 @@ class BashCompletionTests(unittest.TestCase):
|
||||||
|
|
||||||
def test_django_admin_py(self):
|
def test_django_admin_py(self):
|
||||||
"django_admin.py will autocomplete option flags"
|
"django_admin.py will autocomplete option flags"
|
||||||
self._user_input('django-admin.py sqlall --v')
|
self._user_input('django-admin.py sqlall --verb')
|
||||||
output = self._run_autocomplete()
|
output = self._run_autocomplete()
|
||||||
self.assertEqual(output, ['--verbosity='])
|
self.assertEqual(output, ['--verbosity='])
|
||||||
|
|
||||||
def test_manage_py(self):
|
def test_manage_py(self):
|
||||||
"manage.py will autocomplete option flags"
|
"manage.py will autocomplete option flags"
|
||||||
self._user_input('manage.py sqlall --v')
|
self._user_input('manage.py sqlall --verb')
|
||||||
output = self._run_autocomplete()
|
output = self._run_autocomplete()
|
||||||
self.assertEqual(output, ['--verbosity='])
|
self.assertEqual(output, ['--verbosity='])
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
from optparse import make_option
|
||||||
|
|
||||||
|
from django.core.management.base import BaseCommand, CommandError
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = "Test optparse compatibility."
|
||||||
|
args = ''
|
||||||
|
|
||||||
|
option_list = BaseCommand.option_list + (
|
||||||
|
make_option("-s", "--style", default="Rock'n'Roll"),
|
||||||
|
make_option("-x", "--example")
|
||||||
|
)
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
example = options["example"]
|
||||||
|
# BaseCommand default option is available
|
||||||
|
options['verbosity']
|
||||||
|
self.stdout.write("All right, let's dance %s." % options["style"])
|
|
@ -74,6 +74,24 @@ 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_optparse_compatibility(self):
|
||||||
|
"""
|
||||||
|
optparse should be supported during Django 1.8/1.9 releases.
|
||||||
|
"""
|
||||||
|
out = StringIO()
|
||||||
|
management.call_command('optparse_cmd', stdout=out)
|
||||||
|
self.assertEqual(out.getvalue(), "All right, let's dance Rock'n'Roll.\n")
|
||||||
|
|
||||||
|
# Simulate command line execution
|
||||||
|
old_stdout, old_stderr = sys.stdout, sys.stderr
|
||||||
|
sys.stdout, sys.stderr = StringIO(), StringIO()
|
||||||
|
try:
|
||||||
|
management.execute_from_command_line(['django-admin', 'optparse_cmd'])
|
||||||
|
finally:
|
||||||
|
output = sys.stdout.getvalue()
|
||||||
|
sys.stdout, sys.stderr = old_stdout, old_stderr
|
||||||
|
self.assertEqual(output, "All right, let's dance Rock'n'Roll.\n")
|
||||||
|
|
||||||
|
|
||||||
class UtilsTests(SimpleTestCase):
|
class UtilsTests(SimpleTestCase):
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue