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