2008-09-22 14:03:24 +08:00
"""
Base classes for writing management commands ( named commands which can
be executed through ` ` django - admin . py ` ` or ` ` manage . py ` ` ) .
"""
2007-10-14 00:02:54 +08:00
import os
import sys
from optparse import make_option , OptionParser
2007-09-10 05:57:59 +08:00
import django
2007-08-16 14:06:55 +08:00
from django . core . exceptions import ImproperlyConfigured
from django . core . management . color import color_style
2008-07-06 14:39:44 +08:00
try :
set
except NameError :
from sets import Set as set # For Python 2.3
2007-08-16 14:06:55 +08:00
class CommandError ( Exception ) :
2008-09-22 14:03:24 +08:00
"""
Exception class indicating a problem while executing a management
command .
If this exception is raised during the execution of a management
command , it will be caught and turned into a nicely - printed error
message to the appropriate output stream ( i . e . , stderr ) ; as a
result , raising this exception ( with a sensible description of the
error ) is the preferred way to indicate that something has gone
wrong in the execution of a command .
"""
2007-08-16 14:06:55 +08:00
pass
2007-09-22 00:19:20 +08:00
def handle_default_options ( options ) :
"""
2008-09-22 14:03:24 +08:00
Include any default options that all commands should accept here
so that ManagementUtility can handle them before searching for
user commands .
2007-09-22 00:19:20 +08:00
"""
if options . settings :
os . environ [ ' DJANGO_SETTINGS_MODULE ' ] = options . settings
if options . pythonpath :
sys . path . insert ( 0 , options . pythonpath )
2007-10-14 00:40:20 +08:00
2007-08-16 14:06:55 +08:00
class BaseCommand ( object ) :
2008-09-22 14:03:24 +08:00
"""
The base class from which all management commands ultimately
derive .
Use this class if you want access to all of the mechanisms which
parse the command - line arguments and work out what code to call in
response ; if you don ' t need to change any of that behavior,
consider using one of the subclasses defined in this file .
If you are interested in overriding / customizing various aspects of
the command - parsing and - execution behavior , the normal flow works
as follows :
1. ` ` django - admin . py ` ` or ` ` manage . py ` ` loads the command class
and calls its ` ` run_from_argv ( ) ` ` method .
2. The ` ` run_from_argv ( ) ` ` method calls ` ` create_parser ( ) ` ` to get
an ` ` OptionParser ` ` for the arguments , parses them , performs
any environment changes requested by options like
` ` pythonpath ` ` , and then calls the ` ` execute ( ) ` ` method ,
passing the parsed arguments .
3. The ` ` execute ( ) ` ` method attempts to carry out the command by
calling the ` ` handle ( ) ` ` method with the parsed arguments ; any
output produced by ` ` handle ( ) ` ` will be printed to standard
output and , if the command is intended to produce a block of
SQL statements , will be wrapped in ` ` BEGIN ` ` and ` ` COMMIT ` ` .
2009-04-01 00:57:46 +08:00
4. If ` ` handle ( ) ` ` raised a ` ` CommandError ` ` , ` ` execute ( ) ` ` will
2008-09-22 14:03:24 +08:00
instead print an error message to ` ` stderr ` ` .
Thus , the ` ` handle ( ) ` ` method is typically the starting point for
subclasses ; many built - in commands and command types either place
all of their logic in ` ` handle ( ) ` ` , or perform some additional
parsing work in ` ` handle ( ) ` ` and then delegate from it to more
specialized methods as needed .
Several attributes affect behavior at various steps along the way :
` ` args ` `
A string listing the arguments accepted by the command ,
suitable for use in help messages ; e . g . , a command which takes
a list of application names might set this to ' <appname
appname . . . > ' .
` ` can_import_settings ` `
A boolean indicating whether the command needs to be able to
import Django settings ; if ` ` True ` ` , ` ` execute ( ) ` ` will verify
that this is possible before proceeding . Default value is
` ` True ` ` .
` ` help ` `
A short description of the command , which will be printed in
help messages .
` ` option_list ` `
This is the list of ` ` optparse ` ` options which will be fed
into the command ' s ``OptionParser`` for parsing arguments.
` ` output_transaction ` `
A boolean indicating whether the command outputs SQL
statements ; if ` ` True ` ` , the output will automatically be
wrapped with ` ` BEGIN ; ` ` and ` ` COMMIT ; ` ` . Default value is
` ` False ` ` .
` ` requires_model_validation ` `
A boolean ; if ` ` True ` ` , validation of installed models will be
performed prior to executing the command . Default value is
` ` True ` ` . To validate an individual application ' s models
rather than all applications ' models, call
` ` self . validate ( app ) ` ` from ` ` handle ( ) ` ` , where ` ` app ` ` is the
application ' s Python module.
"""
2007-08-16 14:06:55 +08:00
# Metadata about this command.
2007-09-10 05:57:59 +08:00
option_list = (
2008-10-02 20:57:13 +08:00
make_option ( ' -v ' , ' --verbosity ' , action = ' store ' , dest = ' verbosity ' , default = ' 1 ' ,
type = ' choice ' , choices = [ ' 0 ' , ' 1 ' , ' 2 ' ] ,
help = ' Verbosity level; 0=minimal output, 1=normal output, 2=all output ' ) ,
2007-09-10 05:57:59 +08:00
make_option ( ' --settings ' ,
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. ' ) ,
make_option ( ' --pythonpath ' ,
help = ' A directory to add to the Python path, e.g. " /home/djangoprojects/myproject " . ' ) ,
2007-12-17 19:09:32 +08:00
make_option ( ' --traceback ' , action = ' store_true ' ,
help = ' Print traceback on exception ' ) ,
2007-09-10 05:57:59 +08:00
)
2007-08-16 14:06:55 +08:00
help = ' '
args = ' '
# Configuration shortcuts that alter various logic.
can_import_settings = True
requires_model_validation = True
output_transaction = False # Whether to wrap the output in a "BEGIN; COMMIT;"
def __init__ ( self ) :
self . style = color_style ( )
2007-09-10 05:57:59 +08:00
def get_version ( self ) :
"""
2008-09-22 14:03:24 +08:00
Return the Django version , which should be correct for all
built - in Django commands . User - supplied commands should
override this method .
2007-09-10 05:57:59 +08:00
"""
return django . get_version ( )
2007-09-11 12:24:35 +08:00
def usage ( self , subcommand ) :
2008-09-22 14:03:24 +08:00
"""
Return a brief description of how to use this command , by
default from the attribute ` ` self . help ` ` .
"""
2007-09-11 12:24:35 +08:00
usage = ' %% prog %s [options] %s ' % ( subcommand , self . args )
2007-09-10 05:57:59 +08:00
if self . help :
return ' %s \n \n %s ' % ( usage , self . help )
else :
return usage
2007-09-11 12:24:35 +08:00
def create_parser ( self , prog_name , subcommand ) :
2008-09-22 14:03:24 +08:00
"""
Create and return the ` ` OptionParser ` ` which will be used to
parse the arguments to this command .
"""
2007-09-10 05:57:59 +08:00
return OptionParser ( prog = prog_name ,
2007-09-11 12:24:35 +08:00
usage = self . usage ( subcommand ) ,
2007-09-10 05:57:59 +08:00
version = self . get_version ( ) ,
option_list = self . option_list )
2007-09-11 12:24:35 +08:00
def print_help ( self , prog_name , subcommand ) :
2008-09-22 14:03:24 +08:00
"""
Print the help message for this command , derived from
` ` self . usage ( ) ` ` .
"""
2007-09-11 12:24:35 +08:00
parser = self . create_parser ( prog_name , subcommand )
2007-09-10 05:57:59 +08:00
parser . print_help ( )
2007-09-11 11:46:35 +08:00
def run_from_argv ( self , argv ) :
2008-09-22 14:03:24 +08:00
"""
Set up any environment changes requested ( e . g . , Python path
and Django settings ) , then run this command .
"""
2007-09-11 12:24:35 +08:00
parser = self . create_parser ( argv [ 0 ] , argv [ 1 ] )
options , args = parser . parse_args ( argv [ 2 : ] )
2007-09-22 00:19:20 +08:00
handle_default_options ( options )
2007-09-11 12:37:23 +08:00
self . execute ( * args , * * options . __dict__ )
2007-09-10 05:57:59 +08:00
2007-08-16 14:06:55 +08:00
def execute ( self , * args , * * options ) :
2008-09-22 14:03:24 +08:00
"""
Try to execute this command , performing model validation if
needed ( as controlled by the attribute
` ` self . requires_model_validation ` ` ) . If the command raises a
` ` CommandError ` ` , intercept it and print it sensibly to
stderr .
"""
2007-08-16 14:06:55 +08:00
# Switch to English, because django-admin.py creates database content
# like permissions, and those shouldn't contain any translations.
# But only do this if we can assume we have a working settings file,
# because django.utils.translation requires settings.
if self . can_import_settings :
2008-08-10 16:42:49 +08:00
try :
from django . utils import translation
translation . activate ( ' en-us ' )
except ImportError , e :
2008-10-02 20:57:13 +08:00
# If settings should be available, but aren't,
2008-08-10 16:42:49 +08:00
# raise the error and quit.
sys . stderr . write ( self . style . ERROR ( str ( ' Error: %s \n ' % e ) ) )
sys . exit ( 1 )
2007-08-16 14:06:55 +08:00
try :
if self . requires_model_validation :
self . validate ( )
output = self . handle ( * args , * * options )
if output :
if self . output_transaction :
# This needs to be imported here, because it relies on settings.
2007-08-20 08:24:03 +08:00
from django . db import connection
if connection . ops . start_transaction_sql ( ) :
print self . style . SQL_KEYWORD ( connection . ops . start_transaction_sql ( ) )
2007-08-16 14:06:55 +08:00
print output
if self . output_transaction :
print self . style . SQL_KEYWORD ( " COMMIT; " )
except CommandError , e :
sys . stderr . write ( self . style . ERROR ( str ( ' Error: %s \n ' % e ) ) )
sys . exit ( 1 )
2007-08-27 08:30:37 +08:00
def validate ( self , app = None , display_num_errors = False ) :
2007-08-16 14:06:55 +08:00
"""
Validates the given app , raising CommandError for any errors .
2008-09-22 14:03:24 +08:00
2007-08-16 14:06:55 +08:00
If app is None , then this will validate all installed apps .
2008-09-22 14:03:24 +08:00
2007-08-16 14:06:55 +08:00
"""
from django . core . management . validation import get_validation_errors
try :
from cStringIO import StringIO
except ImportError :
from StringIO import StringIO
s = StringIO ( )
num_errors = get_validation_errors ( s , app )
if num_errors :
s . seek ( 0 )
error_text = s . read ( )
raise CommandError ( " One or more models did not validate: \n %s " % error_text )
2007-08-27 08:30:37 +08:00
if display_num_errors :
print " %s error %s found " % ( num_errors , num_errors != 1 and ' s ' or ' ' )
2007-08-16 14:06:55 +08:00
def handle ( self , * args , * * options ) :
2008-09-22 14:03:24 +08:00
"""
The actual logic of the command . Subclasses must implement
this method .
"""
2007-08-16 14:06:55 +08:00
raise NotImplementedError ( )
class AppCommand ( BaseCommand ) :
2008-09-22 14:03:24 +08:00
"""
A management command which takes one or more installed application
names as arguments , and does something with each of them .
Rather than implementing ` ` handle ( ) ` ` , subclasses must implement
` ` handle_app ( ) ` ` , which will be called once for each application .
"""
2007-09-10 05:57:59 +08:00
args = ' <appname appname ...> '
2007-08-16 14:06:55 +08:00
def handle ( self , * app_labels , * * options ) :
from django . db import models
if not app_labels :
raise CommandError ( ' Enter at least one appname. ' )
try :
app_list = [ models . get_app ( app_label ) for app_label in app_labels ]
except ( ImproperlyConfigured , ImportError ) , e :
raise CommandError ( " %s . Are you sure your INSTALLED_APPS setting is correct? " % e )
output = [ ]
for app in app_list :
app_output = self . handle_app ( app , * * options )
if app_output :
output . append ( app_output )
return ' \n ' . join ( output )
def handle_app ( self , app , * * options ) :
2008-09-22 14:03:24 +08:00
"""
Perform the command ' s actions for ``app``, which will be the
Python module corresponding to an application name given on
the command line .
"""
2007-08-16 14:06:55 +08:00
raise NotImplementedError ( )
2007-08-16 22:34:01 +08:00
class LabelCommand ( BaseCommand ) :
2008-09-22 14:03:24 +08:00
"""
A management command which takes one or more arbitrary arguments
( labels ) on the command line , and does something with each of
them .
Rather than implementing ` ` handle ( ) ` ` , subclasses must implement
` ` handle_label ( ) ` ` , which will be called once for each label .
If the arguments should be names of installed applications , use
` ` AppCommand ` ` instead .
"""
2007-09-10 05:57:59 +08:00
args = ' <label label ...> '
2007-08-16 22:34:01 +08:00
label = ' label '
2007-08-17 03:14:09 +08:00
2007-08-16 22:34:01 +08:00
def handle ( self , * labels , * * options ) :
if not labels :
raise CommandError ( ' Enter at least one %s . ' % self . label )
output = [ ]
for label in labels :
label_output = self . handle_label ( label , * * options )
if label_output :
output . append ( label_output )
return ' \n ' . join ( output )
def handle_label ( self , label , * * options ) :
2008-09-22 14:03:24 +08:00
"""
Perform the command ' s actions for ``label``, which will be the
string as given on the command line .
"""
2007-08-16 22:34:01 +08:00
raise NotImplementedError ( )
class NoArgsCommand ( BaseCommand ) :
2008-09-22 14:03:24 +08:00
"""
A command which takes no arguments on the command line .
Rather than implementing ` ` handle ( ) ` ` , subclasses must implement
` ` handle_noargs ( ) ` ` ; ` ` handle ( ) ` ` itself is overridden to ensure
no arguments are passed to the command .
Attempting to pass arguments will raise ` ` CommandError ` ` .
"""
2007-08-16 22:34:01 +08:00
args = ' '
def handle ( self , * args , * * options ) :
2007-10-14 02:45:49 +08:00
if args :
2007-08-16 22:34:01 +08:00
raise CommandError ( " Command doesn ' t accept any arguments " )
return self . handle_noargs ( * * options )
def handle_noargs ( self , * * options ) :
2008-09-22 14:03:24 +08:00
"""
Perform this command ' s actions.
"""
2007-08-16 22:34:01 +08:00
raise NotImplementedError ( )
2007-08-17 03:14:09 +08:00
def copy_helper ( style , app_or_project , name , directory , other_name = ' ' ) :
2007-10-14 00:38:19 +08:00
"""
Copies either a Django application layout template or a Django project
layout template into the specified directory .
2008-09-22 14:03:24 +08:00
2007-10-14 00:38:19 +08:00
"""
2007-11-30 13:13:05 +08:00
# style -- A color style object (see django.core.management.color).
# app_or_project -- The string 'app' or 'project'.
# name -- The name of the application or project.
# directory -- The directory to which the layout template should be copied.
# other_name -- When copying an application layout, this should be the name
# of the project.
2007-08-16 22:34:01 +08:00
import re
import shutil
other = { ' project ' : ' app ' , ' app ' : ' project ' } [ app_or_project ]
2008-09-16 13:39:49 +08:00
if not re . search ( r ' ^[_a-zA-Z] \ w*$ ' , name ) : # If it's not a valid directory name.
# Provide a smart error message, depending on the error.
if not re . search ( r ' ^[_a-zA-Z] ' , name ) :
message = ' make sure the name begins with a letter or underscore '
else :
message = ' use only numbers, letters and underscores '
raise CommandError ( " %r is not a valid %s name. Please %s . " % ( name , app_or_project , message ) )
2007-08-16 22:34:01 +08:00
top_dir = os . path . join ( directory , name )
try :
os . mkdir ( top_dir )
except OSError , e :
raise CommandError ( e )
# Determine where the app or project templates are. Use
# django.__path__[0] because we don't know into which directory
# django has been installed.
template_dir = os . path . join ( django . __path__ [ 0 ] , ' conf ' , ' %s _template ' % app_or_project )
for d , subdirs , files in os . walk ( template_dir ) :
relative_dir = d [ len ( template_dir ) + 1 : ] . replace ( ' %s _name ' % app_or_project , name )
if relative_dir :
os . mkdir ( os . path . join ( top_dir , relative_dir ) )
for i , subdir in enumerate ( subdirs ) :
if subdir . startswith ( ' . ' ) :
del subdirs [ i ]
for f in files :
if f . endswith ( ' .pyc ' ) :
continue
path_old = os . path . join ( d , f )
path_new = os . path . join ( top_dir , relative_dir , f . replace ( ' %s _name ' % app_or_project , name ) )
fp_old = open ( path_old , ' r ' )
fp_new = open ( path_new , ' w ' )
fp_new . write ( fp_old . read ( ) . replace ( ' {{ %s _name }} ' % app_or_project , name ) . replace ( ' {{ %s _name }} ' % other , other_name ) )
fp_old . close ( )
fp_new . close ( )
try :
shutil . copymode ( path_old , path_new )
2007-08-31 12:35:03 +08:00
_make_writeable ( path_new )
2007-08-16 22:34:01 +08:00
except OSError :
2007-08-17 03:14:09 +08:00
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 ) )
2007-08-31 12:35:03 +08:00
def _make_writeable ( filename ) :
2008-09-22 14:03:24 +08:00
"""
Make sure that the file is writeable . Useful if our source is
read - only .
"""
2007-08-31 12:35:03 +08:00
import stat
2007-09-15 19:19:50 +08:00
if sys . platform . startswith ( ' java ' ) :
# On Jython there is no os.access()
return
2007-08-31 12:35:03 +08:00
if not os . access ( filename , os . W_OK ) :
2007-09-15 19:19:50 +08:00
st = os . stat ( filename )
new_permissions = stat . S_IMODE ( st . st_mode ) | stat . S_IWUSR
os . chmod ( filename , new_permissions )