web/lib/django/core/management/base.py
changeset 0 0d40e90630ef
child 29 cc9b7e14412b
equal deleted inserted replaced
-1:000000000000 0:0d40e90630ef
       
     1 """
       
     2 Base classes for writing management commands (named commands which can
       
     3 be executed through ``django-admin.py`` or ``manage.py``).
       
     4 
       
     5 """
       
     6 
       
     7 import os
       
     8 import sys
       
     9 from optparse import make_option, OptionParser
       
    10 
       
    11 import django
       
    12 from django.core.exceptions import ImproperlyConfigured
       
    13 from django.core.management.color import color_style
       
    14 
       
    15 try:
       
    16     set
       
    17 except NameError:
       
    18     from sets import Set as set     # For Python 2.3
       
    19 
       
    20 class CommandError(Exception):
       
    21     """
       
    22     Exception class indicating a problem while executing a management
       
    23     command.
       
    24 
       
    25     If this exception is raised during the execution of a management
       
    26     command, it will be caught and turned into a nicely-printed error
       
    27     message to the appropriate output stream (i.e., stderr); as a
       
    28     result, raising this exception (with a sensible description of the
       
    29     error) is the preferred way to indicate that something has gone
       
    30     wrong in the execution of a command.
       
    31     
       
    32     """
       
    33     pass
       
    34 
       
    35 def handle_default_options(options):
       
    36     """
       
    37     Include any default options that all commands should accept here
       
    38     so that ManagementUtility can handle them before searching for
       
    39     user commands.
       
    40     
       
    41     """
       
    42     if options.settings:
       
    43         os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
       
    44     if options.pythonpath:
       
    45         sys.path.insert(0, options.pythonpath)
       
    46 
       
    47 class BaseCommand(object):
       
    48     """
       
    49     The base class from which all management commands ultimately
       
    50     derive.
       
    51 
       
    52     Use this class if you want access to all of the mechanisms which
       
    53     parse the command-line arguments and work out what code to call in
       
    54     response; if you don't need to change any of that behavior,
       
    55     consider using one of the subclasses defined in this file.
       
    56 
       
    57     If you are interested in overriding/customizing various aspects of
       
    58     the command-parsing and -execution behavior, the normal flow works
       
    59     as follows:
       
    60 
       
    61     1. ``django-admin.py`` or ``manage.py`` loads the command class
       
    62        and calls its ``run_from_argv()`` method.
       
    63 
       
    64     2. The ``run_from_argv()`` method calls ``create_parser()`` to get
       
    65        an ``OptionParser`` for the arguments, parses them, performs
       
    66        any environment changes requested by options like
       
    67        ``pythonpath``, and then calls the ``execute()`` method,
       
    68        passing the parsed arguments.
       
    69 
       
    70     3. The ``execute()`` method attempts to carry out the command by
       
    71        calling the ``handle()`` method with the parsed arguments; any
       
    72        output produced by ``handle()`` will be printed to standard
       
    73        output and, if the command is intended to produce a block of
       
    74        SQL statements, will be wrapped in ``BEGIN`` and ``COMMIT``.
       
    75 
       
    76     4. If ``handle()`` raised a ``CommandError``, ``execute()`` will
       
    77        instead print an error message to ``stderr``.
       
    78 
       
    79     Thus, the ``handle()`` method is typically the starting point for
       
    80     subclasses; many built-in commands and command types either place
       
    81     all of their logic in ``handle()``, or perform some additional
       
    82     parsing work in ``handle()`` and then delegate from it to more
       
    83     specialized methods as needed.
       
    84 
       
    85     Several attributes affect behavior at various steps along the way:
       
    86     
       
    87     ``args``
       
    88         A string listing the arguments accepted by the command,
       
    89         suitable for use in help messages; e.g., a command which takes
       
    90         a list of application names might set this to '<appname
       
    91         appname ...>'.
       
    92 
       
    93     ``can_import_settings``
       
    94         A boolean indicating whether the command needs to be able to
       
    95         import Django settings; if ``True``, ``execute()`` will verify
       
    96         that this is possible before proceeding. Default value is
       
    97         ``True``.
       
    98 
       
    99     ``help``
       
   100         A short description of the command, which will be printed in
       
   101         help messages.
       
   102 
       
   103     ``option_list``
       
   104         This is the list of ``optparse`` options which will be fed
       
   105         into the command's ``OptionParser`` for parsing arguments.
       
   106 
       
   107     ``output_transaction``
       
   108         A boolean indicating whether the command outputs SQL
       
   109         statements; if ``True``, the output will automatically be
       
   110         wrapped with ``BEGIN;`` and ``COMMIT;``. Default value is
       
   111         ``False``.
       
   112 
       
   113     ``requires_model_validation``
       
   114         A boolean; if ``True``, validation of installed models will be
       
   115         performed prior to executing the command. Default value is
       
   116         ``True``. To validate an individual application's models
       
   117         rather than all applications' models, call
       
   118         ``self.validate(app)`` from ``handle()``, where ``app`` is the
       
   119         application's Python module.
       
   120     
       
   121     """
       
   122     # Metadata about this command.
       
   123     option_list = (
       
   124         make_option('-v', '--verbosity', action='store', dest='verbosity', default='1',
       
   125             type='choice', choices=['0', '1', '2'],
       
   126             help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'),
       
   127         make_option('--settings',
       
   128             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.'),
       
   129         make_option('--pythonpath',
       
   130             help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
       
   131         make_option('--traceback', action='store_true',
       
   132             help='Print traceback on exception'),
       
   133     )
       
   134     help = ''
       
   135     args = ''
       
   136 
       
   137     # Configuration shortcuts that alter various logic.
       
   138     can_import_settings = True
       
   139     requires_model_validation = True
       
   140     output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
       
   141 
       
   142     def __init__(self):
       
   143         self.style = color_style()
       
   144 
       
   145     def get_version(self):
       
   146         """
       
   147         Return the Django version, which should be correct for all
       
   148         built-in Django commands. User-supplied commands should
       
   149         override this method.
       
   150         
       
   151         """
       
   152         return django.get_version()
       
   153 
       
   154     def usage(self, subcommand):
       
   155         """
       
   156         Return a brief description of how to use this command, by
       
   157         default from the attribute ``self.help``.
       
   158         
       
   159         """
       
   160         usage = '%%prog %s [options] %s' % (subcommand, self.args)
       
   161         if self.help:
       
   162             return '%s\n\n%s' % (usage, self.help)
       
   163         else:
       
   164             return usage
       
   165 
       
   166     def create_parser(self, prog_name, subcommand):
       
   167         """
       
   168         Create and return the ``OptionParser`` which will be used to
       
   169         parse the arguments to this command.
       
   170         
       
   171         """
       
   172         return OptionParser(prog=prog_name,
       
   173                             usage=self.usage(subcommand),
       
   174                             version=self.get_version(),
       
   175                             option_list=self.option_list)
       
   176 
       
   177     def print_help(self, prog_name, subcommand):
       
   178         """
       
   179         Print the help message for this command, derived from
       
   180         ``self.usage()``.
       
   181         
       
   182         """
       
   183         parser = self.create_parser(prog_name, subcommand)
       
   184         parser.print_help()
       
   185 
       
   186     def run_from_argv(self, argv):
       
   187         """
       
   188         Set up any environment changes requested (e.g., Python path
       
   189         and Django settings), then run this command.
       
   190         
       
   191         """
       
   192         parser = self.create_parser(argv[0], argv[1])
       
   193         options, args = parser.parse_args(argv[2:])
       
   194         handle_default_options(options)
       
   195         self.execute(*args, **options.__dict__)
       
   196 
       
   197     def execute(self, *args, **options):
       
   198         """
       
   199         Try to execute this command, performing model validation if
       
   200         needed (as controlled by the attribute
       
   201         ``self.requires_model_validation``). If the command raises a
       
   202         ``CommandError``, intercept it and print it sensibly to
       
   203         stderr.
       
   204         
       
   205         """
       
   206         # Switch to English, because django-admin.py creates database content
       
   207         # like permissions, and those shouldn't contain any translations.
       
   208         # But only do this if we can assume we have a working settings file,
       
   209         # because django.utils.translation requires settings.
       
   210         if self.can_import_settings:
       
   211             try:
       
   212                 from django.utils import translation
       
   213                 translation.activate('en-us')
       
   214             except ImportError, e:
       
   215                 # If settings should be available, but aren't,
       
   216                 # raise the error and quit.
       
   217                 sys.stderr.write(self.style.ERROR(str('Error: %s\n' % e)))
       
   218                 sys.exit(1)
       
   219         try:
       
   220             if self.requires_model_validation:
       
   221                 self.validate()
       
   222             output = self.handle(*args, **options)
       
   223             if output:
       
   224                 if self.output_transaction:
       
   225                     # This needs to be imported here, because it relies on settings.
       
   226                     from django.db import connection
       
   227                     if connection.ops.start_transaction_sql():
       
   228                         print self.style.SQL_KEYWORD(connection.ops.start_transaction_sql())
       
   229                 print output
       
   230                 if self.output_transaction:
       
   231                     print self.style.SQL_KEYWORD("COMMIT;")
       
   232         except CommandError, e:
       
   233             sys.stderr.write(self.style.ERROR(str('Error: %s\n' % e)))
       
   234             sys.exit(1)
       
   235 
       
   236     def validate(self, app=None, display_num_errors=False):
       
   237         """
       
   238         Validates the given app, raising CommandError for any errors.
       
   239         
       
   240         If app is None, then this will validate all installed apps.
       
   241         
       
   242         """
       
   243         from django.core.management.validation import get_validation_errors
       
   244         try:
       
   245             from cStringIO import StringIO
       
   246         except ImportError:
       
   247             from StringIO import StringIO
       
   248         s = StringIO()
       
   249         num_errors = get_validation_errors(s, app)
       
   250         if num_errors:
       
   251             s.seek(0)
       
   252             error_text = s.read()
       
   253             raise CommandError("One or more models did not validate:\n%s" % error_text)
       
   254         if display_num_errors:
       
   255             print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '')
       
   256 
       
   257     def handle(self, *args, **options):
       
   258         """
       
   259         The actual logic of the command. Subclasses must implement
       
   260         this method.
       
   261         
       
   262         """
       
   263         raise NotImplementedError()
       
   264 
       
   265 class AppCommand(BaseCommand):
       
   266     """
       
   267     A management command which takes one or more installed application
       
   268     names as arguments, and does something with each of them.
       
   269 
       
   270     Rather than implementing ``handle()``, subclasses must implement
       
   271     ``handle_app()``, which will be called once for each application.
       
   272     
       
   273     """
       
   274     args = '<appname appname ...>'
       
   275 
       
   276     def handle(self, *app_labels, **options):
       
   277         from django.db import models
       
   278         if not app_labels:
       
   279             raise CommandError('Enter at least one appname.')
       
   280         try:
       
   281             app_list = [models.get_app(app_label) for app_label in app_labels]
       
   282         except (ImproperlyConfigured, ImportError), e:
       
   283             raise CommandError("%s. Are you sure your INSTALLED_APPS setting is correct?" % e)
       
   284         output = []
       
   285         for app in app_list:
       
   286             app_output = self.handle_app(app, **options)
       
   287             if app_output:
       
   288                 output.append(app_output)
       
   289         return '\n'.join(output)
       
   290 
       
   291     def handle_app(self, app, **options):
       
   292         """
       
   293         Perform the command's actions for ``app``, which will be the
       
   294         Python module corresponding to an application name given on
       
   295         the command line.
       
   296         
       
   297         """
       
   298         raise NotImplementedError()
       
   299 
       
   300 class LabelCommand(BaseCommand):
       
   301     """
       
   302     A management command which takes one or more arbitrary arguments
       
   303     (labels) on the command line, and does something with each of
       
   304     them.
       
   305 
       
   306     Rather than implementing ``handle()``, subclasses must implement
       
   307     ``handle_label()``, which will be called once for each label.
       
   308 
       
   309     If the arguments should be names of installed applications, use
       
   310     ``AppCommand`` instead.
       
   311     
       
   312     """
       
   313     args = '<label label ...>'
       
   314     label = 'label'
       
   315 
       
   316     def handle(self, *labels, **options):
       
   317         if not labels:
       
   318             raise CommandError('Enter at least one %s.' % self.label)
       
   319 
       
   320         output = []
       
   321         for label in labels:
       
   322             label_output = self.handle_label(label, **options)
       
   323             if label_output:
       
   324                 output.append(label_output)
       
   325         return '\n'.join(output)
       
   326 
       
   327     def handle_label(self, label, **options):
       
   328         """
       
   329         Perform the command's actions for ``label``, which will be the
       
   330         string as given on the command line.
       
   331         
       
   332         """
       
   333         raise NotImplementedError()
       
   334 
       
   335 class NoArgsCommand(BaseCommand):
       
   336     """
       
   337     A command which takes no arguments on the command line.
       
   338 
       
   339     Rather than implementing ``handle()``, subclasses must implement
       
   340     ``handle_noargs()``; ``handle()`` itself is overridden to ensure
       
   341     no arguments are passed to the command.
       
   342 
       
   343     Attempting to pass arguments will raise ``CommandError``.
       
   344     
       
   345     """
       
   346     args = ''
       
   347 
       
   348     def handle(self, *args, **options):
       
   349         if args:
       
   350             raise CommandError("Command doesn't accept any arguments")
       
   351         return self.handle_noargs(**options)
       
   352 
       
   353     def handle_noargs(self, **options):
       
   354         """
       
   355         Perform this command's actions.
       
   356         
       
   357         """
       
   358         raise NotImplementedError()
       
   359 
       
   360 def copy_helper(style, app_or_project, name, directory, other_name=''):
       
   361     """
       
   362     Copies either a Django application layout template or a Django project
       
   363     layout template into the specified directory.
       
   364 
       
   365     """
       
   366     # style -- A color style object (see django.core.management.color).
       
   367     # app_or_project -- The string 'app' or 'project'.
       
   368     # name -- The name of the application or project.
       
   369     # directory -- The directory to which the layout template should be copied.
       
   370     # other_name -- When copying an application layout, this should be the name
       
   371     #               of the project.
       
   372     import re
       
   373     import shutil
       
   374     other = {'project': 'app', 'app': 'project'}[app_or_project]
       
   375     if not re.search(r'^[_a-zA-Z]\w*$', name): # If it's not a valid directory name.
       
   376         # Provide a smart error message, depending on the error.
       
   377         if not re.search(r'^[_a-zA-Z]', name):
       
   378             message = 'make sure the name begins with a letter or underscore'
       
   379         else:
       
   380             message = 'use only numbers, letters and underscores'
       
   381         raise CommandError("%r is not a valid %s name. Please %s." % (name, app_or_project, message))
       
   382     top_dir = os.path.join(directory, name)
       
   383     try:
       
   384         os.mkdir(top_dir)
       
   385     except OSError, e:
       
   386         raise CommandError(e)
       
   387 
       
   388     # Determine where the app or project templates are. Use
       
   389     # django.__path__[0] because we don't know into which directory
       
   390     # django has been installed.
       
   391     template_dir = os.path.join(django.__path__[0], 'conf', '%s_template' % app_or_project)
       
   392 
       
   393     for d, subdirs, files in os.walk(template_dir):
       
   394         relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name)
       
   395         if relative_dir:
       
   396             os.mkdir(os.path.join(top_dir, relative_dir))
       
   397         for i, subdir in enumerate(subdirs):
       
   398             if subdir.startswith('.'):
       
   399                 del subdirs[i]
       
   400         for f in files:
       
   401             if not f.endswith('.py'):
       
   402                 # Ignore .pyc, .pyo, .py.class etc, as they cause various
       
   403                 # breakages.
       
   404                 continue
       
   405             path_old = os.path.join(d, f)
       
   406             path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name))
       
   407             fp_old = open(path_old, 'r')
       
   408             fp_new = open(path_new, 'w')
       
   409             fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name))
       
   410             fp_old.close()
       
   411             fp_new.close()
       
   412             try:
       
   413                 shutil.copymode(path_old, path_new)
       
   414                 _make_writeable(path_new)
       
   415             except OSError:
       
   416                 sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new))
       
   417 
       
   418 def _make_writeable(filename):
       
   419     """
       
   420     Make sure that the file is writeable. Useful if our source is
       
   421     read-only.
       
   422     
       
   423     """
       
   424     import stat
       
   425     if sys.platform.startswith('java'):
       
   426         # On Jython there is no os.access()
       
   427         return
       
   428     if not os.access(filename, os.W_OK):
       
   429         st = os.stat(filename)
       
   430         new_permissions = stat.S_IMODE(st.st_mode) | stat.S_IWUSR
       
   431         os.chmod(filename, new_permissions)