app/django/core/management/__init__.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 import os
       
     2 import sys
       
     3 from optparse import OptionParser
       
     4 from imp import find_module
       
     5 
       
     6 import django
       
     7 from django.core.management.base import BaseCommand, CommandError, handle_default_options
       
     8 
       
     9 # For backwards compatibility: get_version() used to be in this module.
       
    10 get_version = django.get_version
       
    11 
       
    12 # A cache of loaded commands, so that call_command
       
    13 # doesn't have to reload every time it's called.
       
    14 _commands = None
       
    15 
       
    16 def find_commands(management_dir):
       
    17     """
       
    18     Given a path to a management directory, returns a list of all the command
       
    19     names that are available.
       
    20 
       
    21     Returns an empty list if no commands are defined.
       
    22     """
       
    23     command_dir = os.path.join(management_dir, 'commands')
       
    24     try:
       
    25         return [f[:-3] for f in os.listdir(command_dir)
       
    26                 if not f.startswith('_') and f.endswith('.py')]
       
    27     except OSError:
       
    28         return []
       
    29 
       
    30 def find_management_module(app_name):
       
    31     """
       
    32     Determines the path to the management module for the given app_name,
       
    33     without actually importing the application or the management module.
       
    34 
       
    35     Raises ImportError if the management module cannot be found for any reason.
       
    36     """
       
    37     parts = app_name.split('.')
       
    38     parts.append('management')
       
    39     parts.reverse()
       
    40     path = None
       
    41     while parts:
       
    42         part = parts.pop()
       
    43         f, path, descr = find_module(part, path and [path] or None)
       
    44     return path
       
    45 
       
    46 def load_command_class(app_name, name):
       
    47     """
       
    48     Given a command name and an application name, returns the Command
       
    49     class instance. All errors raised by the import process
       
    50     (ImportError, AttributeError) are allowed to propagate.
       
    51     """
       
    52     return getattr(__import__('%s.management.commands.%s' % (app_name, name),
       
    53                    {}, {}, ['Command']), 'Command')()
       
    54 
       
    55 def get_commands(load_user_commands=True, project_directory=None):
       
    56     """
       
    57     Returns a dictionary mapping command names to their callback applications.
       
    58 
       
    59     This works by looking for a management.commands package in django.core, and
       
    60     in each installed application -- if a commands package exists, all commands
       
    61     in that package are registered.
       
    62 
       
    63     Core commands are always included. If a settings module has been
       
    64     specified, user-defined commands will also be included, the
       
    65     startproject command will be disabled, and the startapp command
       
    66     will be modified to use the directory in which that module appears.
       
    67 
       
    68     The dictionary is in the format {command_name: app_name}. Key-value
       
    69     pairs from this dictionary can then be used in calls to
       
    70     load_command_class(app_name, command_name)
       
    71 
       
    72     If a specific version of a command must be loaded (e.g., with the
       
    73     startapp command), the instantiated module can be placed in the
       
    74     dictionary in place of the application name.
       
    75 
       
    76     The dictionary is cached on the first call and reused on subsequent
       
    77     calls.
       
    78     """
       
    79     global _commands
       
    80     if _commands is None:
       
    81         _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])])
       
    82 
       
    83         if load_user_commands:
       
    84             # Get commands from all installed apps.
       
    85             from django.conf import settings
       
    86             for app_name in settings.INSTALLED_APPS:
       
    87                 try:
       
    88                     path = find_management_module(app_name)
       
    89                     _commands.update(dict([(name, app_name) for name in find_commands(path)]))
       
    90                 except ImportError:
       
    91                     pass # No management module -- ignore this app.
       
    92 
       
    93         if project_directory:
       
    94             # Remove the "startproject" command from self.commands, because
       
    95             # that's a django-admin.py command, not a manage.py command.
       
    96             del _commands['startproject']
       
    97 
       
    98             # Override the startapp command so that it always uses the
       
    99             # project_directory, not the current working directory
       
   100             # (which is default).
       
   101             from django.core.management.commands.startapp import ProjectCommand
       
   102             _commands['startapp'] = ProjectCommand(project_directory)
       
   103 
       
   104     return _commands
       
   105 
       
   106 def call_command(name, *args, **options):
       
   107     """
       
   108     Calls the given command, with the given options and args/kwargs.
       
   109 
       
   110     This is the primary API you should use for calling specific commands.
       
   111 
       
   112     Some examples:
       
   113         call_command('syncdb')
       
   114         call_command('shell', plain=True)
       
   115         call_command('sqlall', 'myapp')
       
   116     """
       
   117     try:
       
   118         app_name = get_commands()[name]
       
   119         if isinstance(app_name, BaseCommand):
       
   120             # If the command is already loaded, use it directly.
       
   121             klass = app_name
       
   122         else:
       
   123             klass = load_command_class(app_name, name)
       
   124     except KeyError:
       
   125         raise CommandError, "Unknown command: %r" % name
       
   126     return klass.execute(*args, **options)
       
   127 
       
   128 class LaxOptionParser(OptionParser):
       
   129     """
       
   130     An option parser that doesn't raise any errors on unknown options.
       
   131 
       
   132     This is needed because the --settings and --pythonpath options affect
       
   133     the commands (and thus the options) that are available to the user.
       
   134     """
       
   135     def error(self, msg):
       
   136         pass
       
   137 
       
   138 class ManagementUtility(object):
       
   139     """
       
   140     Encapsulates the logic of the django-admin.py and manage.py utilities.
       
   141 
       
   142     A ManagementUtility has a number of commands, which can be manipulated
       
   143     by editing the self.commands dictionary.
       
   144     """
       
   145     def __init__(self, argv=None):
       
   146         self.argv = argv or sys.argv[:]
       
   147         self.prog_name = os.path.basename(self.argv[0])
       
   148         self.project_directory = None
       
   149         self.user_commands = False
       
   150 
       
   151     def main_help_text(self):
       
   152         """
       
   153         Returns the script's main help text, as a string.
       
   154         """
       
   155         usage = ['%s <subcommand> [options] [args]' % self.prog_name]
       
   156         usage.append('Django command line tool, version %s' % django.get_version())
       
   157         usage.append("Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name)
       
   158         usage.append('Available subcommands:')
       
   159         commands = get_commands(self.user_commands, self.project_directory).keys()
       
   160         commands.sort()
       
   161         for cmd in commands:
       
   162             usage.append('  %s' % cmd)
       
   163         return '\n'.join(usage)
       
   164 
       
   165     def fetch_command(self, subcommand):
       
   166         """
       
   167         Tries to fetch the given subcommand, printing a message with the
       
   168         appropriate command called from the command line (usually
       
   169         "django-admin.py" or "manage.py") if it can't be found.
       
   170         """
       
   171         try:
       
   172             app_name = get_commands(self.user_commands, self.project_directory)[subcommand]
       
   173             if isinstance(app_name, BaseCommand):
       
   174                 # If the command is already loaded, use it directly.
       
   175                 klass = app_name
       
   176             else:
       
   177                 klass = load_command_class(app_name, subcommand)
       
   178         except KeyError:
       
   179             sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \
       
   180                 (subcommand, self.prog_name))
       
   181             sys.exit(1)
       
   182         return klass
       
   183 
       
   184     def execute(self):
       
   185         """
       
   186         Given the command-line arguments, this figures out which subcommand is
       
   187         being run, creates a parser appropriate to that command, and runs it.
       
   188         """
       
   189         # Preprocess options to extract --settings and --pythonpath.
       
   190         # These options could affect the commands that are available, so they
       
   191         # must be processed early.
       
   192         parser = LaxOptionParser(version=get_version(), option_list=BaseCommand.option_list)
       
   193         try:
       
   194             options, args = parser.parse_args(self.argv)
       
   195             handle_default_options(options)
       
   196         except:
       
   197             pass # Ignore any option errors at this point.
       
   198 
       
   199         try:
       
   200             subcommand = self.argv[1]
       
   201         except IndexError:
       
   202             sys.stderr.write("Type '%s help' for usage.\n" % self.prog_name)
       
   203             sys.exit(1)
       
   204 
       
   205         if subcommand == 'help':
       
   206             if len(args) > 2:
       
   207                 self.fetch_command(args[2]).print_help(self.prog_name, args[2])
       
   208             else:
       
   209                 sys.stderr.write(self.main_help_text() + '\n')
       
   210                 sys.exit(1)
       
   211         # Special-cases: We want 'django-admin.py --version' and
       
   212         # 'django-admin.py --help' to work, for backwards compatibility.
       
   213         elif self.argv[1:] == ['--version']:
       
   214             # LaxOptionParser already takes care of printing the version.
       
   215             pass
       
   216         elif self.argv[1:] == ['--help']:
       
   217             sys.stderr.write(self.main_help_text() + '\n')
       
   218         else:
       
   219             self.fetch_command(subcommand).run_from_argv(self.argv)
       
   220 
       
   221 class ProjectManagementUtility(ManagementUtility):
       
   222     """
       
   223     A ManagementUtility that is specific to a particular Django project.
       
   224     As such, its commands are slightly different than those of its parent
       
   225     class.
       
   226 
       
   227     In practice, this class represents manage.py, whereas ManagementUtility
       
   228     represents django-admin.py.
       
   229     """
       
   230     def __init__(self, argv, project_directory):
       
   231         super(ProjectManagementUtility, self).__init__(argv)
       
   232         self.project_directory = project_directory
       
   233         self.user_commands = True
       
   234 
       
   235 def setup_environ(settings_mod):
       
   236     """
       
   237     Configures the runtime environment. This can also be used by external
       
   238     scripts wanting to set up a similar environment to manage.py.
       
   239     Returns the project directory (assuming the passed settings module is
       
   240     directly in the project directory).
       
   241     """
       
   242     # Add this project to sys.path so that it's importable in the conventional
       
   243     # way. For example, if this file (manage.py) lives in a directory
       
   244     # "myproject", this code would add "/path/to/myproject" to sys.path.
       
   245     project_directory, settings_filename = os.path.split(settings_mod.__file__)
       
   246     if project_directory == os.curdir or not project_directory:
       
   247         project_directory = os.getcwd()
       
   248     project_name = os.path.basename(project_directory)
       
   249     settings_name = os.path.splitext(settings_filename)[0]
       
   250     sys.path.append(os.path.join(project_directory, os.pardir))
       
   251     project_module = __import__(project_name, {}, {}, [''])
       
   252     sys.path.pop()
       
   253 
       
   254     # Set DJANGO_SETTINGS_MODULE appropriately.
       
   255     os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
       
   256     return project_directory
       
   257 
       
   258 def execute_from_command_line(argv=None):
       
   259     """
       
   260     A simple method that runs a ManagementUtility.
       
   261     """
       
   262     utility = ManagementUtility(argv)
       
   263     utility.execute()
       
   264 
       
   265 def execute_manager(settings_mod, argv=None):
       
   266     """
       
   267     Like execute_from_command_line(), but for use by manage.py, a
       
   268     project-specific django-admin.py utility.
       
   269     """
       
   270     project_directory = setup_environ(settings_mod)
       
   271     utility = ProjectManagementUtility(argv, project_directory)
       
   272     utility.execute()