app/django/core/management/__init__.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
     1 import os
     1 import os
     2 import sys
     2 import sys
     3 from optparse import OptionParser
     3 from optparse import OptionParser
     4 from imp import find_module
     4 import imp
     5 
     5 
     6 import django
     6 import django
     7 from django.core.management.base import BaseCommand, CommandError, handle_default_options
     7 from django.core.management.base import BaseCommand, CommandError, handle_default_options
     8 
     8 
     9 # For backwards compatibility: get_version() used to be in this module.
     9 # For backwards compatibility: get_version() used to be in this module.
    35     Raises ImportError if the management module cannot be found for any reason.
    35     Raises ImportError if the management module cannot be found for any reason.
    36     """
    36     """
    37     parts = app_name.split('.')
    37     parts = app_name.split('.')
    38     parts.append('management')
    38     parts.append('management')
    39     parts.reverse()
    39     parts.reverse()
       
    40     part = parts.pop()
    40     path = None
    41     path = None
       
    42 
       
    43     # When using manage.py, the project module is added to the path,
       
    44     # loaded, then removed from the path. This means that
       
    45     # testproject.testapp.models can be loaded in future, even if
       
    46     # testproject isn't in the path. When looking for the management
       
    47     # module, we need look for the case where the project name is part
       
    48     # of the app_name but the project directory itself isn't on the path.
       
    49     try:
       
    50         f, path, descr = imp.find_module(part,path)
       
    51     except ImportError,e:
       
    52         if os.path.basename(os.getcwd()) != part:
       
    53             raise e
       
    54 
    41     while parts:
    55     while parts:
    42         part = parts.pop()
    56         part = parts.pop()
    43         f, path, descr = find_module(part, path and [path] or None)
    57         f, path, descr = imp.find_module(part, path and [path] or None)
    44     return path
    58     return path
    45 
    59 
    46 def load_command_class(app_name, name):
    60 def load_command_class(app_name, name):
    47     """
    61     """
    48     Given a command name and an application name, returns the Command
    62     Given a command name and an application name, returns the Command
    50     (ImportError, AttributeError) are allowed to propagate.
    64     (ImportError, AttributeError) are allowed to propagate.
    51     """
    65     """
    52     return getattr(__import__('%s.management.commands.%s' % (app_name, name),
    66     return getattr(__import__('%s.management.commands.%s' % (app_name, name),
    53                    {}, {}, ['Command']), 'Command')()
    67                    {}, {}, ['Command']), 'Command')()
    54 
    68 
    55 def get_commands(load_user_commands=True, project_directory=None):
    69 def get_commands():
    56     """
    70     """
    57     Returns a dictionary mapping command names to their callback applications.
    71     Returns a dictionary mapping command names to their callback applications.
    58 
    72 
    59     This works by looking for a management.commands package in django.core, and
    73     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
    74     in each installed application -- if a commands package exists, all commands
    61     in that package are registered.
    75     in that package are registered.
    62 
    76 
    63     Core commands are always included. If a settings module has been
    77     Core commands are always included. If a settings module has been
    64     specified, user-defined commands will also be included, the
    78     specified, user-defined commands will also be included, the
    65     startproject command will be disabled, and the startapp command
    79     startproject command will be disabled, and the startapp command
    66     will be modified to use the directory in which that module appears.
    80     will be modified to use the directory in which the settings module appears.
    67 
    81 
    68     The dictionary is in the format {command_name: app_name}. Key-value
    82     The dictionary is in the format {command_name: app_name}. Key-value
    69     pairs from this dictionary can then be used in calls to
    83     pairs from this dictionary can then be used in calls to
    70     load_command_class(app_name, command_name)
    84     load_command_class(app_name, command_name)
    71 
    85 
    78     """
    92     """
    79     global _commands
    93     global _commands
    80     if _commands is None:
    94     if _commands is None:
    81         _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])])
    95         _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])])
    82 
    96 
    83         if load_user_commands:
    97         # Find the installed apps
    84             # Get commands from all installed apps.
    98         try:
    85             from django.conf import settings
    99             from django.conf import settings
    86             for app_name in settings.INSTALLED_APPS:
   100             apps = settings.INSTALLED_APPS
    87                 try:
   101         except (AttributeError, EnvironmentError, ImportError):
    88                     path = find_management_module(app_name)
   102             apps = []
    89                     _commands.update(dict([(name, app_name) for name in find_commands(path)]))
   103 
    90                 except ImportError:
   104         # Find the project directory
    91                     pass # No management module -- ignore this app.
   105         try:
       
   106             from django.conf import settings
       
   107             project_directory = setup_environ(
       
   108                 __import__(
       
   109                     settings.SETTINGS_MODULE, {}, {},
       
   110                     (settings.SETTINGS_MODULE.split(".")[-1],)
       
   111                 ), settings.SETTINGS_MODULE
       
   112             )
       
   113         except (AttributeError, EnvironmentError, ImportError):
       
   114             project_directory = None
       
   115 
       
   116         # Find and load the management module for each installed app.
       
   117         for app_name in apps:
       
   118             try:
       
   119                 path = find_management_module(app_name)
       
   120                 _commands.update(dict([(name, app_name)
       
   121                                        for name in find_commands(path)]))
       
   122             except ImportError:
       
   123                 pass # No management module - ignore this app
    92 
   124 
    93         if project_directory:
   125         if project_directory:
    94             # Remove the "startproject" command from self.commands, because
   126             # Remove the "startproject" command from self.commands, because
    95             # that's a django-admin.py command, not a manage.py command.
   127             # that's a django-admin.py command, not a manage.py command.
    96             del _commands['startproject']
   128             del _commands['startproject']
   133     the commands (and thus the options) that are available to the user.
   165     the commands (and thus the options) that are available to the user.
   134     """
   166     """
   135     def error(self, msg):
   167     def error(self, msg):
   136         pass
   168         pass
   137 
   169 
       
   170     def print_help(self):
       
   171         """Output nothing.
       
   172 
       
   173         The lax options are included in the normal option parser, so under
       
   174         normal usage, we don't need to print the lax options.
       
   175         """
       
   176         pass
       
   177 
       
   178     def print_lax_help(self):
       
   179         """Output the basic options available to every command.
       
   180 
       
   181         This just redirects to the default print_help() behaviour.
       
   182         """
       
   183         OptionParser.print_help(self)
       
   184 
       
   185     def _process_args(self, largs, rargs, values):
       
   186         """
       
   187         Overrides OptionParser._process_args to exclusively handle default
       
   188         options and ignore args and other options.
       
   189 
       
   190         This overrides the behavior of the super class, which stop parsing
       
   191         at the first unrecognized option.
       
   192         """
       
   193         while rargs:
       
   194             arg = rargs[0]
       
   195             try:
       
   196                 if arg[0:2] == "--" and len(arg) > 2:
       
   197                     # process a single long option (possibly with value(s))
       
   198                     # the superclass code pops the arg off rargs
       
   199                     self._process_long_opt(rargs, values)
       
   200                 elif arg[:1] == "-" and len(arg) > 1:
       
   201                     # process a cluster of short options (possibly with
       
   202                     # value(s) for the last one only)
       
   203                     # the superclass code pops the arg off rargs
       
   204                     self._process_short_opts(rargs, values)
       
   205                 else:
       
   206                     # it's either a non-default option or an arg
       
   207                     # either way, add it to the args list so we can keep
       
   208                     # dealing with options
       
   209                     del rargs[0]
       
   210                     raise error
       
   211             except:
       
   212                 largs.append(arg)
       
   213 
   138 class ManagementUtility(object):
   214 class ManagementUtility(object):
   139     """
   215     """
   140     Encapsulates the logic of the django-admin.py and manage.py utilities.
   216     Encapsulates the logic of the django-admin.py and manage.py utilities.
   141 
   217 
   142     A ManagementUtility has a number of commands, which can be manipulated
   218     A ManagementUtility has a number of commands, which can be manipulated
   143     by editing the self.commands dictionary.
   219     by editing the self.commands dictionary.
   144     """
   220     """
   145     def __init__(self, argv=None):
   221     def __init__(self, argv=None):
   146         self.argv = argv or sys.argv[:]
   222         self.argv = argv or sys.argv[:]
   147         self.prog_name = os.path.basename(self.argv[0])
   223         self.prog_name = os.path.basename(self.argv[0])
   148         self.project_directory = None
       
   149         self.user_commands = False
       
   150 
   224 
   151     def main_help_text(self):
   225     def main_help_text(self):
   152         """
   226         """
   153         Returns the script's main help text, as a string.
   227         Returns the script's main help text, as a string.
   154         """
   228         """
   155         usage = ['%s <subcommand> [options] [args]' % self.prog_name]
   229         usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % 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:')
   230         usage.append('Available subcommands:')
   159         commands = get_commands(self.user_commands, self.project_directory).keys()
   231         commands = get_commands().keys()
   160         commands.sort()
   232         commands.sort()
   161         for cmd in commands:
   233         for cmd in commands:
   162             usage.append('  %s' % cmd)
   234             usage.append('  %s' % cmd)
   163         return '\n'.join(usage)
   235         return '\n'.join(usage)
   164 
   236 
   167         Tries to fetch the given subcommand, printing a message with the
   239         Tries to fetch the given subcommand, printing a message with the
   168         appropriate command called from the command line (usually
   240         appropriate command called from the command line (usually
   169         "django-admin.py" or "manage.py") if it can't be found.
   241         "django-admin.py" or "manage.py") if it can't be found.
   170         """
   242         """
   171         try:
   243         try:
   172             app_name = get_commands(self.user_commands, self.project_directory)[subcommand]
   244             app_name = get_commands()[subcommand]
   173             if isinstance(app_name, BaseCommand):
   245             if isinstance(app_name, BaseCommand):
   174                 # If the command is already loaded, use it directly.
   246                 # If the command is already loaded, use it directly.
   175                 klass = app_name
   247                 klass = app_name
   176             else:
   248             else:
   177                 klass = load_command_class(app_name, subcommand)
   249                 klass = load_command_class(app_name, subcommand)
   187         being run, creates a parser appropriate to that command, and runs it.
   259         being run, creates a parser appropriate to that command, and runs it.
   188         """
   260         """
   189         # Preprocess options to extract --settings and --pythonpath.
   261         # Preprocess options to extract --settings and --pythonpath.
   190         # These options could affect the commands that are available, so they
   262         # These options could affect the commands that are available, so they
   191         # must be processed early.
   263         # must be processed early.
   192         parser = LaxOptionParser(version=get_version(), option_list=BaseCommand.option_list)
   264         parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
       
   265                                  version=get_version(),
       
   266                                  option_list=BaseCommand.option_list)
   193         try:
   267         try:
   194             options, args = parser.parse_args(self.argv)
   268             options, args = parser.parse_args(self.argv)
   195             handle_default_options(options)
   269             handle_default_options(options)
   196         except:
   270         except:
   197             pass # Ignore any option errors at this point.
   271             pass # Ignore any option errors at this point.
   204 
   278 
   205         if subcommand == 'help':
   279         if subcommand == 'help':
   206             if len(args) > 2:
   280             if len(args) > 2:
   207                 self.fetch_command(args[2]).print_help(self.prog_name, args[2])
   281                 self.fetch_command(args[2]).print_help(self.prog_name, args[2])
   208             else:
   282             else:
       
   283                 parser.print_lax_help()
   209                 sys.stderr.write(self.main_help_text() + '\n')
   284                 sys.stderr.write(self.main_help_text() + '\n')
   210                 sys.exit(1)
   285                 sys.exit(1)
   211         # Special-cases: We want 'django-admin.py --version' and
   286         # Special-cases: We want 'django-admin.py --version' and
   212         # 'django-admin.py --help' to work, for backwards compatibility.
   287         # 'django-admin.py --help' to work, for backwards compatibility.
   213         elif self.argv[1:] == ['--version']:
   288         elif self.argv[1:] == ['--version']:
   214             # LaxOptionParser already takes care of printing the version.
   289             # LaxOptionParser already takes care of printing the version.
   215             pass
   290             pass
   216         elif self.argv[1:] == ['--help']:
   291         elif self.argv[1:] == ['--help']:
       
   292             parser.print_lax_help()
   217             sys.stderr.write(self.main_help_text() + '\n')
   293             sys.stderr.write(self.main_help_text() + '\n')
   218         else:
   294         else:
   219             self.fetch_command(subcommand).run_from_argv(self.argv)
   295             self.fetch_command(subcommand).run_from_argv(self.argv)
   220 
   296 
   221 class ProjectManagementUtility(ManagementUtility):
   297 def setup_environ(settings_mod, original_settings_path=None):
   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     """
   298     """
   237     Configures the runtime environment. This can also be used by external
   299     Configures the runtime environment. This can also be used by external
   238     scripts wanting to set up a similar environment to manage.py.
   300     scripts wanting to set up a similar environment to manage.py.
   239     Returns the project directory (assuming the passed settings module is
   301     Returns the project directory (assuming the passed settings module is
   240     directly in the project directory).
   302     directly in the project directory).
       
   303 
       
   304     The "original_settings_path" parameter is optional, but recommended, since
       
   305     trying to work out the original path from the module can be problematic.
   241     """
   306     """
   242     # Add this project to sys.path so that it's importable in the conventional
   307     # 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
   308     # way. For example, if this file (manage.py) lives in a directory
   244     # "myproject", this code would add "/path/to/myproject" to sys.path.
   309     # "myproject", this code would add "/path/to/myproject" to sys.path.
   245     project_directory, settings_filename = os.path.split(settings_mod.__file__)
   310     project_directory, settings_filename = os.path.split(settings_mod.__file__)
   250     sys.path.append(os.path.join(project_directory, os.pardir))
   315     sys.path.append(os.path.join(project_directory, os.pardir))
   251     project_module = __import__(project_name, {}, {}, [''])
   316     project_module = __import__(project_name, {}, {}, [''])
   252     sys.path.pop()
   317     sys.path.pop()
   253 
   318 
   254     # Set DJANGO_SETTINGS_MODULE appropriately.
   319     # Set DJANGO_SETTINGS_MODULE appropriately.
   255     os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
   320     if original_settings_path:
       
   321         os.environ['DJANGO_SETTINGS_MODULE'] = original_settings_path
       
   322     else:
       
   323         os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
   256     return project_directory
   324     return project_directory
   257 
   325 
   258 def execute_from_command_line(argv=None):
   326 def execute_from_command_line(argv=None):
   259     """
   327     """
   260     A simple method that runs a ManagementUtility.
   328     A simple method that runs a ManagementUtility.
   265 def execute_manager(settings_mod, argv=None):
   333 def execute_manager(settings_mod, argv=None):
   266     """
   334     """
   267     Like execute_from_command_line(), but for use by manage.py, a
   335     Like execute_from_command_line(), but for use by manage.py, a
   268     project-specific django-admin.py utility.
   336     project-specific django-admin.py utility.
   269     """
   337     """
   270     project_directory = setup_environ(settings_mod)
   338     setup_environ(settings_mod)
   271     utility = ProjectManagementUtility(argv, project_directory)
   339     utility = ManagementUtility(argv)
   272     utility.execute()
   340     utility.execute()