123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416 |
- import collections
- import imp
- from importlib import import_module
- from optparse import OptionParser, NO_DEFAULT
- import os
- import sys
- from django.core.exceptions import ImproperlyConfigured
- from django.core.management.base import BaseCommand, CommandError, handle_default_options
- from django.core.management.color import color_style
- from django.utils import six
- # For backwards compatibility: get_version() used to be in this module.
- from django import get_version
- # A cache of loaded commands, so that call_command
- # doesn't have to reload every time it's called.
- _commands = None
- def find_commands(management_dir):
- """
- Given a path to a management directory, returns a list of all the command
- names that are available.
- Returns an empty list if no commands are defined.
- """
- command_dir = os.path.join(management_dir, 'commands')
- try:
- return [f[:-3] for f in os.listdir(command_dir)
- if not f.startswith('_') and f.endswith('.py')]
- except OSError:
- return []
- def find_management_module(app_name):
- """
- Determines the path to the management module for the given app_name,
- without actually importing the application or the management module.
- Raises ImportError if the management module cannot be found for any reason.
- """
- parts = app_name.split('.')
- parts.append('management')
- parts.reverse()
- part = parts.pop()
- path = None
- # When using manage.py, the project module is added to the path,
- # loaded, then removed from the path. This means that
- # testproject.testapp.models can be loaded in future, even if
- # testproject isn't in the path. When looking for the management
- # module, we need look for the case where the project name is part
- # of the app_name but the project directory itself isn't on the path.
- try:
- f, path, descr = imp.find_module(part, path)
- except ImportError as e:
- if os.path.basename(os.getcwd()) != part:
- raise e
- else:
- if f:
- f.close()
- while parts:
- part = parts.pop()
- f, path, descr = imp.find_module(part, [path] if path else None)
- if f:
- f.close()
- return path
- def load_command_class(app_name, name):
- """
- Given a command name and an application name, returns the Command
- class instance. All errors raised by the import process
- (ImportError, AttributeError) are allowed to propagate.
- """
- module = import_module('%s.management.commands.%s' % (app_name, name))
- return module.Command()
- def get_commands():
- """
- Returns a dictionary mapping command names to their callback applications.
- This works by looking for a management.commands package in django.core, and
- in each installed application -- if a commands package exists, all commands
- in that package are registered.
- Core commands are always included. If a settings module has been
- specified, user-defined commands will also be included.
- The dictionary is in the format {command_name: app_name}. Key-value
- pairs from this dictionary can then be used in calls to
- load_command_class(app_name, command_name)
- If a specific version of a command must be loaded (e.g., with the
- startapp command), the instantiated module can be placed in the
- dictionary in place of the application name.
- The dictionary is cached on the first call and reused on subsequent
- calls.
- """
- global _commands
- if _commands is None:
- _commands = dict((name, 'django.core') for name in find_commands(__path__[0]))
- # Find the installed apps
- from django.conf import settings
- try:
- apps = settings.INSTALLED_APPS
- except ImproperlyConfigured:
- # Still useful for commands that do not require functional settings,
- # like startproject or help
- apps = []
- # Find and load the management module for each installed app.
- for app_name in apps:
- try:
- path = find_management_module(app_name)
- _commands.update(dict((name, app_name)
- for name in find_commands(path)))
- except ImportError:
- pass # No management module - ignore this app
- return _commands
- 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)
- call_command('sqlall', 'myapp')
- """
- # Load the command object.
- try:
- app_name = get_commands()[name]
- except KeyError:
- raise CommandError("Unknown command: %r" % name)
- if isinstance(app_name, BaseCommand):
- # If the command is already loaded, use it directly.
- klass = app_name
- else:
- klass = load_command_class(app_name, name)
- # Grab out a list of defaults from the options. optparse does this for us
- # when the script runs from the command line, but since call_command can
- # be called programmatically, we need to simulate the loading and handling
- # of defaults (see #10080 for details).
- defaults = {}
- for opt in klass.option_list:
- if opt.default is NO_DEFAULT:
- defaults[opt.dest] = None
- else:
- defaults[opt.dest] = opt.default
- defaults.update(options)
- return klass.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):
- """
- Encapsulates the logic of the django-admin.py and manage.py utilities.
- A ManagementUtility has a number of commands, which can be manipulated
- by editing the self.commands dictionary.
- """
- def __init__(self, argv=None):
- self.argv = argv or sys.argv[:]
- self.prog_name = os.path.basename(self.argv[0])
- def main_help_text(self, commands_only=False):
- """
- Returns the script's main help text, as a string.
- """
- if commands_only:
- usage = sorted(get_commands().keys())
- else:
- usage = [
- "",
- "Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,
- "",
- "Available subcommands:",
- ]
- commands_dict = collections.defaultdict(lambda: [])
- for name, app in six.iteritems(get_commands()):
- if app == 'django.core':
- app = 'django'
- else:
- app = app.rpartition('.')[-1]
- commands_dict[app].append(name)
- style = color_style()
- for app in sorted(commands_dict.keys()):
- usage.append("")
- usage.append(style.NOTICE("[%s]" % app))
- for name in sorted(commands_dict[app]):
- usage.append(" %s" % name)
- # Output an extra note if settings are not properly configured
- try:
- from django.conf import settings
- settings.INSTALLED_APPS
- except ImproperlyConfigured as e:
- usage.append(style.NOTICE(
- "Note that only Django core commands are listed as settings "
- "are not properly configured (error: %s)." % e))
- return '\n'.join(usage)
- def fetch_command(self, subcommand):
- """
- 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.
- """
- # Get commands outside of try block to prevent swallowing exceptions
- commands = get_commands()
- try:
- app_name = commands[subcommand]
- except KeyError:
- sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" %
- (subcommand, self.prog_name))
- sys.exit(1)
- if isinstance(app_name, BaseCommand):
- # If the command is already loaded, use it directly.
- klass = app_name
- else:
- klass = load_command_class(app_name, subcommand)
- return klass
- def autocomplete(self):
- """
- Output completion suggestions for BASH.
- The output of this function is passed to BASH's `COMREPLY` variable and
- treated as completion suggestions. `COMREPLY` expects a space
- separated string as the result.
- The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used
- to get information about the cli input. Please refer to the BASH
- man-page for more information about this variables.
- Subcommand options are saved as pairs. A pair consists of
- the long option string (e.g. '--exclude') and a boolean
- value indicating if the option requires arguments. When printing to
- stdout, a equal sign is appended to options which require arguments.
- Note: If debugging this function, it is recommended to write the debug
- output in a separate file. Otherwise the debug output will be treated
- and formatted as potential completion suggestions.
- """
- # Don't complete if user hasn't sourced bash_completion file.
- if 'DJANGO_AUTO_COMPLETE' not in os.environ:
- return
- cwords = os.environ['COMP_WORDS'].split()[1:]
- cword = int(os.environ['COMP_CWORD'])
- try:
- curr = cwords[cword-1]
- except IndexError:
- curr = ''
- subcommands = list(get_commands()) + ['help']
- options = [('--help', None)]
- # subcommand
- if cword == 1:
- print(' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands))))
- # subcommand options
- # special case: the 'help' subcommand has no options
- elif cwords[0] in subcommands and cwords[0] != 'help':
- subcommand_cls = self.fetch_command(cwords[0])
- # special case: 'runfcgi' stores additional options as
- # 'key=value' pairs
- if cwords[0] == 'runfcgi':
- from django.core.servers.fastcgi import FASTCGI_OPTIONS
- options += [(k, 1) for k in FASTCGI_OPTIONS]
- # special case: add the names of installed apps to options
- elif cwords[0] in ('dumpdata', 'sql', 'sqlall', 'sqlclear',
- 'sqlcustom', 'sqlindexes', 'sqlsequencereset', 'test'):
- try:
- from django.conf import settings
- # Get the last part of the dotted path as the app name.
- options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS]
- except ImportError:
- # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
- # user will find out once they execute the command.
- pass
- options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in
- subcommand_cls.option_list]
- # filter out previously specified options from available options
- 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]
- # filter options by current input
- options = sorted((k, v) for k, v in options if k.startswith(curr))
- for option in options:
- opt_label = option[0]
- # append '=' to options which require args
- if option[1]:
- opt_label += '='
- print(opt_label)
- sys.exit(1)
- def execute(self):
- """
- Given the command-line arguments, this figures out which subcommand is
- 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=get_version(),
- option_list=BaseCommand.option_list)
- self.autocomplete()
- 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:
- subcommand = self.argv[1]
- except IndexError:
- subcommand = 'help' # Display help if no arguments were given.
- if subcommand == 'help':
- if len(args) <= 2:
- 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')
- else:
- self.fetch_command(args[2]).print_help(self.prog_name, args[2])
- elif subcommand == 'version':
- sys.stdout.write(parser.get_version() + '\n')
- # Special-cases: We want 'django-admin.py --version' and
- # 'django-admin.py --help' to work, for backwards compatibility.
- elif self.argv[1:] == ['--version']:
- # LaxOptionParser already takes care of printing the version.
- pass
- elif self.argv[1:] in (['--help'], ['-h']):
- parser.print_lax_help()
- sys.stdout.write(self.main_help_text() + '\n')
- else:
- self.fetch_command(subcommand).run_from_argv(self.argv)
- def execute_from_command_line(argv=None):
- """
- A simple method that runs a ManagementUtility.
- """
- utility = ManagementUtility(argv)
- utility.execute()
|