| 1 |
import os |
|---|
| 2 |
import sys |
|---|
| 3 |
from optparse import OptionParser |
|---|
| 4 |
import imp |
|---|
| 5 |
|
|---|
| 6 |
import django |
|---|
| 7 |
from django.core.management.base import BaseCommand, CommandError, handle_default_options |
|---|
| 8 |
|
|---|
| 9 |
# For backwards compatibility: get_version() used to be in this module. |
|---|
| 10 |
get_version = django.get_version |
|---|
| 11 |
|
|---|
| 12 |
# A cache of loaded commands, so that call_command |
|---|
| 13 |
# doesn't have to reload every time it's called. |
|---|
| 14 |
_commands = None |
|---|
| 15 |
|
|---|
| 16 |
def find_commands(management_dir): |
|---|
| 17 |
""" |
|---|
| 18 |
Given a path to a management directory, returns a list of all the command |
|---|
| 19 |
names that are available. |
|---|
| 20 |
|
|---|
| 21 |
Returns an empty list if no commands are defined. |
|---|
| 22 |
""" |
|---|
| 23 |
command_dir = os.path.join(management_dir, 'commands') |
|---|
| 24 |
try: |
|---|
| 25 |
return [f[:-3] for f in os.listdir(command_dir) |
|---|
| 26 |
if not f.startswith('_') and f.endswith('.py')] |
|---|
| 27 |
except OSError: |
|---|
| 28 |
return [] |
|---|
| 29 |
|
|---|
| 30 |
def find_management_module(app_name): |
|---|
| 31 |
""" |
|---|
| 32 |
Determines the path to the management module for the given app_name, |
|---|
| 33 |
without actually importing the application or the management module. |
|---|
| 34 |
|
|---|
| 35 |
Raises ImportError if the management module cannot be found for any reason. |
|---|
| 36 |
""" |
|---|
| 37 |
parts = app_name.split('.') |
|---|
| 38 |
parts.append('management') |
|---|
| 39 |
parts.reverse() |
|---|
| 40 |
part = parts.pop() |
|---|
| 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 |
|
|---|
| 55 |
while parts: |
|---|
| 56 |
part = parts.pop() |
|---|
| 57 |
f, path, descr = imp.find_module(part, path and [path] or None) |
|---|
| 58 |
return path |
|---|
| 59 |
|
|---|
| 60 |
def load_command_class(app_name, name): |
|---|
| 61 |
""" |
|---|
| 62 |
Given a command name and an application name, returns the Command |
|---|
| 63 |
class instance. All errors raised by the import process |
|---|
| 64 |
(ImportError, AttributeError) are allowed to propagate. |
|---|
| 65 |
""" |
|---|
| 66 |
return getattr(__import__('%s.management.commands.%s' % (app_name, name), |
|---|
| 67 |
{}, {}, ['Command']), 'Command')() |
|---|
| 68 |
|
|---|
| 69 |
def get_commands(): |
|---|
| 70 |
""" |
|---|
| 71 |
Returns a dictionary mapping command names to their callback applications. |
|---|
| 72 |
|
|---|
| 73 |
This works by looking for a management.commands package in django.core, and |
|---|
| 74 |
in each installed application -- if a commands package exists, all commands |
|---|
| 75 |
in that package are registered. |
|---|
| 76 |
|
|---|
| 77 |
Core commands are always included. If a settings module has been |
|---|
| 78 |
specified, user-defined commands will also be included, the |
|---|
| 79 |
startproject command will be disabled, and the startapp command |
|---|
| 80 |
will be modified to use the directory in which the settings module appears. |
|---|
| 81 |
|
|---|
| 82 |
The dictionary is in the format {command_name: app_name}. Key-value |
|---|
| 83 |
pairs from this dictionary can then be used in calls to |
|---|
| 84 |
load_command_class(app_name, command_name) |
|---|
| 85 |
|
|---|
| 86 |
If a specific version of a command must be loaded (e.g., with the |
|---|
| 87 |
startapp command), the instantiated module can be placed in the |
|---|
| 88 |
dictionary in place of the application name. |
|---|
| 89 |
|
|---|
| 90 |
The dictionary is cached on the first call and reused on subsequent |
|---|
| 91 |
calls. |
|---|
| 92 |
""" |
|---|
| 93 |
global _commands |
|---|
| 94 |
if _commands is None: |
|---|
| 95 |
_commands = dict([(name, 'django.core') for name in find_commands(__path__[0])]) |
|---|
| 96 |
|
|---|
| 97 |
# Find the installed apps |
|---|
| 98 |
try: |
|---|
| 99 |
from django.conf import settings |
|---|
| 100 |
apps = settings.INSTALLED_APPS |
|---|
| 101 |
except (AttributeError, EnvironmentError, ImportError): |
|---|
| 102 |
apps = [] |
|---|
| 103 |
|
|---|
| 104 |
# Find the project directory |
|---|
| 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 |
|---|
| 124 |
|
|---|
| 125 |
if project_directory: |
|---|
| 126 |
# Remove the "startproject" command from self.commands, because |
|---|
| 127 |
# that's a django-admin.py command, not a manage.py command. |
|---|
| 128 |
del _commands['startproject'] |
|---|
| 129 |
|
|---|
| 130 |
# Override the startapp command so that it always uses the |
|---|
| 131 |
# project_directory, not the current working directory |
|---|
| 132 |
# (which is default). |
|---|
| 133 |
from django.core.management.commands.startapp import ProjectCommand |
|---|
| 134 |
_commands['startapp'] = ProjectCommand(project_directory) |
|---|
| 135 |
|
|---|
| 136 |
return _commands |
|---|
| 137 |
|
|---|
| 138 |
def call_command(name, *args, **options): |
|---|
| 139 |
""" |
|---|
| 140 |
Calls the given command, with the given options and args/kwargs. |
|---|
| 141 |
|
|---|
| 142 |
This is the primary API you should use for calling specific commands. |
|---|
| 143 |
|
|---|
| 144 |
Some examples: |
|---|
| 145 |
call_command('syncdb') |
|---|
| 146 |
call_command('shell', plain=True) |
|---|
| 147 |
call_command('sqlall', 'myapp') |
|---|
| 148 |
""" |
|---|
| 149 |
try: |
|---|
| 150 |
app_name = get_commands()[name] |
|---|
| 151 |
if isinstance(app_name, BaseCommand): |
|---|
| 152 |
# If the command is already loaded, use it directly. |
|---|
| 153 |
klass = app_name |
|---|
| 154 |
else: |
|---|
| 155 |
klass = load_command_class(app_name, name) |
|---|
| 156 |
except KeyError: |
|---|
| 157 |
raise CommandError, "Unknown command: %r" % name |
|---|
| 158 |
return klass.execute(*args, **options) |
|---|
| 159 |
|
|---|
| 160 |
class LaxOptionParser(OptionParser): |
|---|
| 161 |
""" |
|---|
| 162 |
An option parser that doesn't raise any errors on unknown options. |
|---|
| 163 |
|
|---|
| 164 |
This is needed because the --settings and --pythonpath options affect |
|---|
| 165 |
the commands (and thus the options) that are available to the user. |
|---|
| 166 |
""" |
|---|
| 167 |
def error(self, msg): |
|---|
| 168 |
pass |
|---|
| 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 Exception |
|---|
| 211 |
except: |
|---|
| 212 |
largs.append(arg) |
|---|
| 213 |
|
|---|
| 214 |
class ManagementUtility(object): |
|---|
| 215 |
""" |
|---|
| 216 |
Encapsulates the logic of the django-admin.py and manage.py utilities. |
|---|
| 217 |
|
|---|
| 218 |
A ManagementUtility has a number of commands, which can be manipulated |
|---|
| 219 |
by editing the self.commands dictionary. |
|---|
| 220 |
""" |
|---|
| 221 |
def __init__(self, argv=None): |
|---|
| 222 |
self.argv = argv or sys.argv[:] |
|---|
| 223 |
self.prog_name = os.path.basename(self.argv[0]) |
|---|
| 224 |
|
|---|
| 225 |
def main_help_text(self): |
|---|
| 226 |
""" |
|---|
| 227 |
Returns the script's main help text, as a string. |
|---|
| 228 |
""" |
|---|
| 229 |
usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,''] |
|---|
| 230 |
usage.append('Available subcommands:') |
|---|
| 231 |
commands = get_commands().keys() |
|---|
| 232 |
commands.sort() |
|---|
| 233 |
for cmd in commands: |
|---|
| 234 |
usage.append(' %s' % cmd) |
|---|
| 235 |
return '\n'.join(usage) |
|---|
| 236 |
|
|---|
| 237 |
def fetch_command(self, subcommand): |
|---|
| 238 |
""" |
|---|
| 239 |
Tries to fetch the given subcommand, printing a message with the |
|---|
| 240 |
appropriate command called from the command line (usually |
|---|
| 241 |
"django-admin.py" or "manage.py") if it can't be found. |
|---|
| 242 |
""" |
|---|
| 243 |
try: |
|---|
| 244 |
app_name = get_commands()[subcommand] |
|---|
| 245 |
if isinstance(app_name, BaseCommand): |
|---|
| 246 |
# If the command is already loaded, use it directly. |
|---|
| 247 |
klass = app_name |
|---|
| 248 |
else: |
|---|
| 249 |
klass = load_command_class(app_name, subcommand) |
|---|
| 250 |
except KeyError: |
|---|
| 251 |
sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \ |
|---|
| 252 |
(subcommand, self.prog_name)) |
|---|
| 253 |
sys.exit(1) |
|---|
| 254 |
return klass |
|---|
| 255 |
|
|---|
| 256 |
def execute(self): |
|---|
| 257 |
""" |
|---|
| 258 |
Given the command-line arguments, this figures out which subcommand is |
|---|
| 259 |
being run, creates a parser appropriate to that command, and runs it. |
|---|
| 260 |
""" |
|---|
| 261 |
# Preprocess options to extract --settings and --pythonpath. |
|---|
| 262 |
# These options could affect the commands that are available, so they |
|---|
| 263 |
# must be processed early. |
|---|
| 264 |
parser = LaxOptionParser(usage="%prog subcommand [options] [args]", |
|---|
| 265 |
version=get_version(), |
|---|
| 266 |
option_list=BaseCommand.option_list) |
|---|
| 267 |
try: |
|---|
| 268 |
options, args = parser.parse_args(self.argv) |
|---|
| 269 |
handle_default_options(options) |
|---|
| 270 |
except: |
|---|
| 271 |
pass # Ignore any option errors at this point. |
|---|
| 272 |
|
|---|
| 273 |
try: |
|---|
| 274 |
subcommand = self.argv[1] |
|---|
| 275 |
except IndexError: |
|---|
| 276 |
sys.stderr.write("Type '%s help' for usage.\n" % self.prog_name) |
|---|
| 277 |
sys.exit(1) |
|---|
| 278 |
|
|---|
| 279 |
if subcommand == 'help': |
|---|
| 280 |
if len(args) > 2: |
|---|
| 281 |
self.fetch_command(args[2]).print_help(self.prog_name, args[2]) |
|---|
| 282 |
else: |
|---|
| 283 |
parser.print_lax_help() |
|---|
| 284 |
sys.stderr.write(self.main_help_text() + '\n') |
|---|
| 285 |
sys.exit(1) |
|---|
| 286 |
# Special-cases: We want 'django-admin.py --version' and |
|---|
| 287 |
# 'django-admin.py --help' to work, for backwards compatibility. |
|---|
| 288 |
elif self.argv[1:] == ['--version']: |
|---|
| 289 |
# LaxOptionParser already takes care of printing the version. |
|---|
| 290 |
pass |
|---|
| 291 |
elif self.argv[1:] == ['--help']: |
|---|
| 292 |
parser.print_lax_help() |
|---|
| 293 |
sys.stderr.write(self.main_help_text() + '\n') |
|---|
| 294 |
else: |
|---|
| 295 |
self.fetch_command(subcommand).run_from_argv(self.argv) |
|---|
| 296 |
|
|---|
| 297 |
def setup_environ(settings_mod, original_settings_path=None): |
|---|
| 298 |
""" |
|---|
| 299 |
Configures the runtime environment. This can also be used by external |
|---|
| 300 |
scripts wanting to set up a similar environment to manage.py. |
|---|
| 301 |
Returns the project directory (assuming the passed settings module is |
|---|
| 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. |
|---|
| 306 |
""" |
|---|
| 307 |
# Add this project to sys.path so that it's importable in the conventional |
|---|
| 308 |
# way. For example, if this file (manage.py) lives in a directory |
|---|
| 309 |
# "myproject", this code would add "/path/to/myproject" to sys.path. |
|---|
| 310 |
project_directory, settings_filename = os.path.split(settings_mod.__file__) |
|---|
| 311 |
if project_directory == os.curdir or not project_directory: |
|---|
| 312 |
project_directory = os.getcwd() |
|---|
| 313 |
project_name = os.path.basename(project_directory) |
|---|
| 314 |
settings_name = os.path.splitext(settings_filename)[0] |
|---|
| 315 |
sys.path.append(os.path.join(project_directory, os.pardir)) |
|---|
| 316 |
project_module = __import__(project_name, {}, {}, ['']) |
|---|
| 317 |
sys.path.pop() |
|---|
| 318 |
|
|---|
| 319 |
# Set DJANGO_SETTINGS_MODULE appropriately. |
|---|
| 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) |
|---|
| 324 |
return project_directory |
|---|
| 325 |
|
|---|
| 326 |
def execute_from_command_line(argv=None): |
|---|
| 327 |
""" |
|---|
| 328 |
A simple method that runs a ManagementUtility. |
|---|
| 329 |
""" |
|---|
| 330 |
utility = ManagementUtility(argv) |
|---|
| 331 |
utility.execute() |
|---|
| 332 |
|
|---|
| 333 |
def execute_manager(settings_mod, argv=None): |
|---|
| 334 |
""" |
|---|
| 335 |
Like execute_from_command_line(), but for use by manage.py, a |
|---|
| 336 |
project-specific django-admin.py utility. |
|---|
| 337 |
""" |
|---|
| 338 |
setup_environ(settings_mod) |
|---|
| 339 |
utility = ManagementUtility(argv) |
|---|
| 340 |
utility.execute() |
|---|