web/lib/django/core/management/__init__.py
changeset 38 77b6da96e6f1
equal deleted inserted replaced
37:8d941af65caf 38:77b6da96e6f1
       
     1 import os
       
     2 import sys
       
     3 from optparse import OptionParser, NO_DEFAULT
       
     4 import imp
       
     5 
       
     6 import django
       
     7 from django.core.management.base import BaseCommand, CommandError, handle_default_options
       
     8 from django.utils.importlib import import_module
       
     9 
       
    10 # For backwards compatibility: get_version() used to be in this module.
       
    11 get_version = django.get_version
       
    12 
       
    13 # A cache of loaded commands, so that call_command
       
    14 # doesn't have to reload every time it's called.
       
    15 _commands = None
       
    16 
       
    17 def find_commands(management_dir):
       
    18     """
       
    19     Given a path to a management directory, returns a list of all the command
       
    20     names that are available.
       
    21 
       
    22     Returns an empty list if no commands are defined.
       
    23     """
       
    24     command_dir = os.path.join(management_dir, 'commands')
       
    25     try:
       
    26         return [f[:-3] for f in os.listdir(command_dir)
       
    27                 if not f.startswith('_') and f.endswith('.py')]
       
    28     except OSError:
       
    29         return []
       
    30 
       
    31 def find_management_module(app_name):
       
    32     """
       
    33     Determines the path to the management module for the given app_name,
       
    34     without actually importing the application or the management module.
       
    35 
       
    36     Raises ImportError if the management module cannot be found for any reason.
       
    37     """
       
    38     parts = app_name.split('.')
       
    39     parts.append('management')
       
    40     parts.reverse()
       
    41     part = parts.pop()
       
    42     path = None
       
    43 
       
    44     # When using manage.py, the project module is added to the path,
       
    45     # loaded, then removed from the path. This means that
       
    46     # testproject.testapp.models can be loaded in future, even if
       
    47     # testproject isn't in the path. When looking for the management
       
    48     # module, we need look for the case where the project name is part
       
    49     # of the app_name but the project directory itself isn't on the path.
       
    50     try:
       
    51         f, path, descr = imp.find_module(part,path)
       
    52     except ImportError,e:
       
    53         if os.path.basename(os.getcwd()) != part:
       
    54             raise e
       
    55 
       
    56     while parts:
       
    57         part = parts.pop()
       
    58         f, path, descr = imp.find_module(part, path and [path] or None)
       
    59     return path
       
    60 
       
    61 def load_command_class(app_name, name):
       
    62     """
       
    63     Given a command name and an application name, returns the Command
       
    64     class instance. All errors raised by the import process
       
    65     (ImportError, AttributeError) are allowed to propagate.
       
    66     """
       
    67     module = import_module('%s.management.commands.%s' % (app_name, name))
       
    68     return module.Command()
       
    69 
       
    70 def get_commands():
       
    71     """
       
    72     Returns a dictionary mapping command names to their callback applications.
       
    73 
       
    74     This works by looking for a management.commands package in django.core, and
       
    75     in each installed application -- if a commands package exists, all commands
       
    76     in that package are registered.
       
    77 
       
    78     Core commands are always included. If a settings module has been
       
    79     specified, user-defined commands will also be included, the
       
    80     startproject command will be disabled, and the startapp command
       
    81     will be modified to use the directory in which the settings module appears.
       
    82 
       
    83     The dictionary is in the format {command_name: app_name}. Key-value
       
    84     pairs from this dictionary can then be used in calls to
       
    85     load_command_class(app_name, command_name)
       
    86 
       
    87     If a specific version of a command must be loaded (e.g., with the
       
    88     startapp command), the instantiated module can be placed in the
       
    89     dictionary in place of the application name.
       
    90 
       
    91     The dictionary is cached on the first call and reused on subsequent
       
    92     calls.
       
    93     """
       
    94     global _commands
       
    95     if _commands is None:
       
    96         _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])])
       
    97 
       
    98         # Find the installed apps
       
    99         try:
       
   100             from django.conf import settings
       
   101             apps = settings.INSTALLED_APPS
       
   102         except (AttributeError, EnvironmentError, ImportError):
       
   103             apps = []
       
   104 
       
   105         # Find the project directory
       
   106         try:
       
   107             from django.conf import settings
       
   108             module = import_module(settings.SETTINGS_MODULE)
       
   109             project_directory = setup_environ(module, settings.SETTINGS_MODULE)
       
   110         except (AttributeError, EnvironmentError, ImportError, KeyError):
       
   111             project_directory = None
       
   112 
       
   113         # Find and load the management module for each installed app.
       
   114         for app_name in apps:
       
   115             try:
       
   116                 path = find_management_module(app_name)
       
   117                 _commands.update(dict([(name, app_name)
       
   118                                        for name in find_commands(path)]))
       
   119             except ImportError:
       
   120                 pass # No management module - ignore this app
       
   121 
       
   122         if project_directory:
       
   123             # Remove the "startproject" command from self.commands, because
       
   124             # that's a django-admin.py command, not a manage.py command.
       
   125             del _commands['startproject']
       
   126 
       
   127             # Override the startapp command so that it always uses the
       
   128             # project_directory, not the current working directory
       
   129             # (which is default).
       
   130             from django.core.management.commands.startapp import ProjectCommand
       
   131             _commands['startapp'] = ProjectCommand(project_directory)
       
   132 
       
   133     return _commands
       
   134 
       
   135 def call_command(name, *args, **options):
       
   136     """
       
   137     Calls the given command, with the given options and args/kwargs.
       
   138 
       
   139     This is the primary API you should use for calling specific commands.
       
   140 
       
   141     Some examples:
       
   142         call_command('syncdb')
       
   143         call_command('shell', plain=True)
       
   144         call_command('sqlall', 'myapp')
       
   145     """
       
   146     # Load the command object.
       
   147     try:
       
   148         app_name = get_commands()[name]
       
   149         if isinstance(app_name, BaseCommand):
       
   150             # If the command is already loaded, use it directly.
       
   151             klass = app_name
       
   152         else:
       
   153             klass = load_command_class(app_name, name)
       
   154     except KeyError:
       
   155         raise CommandError("Unknown command: %r" % name)
       
   156 
       
   157     # Grab out a list of defaults from the options. optparse does this for us
       
   158     # when the script runs from the command line, but since call_command can
       
   159     # be called programatically, we need to simulate the loading and handling
       
   160     # of defaults (see #10080 for details).
       
   161     defaults = dict([(o.dest, o.default)
       
   162                      for o in klass.option_list
       
   163                      if o.default is not NO_DEFAULT])
       
   164     defaults.update(options)
       
   165 
       
   166     return klass.execute(*args, **defaults)
       
   167 
       
   168 class LaxOptionParser(OptionParser):
       
   169     """
       
   170     An option parser that doesn't raise any errors on unknown options.
       
   171 
       
   172     This is needed because the --settings and --pythonpath options affect
       
   173     the commands (and thus the options) that are available to the user.
       
   174     """
       
   175     def error(self, msg):
       
   176         pass
       
   177 
       
   178     def print_help(self):
       
   179         """Output nothing.
       
   180 
       
   181         The lax options are included in the normal option parser, so under
       
   182         normal usage, we don't need to print the lax options.
       
   183         """
       
   184         pass
       
   185 
       
   186     def print_lax_help(self):
       
   187         """Output the basic options available to every command.
       
   188 
       
   189         This just redirects to the default print_help() behaviour.
       
   190         """
       
   191         OptionParser.print_help(self)
       
   192 
       
   193     def _process_args(self, largs, rargs, values):
       
   194         """
       
   195         Overrides OptionParser._process_args to exclusively handle default
       
   196         options and ignore args and other options.
       
   197 
       
   198         This overrides the behavior of the super class, which stop parsing
       
   199         at the first unrecognized option.
       
   200         """
       
   201         while rargs:
       
   202             arg = rargs[0]
       
   203             try:
       
   204                 if arg[0:2] == "--" and len(arg) > 2:
       
   205                     # process a single long option (possibly with value(s))
       
   206                     # the superclass code pops the arg off rargs
       
   207                     self._process_long_opt(rargs, values)
       
   208                 elif arg[:1] == "-" and len(arg) > 1:
       
   209                     # process a cluster of short options (possibly with
       
   210                     # value(s) for the last one only)
       
   211                     # the superclass code pops the arg off rargs
       
   212                     self._process_short_opts(rargs, values)
       
   213                 else:
       
   214                     # it's either a non-default option or an arg
       
   215                     # either way, add it to the args list so we can keep
       
   216                     # dealing with options
       
   217                     del rargs[0]
       
   218                     raise Exception
       
   219             except:
       
   220                 largs.append(arg)
       
   221 
       
   222 class ManagementUtility(object):
       
   223     """
       
   224     Encapsulates the logic of the django-admin.py and manage.py utilities.
       
   225 
       
   226     A ManagementUtility has a number of commands, which can be manipulated
       
   227     by editing the self.commands dictionary.
       
   228     """
       
   229     def __init__(self, argv=None):
       
   230         self.argv = argv or sys.argv[:]
       
   231         self.prog_name = os.path.basename(self.argv[0])
       
   232 
       
   233     def main_help_text(self):
       
   234         """
       
   235         Returns the script's main help text, as a string.
       
   236         """
       
   237         usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,'']
       
   238         usage.append('Available subcommands:')
       
   239         commands = get_commands().keys()
       
   240         commands.sort()
       
   241         for cmd in commands:
       
   242             usage.append('  %s' % cmd)
       
   243         return '\n'.join(usage)
       
   244 
       
   245     def fetch_command(self, subcommand):
       
   246         """
       
   247         Tries to fetch the given subcommand, printing a message with the
       
   248         appropriate command called from the command line (usually
       
   249         "django-admin.py" or "manage.py") if it can't be found.
       
   250         """
       
   251         try:
       
   252             app_name = get_commands()[subcommand]
       
   253             if isinstance(app_name, BaseCommand):
       
   254                 # If the command is already loaded, use it directly.
       
   255                 klass = app_name
       
   256             else:
       
   257                 klass = load_command_class(app_name, subcommand)
       
   258         except KeyError:
       
   259             sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \
       
   260                 (subcommand, self.prog_name))
       
   261             sys.exit(1)
       
   262         return klass
       
   263 
       
   264     def autocomplete(self):
       
   265         """
       
   266         Output completion suggestions for BASH.
       
   267 
       
   268         The output of this function is passed to BASH's `COMREPLY` variable and
       
   269         treated as completion suggestions. `COMREPLY` expects a space
       
   270         separated string as the result.
       
   271 
       
   272         The `COMP_WORDS` and `COMP_CWORD` BASH environment variables are used
       
   273         to get information about the cli input. Please refer to the BASH
       
   274         man-page for more information about this variables.
       
   275 
       
   276         Subcommand options are saved as pairs. A pair consists of
       
   277         the long option string (e.g. '--exclude') and a boolean
       
   278         value indicating if the option requires arguments. When printing to
       
   279         stdout, a equal sign is appended to options which require arguments.
       
   280 
       
   281         Note: If debugging this function, it is recommended to write the debug
       
   282         output in a separate file. Otherwise the debug output will be treated
       
   283         and formatted as potential completion suggestions.
       
   284         """
       
   285         # Don't complete if user hasn't sourced bash_completion file.
       
   286         if not os.environ.has_key('DJANGO_AUTO_COMPLETE'):
       
   287             return
       
   288 
       
   289         cwords = os.environ['COMP_WORDS'].split()[1:]
       
   290         cword = int(os.environ['COMP_CWORD'])
       
   291 
       
   292         try:
       
   293             curr = cwords[cword-1]
       
   294         except IndexError:
       
   295             curr = ''
       
   296 
       
   297         subcommands = get_commands().keys() + ['help']
       
   298         options = [('--help', None)]
       
   299 
       
   300         # subcommand
       
   301         if cword == 1:
       
   302             print ' '.join(sorted(filter(lambda x: x.startswith(curr), subcommands)))
       
   303         # subcommand options
       
   304         # special case: the 'help' subcommand has no options
       
   305         elif cwords[0] in subcommands and cwords[0] != 'help':
       
   306             subcommand_cls = self.fetch_command(cwords[0])
       
   307             # special case: 'runfcgi' stores additional options as
       
   308             # 'key=value' pairs
       
   309             if cwords[0] == 'runfcgi':
       
   310                 from django.core.servers.fastcgi import FASTCGI_OPTIONS
       
   311                 options += [(k, 1) for k in FASTCGI_OPTIONS]
       
   312             # special case: add the names of installed apps to options
       
   313             elif cwords[0] in ('dumpdata', 'reset', 'sql', 'sqlall',
       
   314                                'sqlclear', 'sqlcustom', 'sqlindexes',
       
   315                                'sqlreset', 'sqlsequencereset', 'test'):
       
   316                 try:
       
   317                     from django.conf import settings
       
   318                     # Get the last part of the dotted path as the app name.
       
   319                     options += [(a.split('.')[-1], 0) for a in settings.INSTALLED_APPS]
       
   320                 except ImportError:
       
   321                     # Fail silently if DJANGO_SETTINGS_MODULE isn't set. The
       
   322                     # user will find out once they execute the command.
       
   323                     pass
       
   324             options += [(s_opt.get_opt_string(), s_opt.nargs) for s_opt in
       
   325                         subcommand_cls.option_list]
       
   326             # filter out previously specified options from available options
       
   327             prev_opts = [x.split('=')[0] for x in cwords[1:cword-1]]
       
   328             options = filter(lambda (x, v): x not in prev_opts, options)
       
   329 
       
   330             # filter options by current input
       
   331             options = sorted([(k, v) for k, v in options if k.startswith(curr)])
       
   332             for option in options:
       
   333                 opt_label = option[0]
       
   334                 # append '=' to options which require args
       
   335                 if option[1]:
       
   336                     opt_label += '='
       
   337                 print opt_label
       
   338         sys.exit(1)
       
   339 
       
   340     def execute(self):
       
   341         """
       
   342         Given the command-line arguments, this figures out which subcommand is
       
   343         being run, creates a parser appropriate to that command, and runs it.
       
   344         """
       
   345         # Preprocess options to extract --settings and --pythonpath.
       
   346         # These options could affect the commands that are available, so they
       
   347         # must be processed early.
       
   348         parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
       
   349                                  version=get_version(),
       
   350                                  option_list=BaseCommand.option_list)
       
   351         self.autocomplete()
       
   352         try:
       
   353             options, args = parser.parse_args(self.argv)
       
   354             handle_default_options(options)
       
   355         except:
       
   356             pass # Ignore any option errors at this point.
       
   357 
       
   358         try:
       
   359             subcommand = self.argv[1]
       
   360         except IndexError:
       
   361             subcommand = 'help' # Display help if no arguments were given.
       
   362 
       
   363         if subcommand == 'help':
       
   364             if len(args) > 2:
       
   365                 self.fetch_command(args[2]).print_help(self.prog_name, args[2])
       
   366             else:
       
   367                 parser.print_lax_help()
       
   368                 sys.stderr.write(self.main_help_text() + '\n')
       
   369                 sys.exit(1)
       
   370         # Special-cases: We want 'django-admin.py --version' and
       
   371         # 'django-admin.py --help' to work, for backwards compatibility.
       
   372         elif self.argv[1:] == ['--version']:
       
   373             # LaxOptionParser already takes care of printing the version.
       
   374             pass
       
   375         elif self.argv[1:] == ['--help']:
       
   376             parser.print_lax_help()
       
   377             sys.stderr.write(self.main_help_text() + '\n')
       
   378         else:
       
   379             self.fetch_command(subcommand).run_from_argv(self.argv)
       
   380 
       
   381 def setup_environ(settings_mod, original_settings_path=None):
       
   382     """
       
   383     Configures the runtime environment. This can also be used by external
       
   384     scripts wanting to set up a similar environment to manage.py.
       
   385     Returns the project directory (assuming the passed settings module is
       
   386     directly in the project directory).
       
   387 
       
   388     The "original_settings_path" parameter is optional, but recommended, since
       
   389     trying to work out the original path from the module can be problematic.
       
   390     """
       
   391     # Add this project to sys.path so that it's importable in the conventional
       
   392     # way. For example, if this file (manage.py) lives in a directory
       
   393     # "myproject", this code would add "/path/to/myproject" to sys.path.
       
   394     if '__init__.py' in settings_mod.__file__:
       
   395         p = os.path.dirname(settings_mod.__file__)
       
   396     else:
       
   397         p = settings_mod.__file__
       
   398     project_directory, settings_filename = os.path.split(p)
       
   399     if project_directory == os.curdir or not project_directory:
       
   400         project_directory = os.getcwd()
       
   401     project_name = os.path.basename(project_directory)
       
   402 
       
   403     # Strip filename suffix to get the module name.
       
   404     settings_name = os.path.splitext(settings_filename)[0]
       
   405 
       
   406     # Strip $py for Jython compiled files (like settings$py.class)
       
   407     if settings_name.endswith("$py"):
       
   408         settings_name = settings_name[:-3]
       
   409 
       
   410     # Set DJANGO_SETTINGS_MODULE appropriately.
       
   411     if original_settings_path:
       
   412         os.environ['DJANGO_SETTINGS_MODULE'] = original_settings_path
       
   413     else:
       
   414         os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
       
   415 
       
   416     # Import the project module. We add the parent directory to PYTHONPATH to
       
   417     # avoid some of the path errors new users can have.
       
   418     sys.path.append(os.path.join(project_directory, os.pardir))
       
   419     project_module = import_module(project_name)
       
   420     sys.path.pop()
       
   421 
       
   422     return project_directory
       
   423 
       
   424 def execute_from_command_line(argv=None):
       
   425     """
       
   426     A simple method that runs a ManagementUtility.
       
   427     """
       
   428     utility = ManagementUtility(argv)
       
   429     utility.execute()
       
   430 
       
   431 def execute_manager(settings_mod, argv=None):
       
   432     """
       
   433     Like execute_from_command_line(), but for use by manage.py, a
       
   434     project-specific django-admin.py utility.
       
   435     """
       
   436     setup_environ(settings_mod)
       
   437     utility = ManagementUtility(argv)
       
   438     utility.execute()