app/django/core/management/base.py
changeset 323 ff1a9aa48cfd
parent 54 03e267d67478
equal deleted inserted replaced
322:6641e941ef1e 323:ff1a9aa48cfd
       
     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 
     1 import os
     7 import os
     2 import sys
     8 import sys
     3 from optparse import make_option, OptionParser
     9 from optparse import make_option, OptionParser
     4 
    10 
     5 import django
    11 import django
     6 from django.core.exceptions import ImproperlyConfigured
    12 from django.core.exceptions import ImproperlyConfigured
     7 from django.core.management.color import color_style
    13 from django.core.management.color import color_style
     8 
    14 
       
    15 try:
       
    16     set
       
    17 except NameError:
       
    18     from sets import Set as set     # For Python 2.3
       
    19 
     9 class CommandError(Exception):
    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     """
    10     pass
    33     pass
    11 
    34 
    12 def handle_default_options(options):
    35 def handle_default_options(options):
    13     """
    36     """
    14     Include any default options that all commands should accept
    37     Include any default options that all commands should accept here
    15     here so that ManagementUtility can handle them before searching
    38     so that ManagementUtility can handle them before searching for
    16     for user commands.
    39     user commands.
       
    40     
    17     """
    41     """
    18     if options.settings:
    42     if options.settings:
    19         os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    43         os.environ['DJANGO_SETTINGS_MODULE'] = options.settings
    20     if options.pythonpath:
    44     if options.pythonpath:
    21         sys.path.insert(0, options.pythonpath)
    45         sys.path.insert(0, options.pythonpath)
    22 
    46 
    23 class BaseCommand(object):
    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 ``ComandError``, ``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     """
    24     # Metadata about this command.
   122     # Metadata about this command.
    25     option_list = (
   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'),
    26         make_option('--settings',
   127         make_option('--settings',
    27             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.'),
   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.'),
    28         make_option('--pythonpath',
   129         make_option('--pythonpath',
    29             help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
   130             help='A directory to add to the Python path, e.g. "/home/djangoprojects/myproject".'),
    30         make_option('--traceback', action='store_true',
   131         make_option('--traceback', action='store_true',
    41     def __init__(self):
   142     def __init__(self):
    42         self.style = color_style()
   143         self.style = color_style()
    43 
   144 
    44     def get_version(self):
   145     def get_version(self):
    45         """
   146         """
    46         Returns the Django version, which should be correct for all built-in
   147         Return the Django version, which should be correct for all
    47         Django commands. User-supplied commands should override this method.
   148         built-in Django commands. User-supplied commands should
       
   149         override this method.
       
   150         
    48         """
   151         """
    49         return django.get_version()
   152         return django.get_version()
    50 
   153 
    51     def usage(self, subcommand):
   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         """
    52         usage = '%%prog %s [options] %s' % (subcommand, self.args)
   160         usage = '%%prog %s [options] %s' % (subcommand, self.args)
    53         if self.help:
   161         if self.help:
    54             return '%s\n\n%s' % (usage, self.help)
   162             return '%s\n\n%s' % (usage, self.help)
    55         else:
   163         else:
    56             return usage
   164             return usage
    57 
   165 
    58     def create_parser(self, prog_name, subcommand):
   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         """
    59         return OptionParser(prog=prog_name,
   172         return OptionParser(prog=prog_name,
    60                             usage=self.usage(subcommand),
   173                             usage=self.usage(subcommand),
    61                             version=self.get_version(),
   174                             version=self.get_version(),
    62                             option_list=self.option_list)
   175                             option_list=self.option_list)
    63 
   176 
    64     def print_help(self, prog_name, subcommand):
   177     def print_help(self, prog_name, subcommand):
       
   178         """
       
   179         Print the help message for this command, derived from
       
   180         ``self.usage()``.
       
   181         
       
   182         """
    65         parser = self.create_parser(prog_name, subcommand)
   183         parser = self.create_parser(prog_name, subcommand)
    66         parser.print_help()
   184         parser.print_help()
    67 
   185 
    68     def run_from_argv(self, argv):
   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         """
    69         parser = self.create_parser(argv[0], argv[1])
   192         parser = self.create_parser(argv[0], argv[1])
    70         options, args = parser.parse_args(argv[2:])
   193         options, args = parser.parse_args(argv[2:])
    71         handle_default_options(options)
   194         handle_default_options(options)
    72         self.execute(*args, **options.__dict__)
   195         self.execute(*args, **options.__dict__)
    73 
   196 
    74     def execute(self, *args, **options):
   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         """
    75         # Switch to English, because django-admin.py creates database content
   206         # Switch to English, because django-admin.py creates database content
    76         # like permissions, and those shouldn't contain any translations.
   207         # like permissions, and those shouldn't contain any translations.
    77         # But only do this if we can assume we have a working settings file,
   208         # But only do this if we can assume we have a working settings file,
    78         # because django.utils.translation requires settings.
   209         # because django.utils.translation requires settings.
    79         if self.can_import_settings:
   210         if self.can_import_settings:
    80             from django.utils import translation
   211             try:
    81             translation.activate('en-us')
   212                 from django.utils import translation
    82 
   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)
    83         try:
   219         try:
    84             if self.requires_model_validation:
   220             if self.requires_model_validation:
    85                 self.validate()
   221                 self.validate()
    86             output = self.handle(*args, **options)
   222             output = self.handle(*args, **options)
    87             if output:
   223             if output:
    98             sys.exit(1)
   234             sys.exit(1)
    99 
   235 
   100     def validate(self, app=None, display_num_errors=False):
   236     def validate(self, app=None, display_num_errors=False):
   101         """
   237         """
   102         Validates the given app, raising CommandError for any errors.
   238         Validates the given app, raising CommandError for any errors.
   103 
   239         
   104         If app is None, then this will validate all installed apps.
   240         If app is None, then this will validate all installed apps.
       
   241         
   105         """
   242         """
   106         from django.core.management.validation import get_validation_errors
   243         from django.core.management.validation import get_validation_errors
   107         try:
   244         try:
   108             from cStringIO import StringIO
   245             from cStringIO import StringIO
   109         except ImportError:
   246         except ImportError:
   116             raise CommandError("One or more models did not validate:\n%s" % error_text)
   253             raise CommandError("One or more models did not validate:\n%s" % error_text)
   117         if display_num_errors:
   254         if display_num_errors:
   118             print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '')
   255             print "%s error%s found" % (num_errors, num_errors != 1 and 's' or '')
   119 
   256 
   120     def handle(self, *args, **options):
   257     def handle(self, *args, **options):
       
   258         """
       
   259         The actual logic of the command. Subclasses must implement
       
   260         this method.
       
   261         
       
   262         """
   121         raise NotImplementedError()
   263         raise NotImplementedError()
   122 
   264 
   123 class AppCommand(BaseCommand):
   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     """
   124     args = '<appname appname ...>'
   274     args = '<appname appname ...>'
   125 
   275 
   126     def handle(self, *app_labels, **options):
   276     def handle(self, *app_labels, **options):
   127         from django.db import models
   277         from django.db import models
   128         if not app_labels:
   278         if not app_labels:
   137             if app_output:
   287             if app_output:
   138                 output.append(app_output)
   288                 output.append(app_output)
   139         return '\n'.join(output)
   289         return '\n'.join(output)
   140 
   290 
   141     def handle_app(self, app, **options):
   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         """
   142         raise NotImplementedError()
   298         raise NotImplementedError()
   143 
   299 
   144 class LabelCommand(BaseCommand):
   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     """
   145     args = '<label label ...>'
   313     args = '<label label ...>'
   146     label = 'label'
   314     label = 'label'
   147 
   315 
   148     def handle(self, *labels, **options):
   316     def handle(self, *labels, **options):
   149         if not labels:
   317         if not labels:
   155             if label_output:
   323             if label_output:
   156                 output.append(label_output)
   324                 output.append(label_output)
   157         return '\n'.join(output)
   325         return '\n'.join(output)
   158 
   326 
   159     def handle_label(self, label, **options):
   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         """
   160         raise NotImplementedError()
   333         raise NotImplementedError()
   161 
   334 
   162 class NoArgsCommand(BaseCommand):
   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     """
   163     args = ''
   346     args = ''
   164 
   347 
   165     def handle(self, *args, **options):
   348     def handle(self, *args, **options):
   166         if args:
   349         if args:
   167             raise CommandError("Command doesn't accept any arguments")
   350             raise CommandError("Command doesn't accept any arguments")
   168         return self.handle_noargs(**options)
   351         return self.handle_noargs(**options)
   169 
   352 
   170     def handle_noargs(self, **options):
   353     def handle_noargs(self, **options):
       
   354         """
       
   355         Perform this command's actions.
       
   356         
       
   357         """
   171         raise NotImplementedError()
   358         raise NotImplementedError()
   172 
   359 
   173 def copy_helper(style, app_or_project, name, directory, other_name=''):
   360 def copy_helper(style, app_or_project, name, directory, other_name=''):
   174     """
   361     """
   175     Copies either a Django application layout template or a Django project
   362     Copies either a Django application layout template or a Django project
   176     layout template into the specified directory.
   363     layout template into the specified directory.
       
   364 
   177     """
   365     """
   178     # style -- A color style object (see django.core.management.color).
   366     # style -- A color style object (see django.core.management.color).
   179     # app_or_project -- The string 'app' or 'project'.
   367     # app_or_project -- The string 'app' or 'project'.
   180     # name -- The name of the application or project.
   368     # name -- The name of the application or project.
   181     # directory -- The directory to which the layout template should be copied.
   369     # directory -- The directory to which the layout template should be copied.
   182     # other_name -- When copying an application layout, this should be the name
   370     # other_name -- When copying an application layout, this should be the name
   183     #               of the project.
   371     #               of the project.
   184     import re
   372     import re
   185     import shutil
   373     import shutil
   186     other = {'project': 'app', 'app': 'project'}[app_or_project]
   374     other = {'project': 'app', 'app': 'project'}[app_or_project]
   187     if not re.search(r'^\w+$', name): # If it's not a valid directory name.
   375     if not re.search(r'^[_a-zA-Z]\w*$', name): # If it's not a valid directory name.
   188         raise CommandError("%r is not a valid %s name. Please use only numbers, letters and underscores." % (name, app_or_project))
   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))
   189     top_dir = os.path.join(directory, name)
   382     top_dir = os.path.join(directory, name)
   190     try:
   383     try:
   191         os.mkdir(top_dir)
   384         os.mkdir(top_dir)
   192     except OSError, e:
   385     except OSError, e:
   193         raise CommandError(e)
   386         raise CommandError(e)
   219                 _make_writeable(path_new)
   412                 _make_writeable(path_new)
   220             except OSError:
   413             except OSError:
   221                 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))
   414                 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))
   222 
   415 
   223 def _make_writeable(filename):
   416 def _make_writeable(filename):
   224     "Makes sure that the file is writeable. Useful if our source is read-only."
   417     """
       
   418     Make sure that the file is writeable. Useful if our source is
       
   419     read-only.
       
   420     
       
   421     """
   225     import stat
   422     import stat
   226     if sys.platform.startswith('java'):
   423     if sys.platform.startswith('java'):
   227         # On Jython there is no os.access()
   424         # On Jython there is no os.access()
   228         return
   425         return
   229     if not os.access(filename, os.W_OK):
   426     if not os.access(filename, os.W_OK):