diff --git a/django/core/management/__init__.py b/django/core/management/__init__.py index 7a3f9ca7a5d..d39d69eff1e 100644 --- a/django/core/management/__init__.py +++ b/django/core/management/__init__.py @@ -18,9 +18,9 @@ def load_command_class(name): def call_command(name, *args, **options): """ Calls the given command, with the given options and args/kwargs. - + This is the primary API you should use for calling specific commands. - + Some examples: call_command('syncdb') call_command('shell', plain=True) @@ -52,76 +52,59 @@ class ManagementUtility(object): names = [f[:-3] for f in os.listdir(command_dir) if not f.startswith('_') and f.endswith('.py')] return dict([(name, load_command_class(name)) for name in names]) - def usage(self): + def print_help(self, argv): """ - Returns a usage string, for use with optparse. - - The string doesn't include the options (e.g., "--verbose"), because - optparse puts those in automatically. + Returns the help message, as a string. """ - usage = ["%prog command [options]\nactions:"] - commands = self.commands.items() + prog_name = os.path.basename(argv[0]) + usage = ['%s [options] [args]' % prog_name] + usage.append('Django command line tool, version %s' % django.get_version()) + usage.append("Type '%s help ' for help on a specific subcommand." % prog_name) + usage.append('Available subcommands:') + commands = self.commands.keys() commands.sort() - for name, cmd in commands: - usage.append(' %s %s' % (name, cmd.args)) - usage.extend(textwrap.wrap(cmd.help, initial_indent=' ', subsequent_indent=' ')) - usage.append('') - return '\n'.join(usage[:-1]) # Cut off the last list element, an empty space. + for cmd in commands: + usage.append(' %s' % cmd) + print '\n'.join(usage) + + def fetch_command(self, subcommand, command_name): + """ + Tries to fetch the given subcommand, printing a message with the + appropriate command called from the command line (usually + django-admin.py or manage.py) if it can't be found. + """ + try: + return self.commands[subcommand] + except KeyError: + sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % (subcommand, command_name)) + sys.exit(1) def execute(self, argv=None): """ - Parses the given argv from the command line, determines which command - to run and runs the command. + Figures out which command is being run (the first arg), creates a parser + appropriate to that command, and runs it. """ if argv is None: argv = sys.argv - - # Create the parser object and parse the command-line args. - # TODO: Ideally each Command class would register its own options for - # add_option(), but we'd need to figure out how to allow for multiple - # Commands using the same options. The optparse library gets in the way - # by checking for conflicts: - # http://docs.python.org/lib/optparse-conflicts-between-options.html - parser = OptionParser(usage=self.usage(), version=get_version()) - 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('--plain', action='store_true', dest='plain', - help='When using "shell": Tells Django to use plain Python, not IPython.') - parser.add_option('--noinput', action='store_false', dest='interactive', default=True, - help='Tells Django to NOT prompt the user for input of any kind.') - parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True, - help='When using "runserver": Tells Django to NOT use the auto-reloader.') - parser.add_option('--format', default='json', dest='format', - help='Specifies the output serialization format for fixtures') - parser.add_option('--indent', default=None, dest='indent', - type='int', help='Specifies the indent level to use when pretty-printing output') - parser.add_option('--verbosity', action='store', dest='verbosity', default='1', - type='choice', choices=['0', '1', '2'], - help='Verbosity level; 0=minimal output, 1=normal output, 2=all output') - parser.add_option('--adminmedia', dest='admin_media_path', default='', - help='When using "runserver": Specifies the directory from which to serve admin media.') - options, args = parser.parse_args(argv[1:]) - - # If the 'settings' or 'pythonpath' options were submitted, activate those. - if options.settings: - os.environ['DJANGO_SETTINGS_MODULE'] = options.settings - if options.pythonpath: - sys.path.insert(0, options.pythonpath) - - # Run the appropriate command. try: - command_name = args[0] + command_name = argv[1] except IndexError: - sys.stderr.write("Type '%s --help' for usage.\n" % os.path.basename(argv[0])) + sys.stderr.write("Type '%s help' for usage.\n" % os.path.basename(argv[0])) sys.exit(1) - try: - command = self.commands[command_name] - except KeyError: - sys.stderr.write("Unknown command: %r\nType '%s --help' for usage.\n" % (command_name, os.path.basename(argv[0]))) - sys.exit(1) - command.execute(*args[1:], **options.__dict__) + + if command_name == 'help': + if len(argv) > 2: + self.fetch_command(argv[2], argv[0]).print_help(argv[2:]) + else: + self.print_help(argv) + # Special-cases: We want 'django-admin.py --version' and + # 'django-admin.py --help' to work, for backwards compatibility. + elif argv[1:] == ['--version']: + print django.get_version() + elif argv[1:] == ['--help']: + self.print_help(argv) + else: + self.fetch_command(command_name, argv[0]).run(argv[1:]) class ProjectManagementUtility(ManagementUtility): """ diff --git a/django/core/management/base.py b/django/core/management/base.py index 978aa7902a4..fb43895b51f 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -1,13 +1,23 @@ +import django from django.core.exceptions import ImproperlyConfigured from django.core.management.color import color_style +import itertools +from optparse import make_option, OptionParser import sys import os +from traceback import print_exc class CommandError(Exception): pass class BaseCommand(object): # Metadata about this command. + option_list = ( + 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".'), + ) help = '' args = '' @@ -19,6 +29,43 @@ class BaseCommand(object): def __init__(self): self.style = color_style() + def get_version(self): + """ + Returns the Django version, which should be correct for all built-in + Django commands. User-supplied commands should override this method. + """ + return django.get_version() + + def usage(self): + usage = '%prog [options] ' + self.args + if self.help: + return '%s\n\n%s' % (usage, self.help) + else: + return usage + + def create_parser(self, prog_name): + return OptionParser(prog=prog_name, + usage=self.usage(), + version=self.get_version(), + option_list=self.option_list) + + def print_help(self, args): + parser = self.create_parser(args[0]) + parser.print_help() + + def run(self, args): + parser = self.create_parser(args[0]) + (options, args) = parser.parse_args(args[1:]) + if options.settings: + os.environ['DJANGO_SETTINGS_MODULE'] = options.settings + if options.pythonpath: + sys.path.insert(0, options.pythonpath) + try: + self.execute(*args, **options.__dict__) + except Exception, e: + print_exc() + parser.print_usage() + def execute(self, *args, **options): # Switch to English, because django-admin.py creates database content # like permissions, and those shouldn't contain any translations. @@ -69,7 +116,7 @@ class BaseCommand(object): raise NotImplementedError() class AppCommand(BaseCommand): - args = '[appname ...]' + args = '' def handle(self, *app_labels, **options): from django.db import models @@ -90,7 +137,7 @@ class AppCommand(BaseCommand): raise NotImplementedError() class LabelCommand(BaseCommand): - args = '[label ...]' + args = '