|
0
|
1 |
import os |
|
|
2 |
import sys |
|
|
3 |
from optparse import OptionParser, NO_DEFAULT |
|
|
4 |
import imp |
|
|
5 |
|
|
|
6 |
import django |
|
|
7 |
from django.core.management.base import BaseCommand, CommandError, handle_default_options |
|
|
8 |
from django.utils.importlib import import_module |
|
|
9 |
|
|
|
10 |
# For backwards compatibility: get_version() used to be in this module. |
|
|
11 |
get_version = django.get_version |
|
|
12 |
|
|
|
13 |
# A cache of loaded commands, so that call_command |
|
|
14 |
# doesn't have to reload every time it's called. |
|
|
15 |
_commands = None |
|
|
16 |
|
|
|
17 |
def find_commands(management_dir): |
|
|
18 |
""" |
|
|
19 |
Given a path to a management directory, returns a list of all the command |
|
|
20 |
names that are available. |
|
|
21 |
|
|
|
22 |
Returns an empty list if no commands are defined. |
|
|
23 |
""" |
|
|
24 |
command_dir = os.path.join(management_dir, 'commands') |
|
|
25 |
try: |
|
|
26 |
return [f[:-3] for f in os.listdir(command_dir) |
|
|
27 |
if not f.startswith('_') and f.endswith('.py')] |
|
|
28 |
except OSError: |
|
|
29 |
return [] |
|
|
30 |
|
|
|
31 |
def find_management_module(app_name): |
|
|
32 |
""" |
|
|
33 |
Determines the path to the management module for the given app_name, |
|
|
34 |
without actually importing the application or the management module. |
|
|
35 |
|
|
|
36 |
Raises ImportError if the management module cannot be found for any reason. |
|
|
37 |
""" |
|
|
38 |
parts = app_name.split('.') |
|
|
39 |
parts.append('management') |
|
|
40 |
parts.reverse() |
|
|
41 |
part = parts.pop() |
|
|
42 |
path = None |
|
|
43 |
|
|
|
44 |
# When using manage.py, the project module is added to the path, |
|
|
45 |
# loaded, then removed from the path. This means that |
|
|
46 |
# testproject.testapp.models can be loaded in future, even if |
|
|
47 |
# testproject isn't in the path. When looking for the management |
|
|
48 |
# module, we need look for the case where the project name is part |
|
|
49 |
# of the app_name but the project directory itself isn't on the path. |
|
|
50 |
try: |
|
|
51 |
f, path, descr = imp.find_module(part,path) |
|
|
52 |
except ImportError,e: |
|
|
53 |
if os.path.basename(os.getcwd()) != part: |
|
|
54 |
raise e |
|
|
55 |
|
|
|
56 |
while parts: |
|
|
57 |
part = parts.pop() |
|
|
58 |
f, path, descr = imp.find_module(part, path and [path] or None) |
|
|
59 |
return path |
|
|
60 |
|
|
|
61 |
def load_command_class(app_name, name): |
|
|
62 |
""" |
|
|
63 |
Given a command name and an application name, returns the Command |
|
|
64 |
class instance. All errors raised by the import process |
|
|
65 |
(ImportError, AttributeError) are allowed to propagate. |
|
|
66 |
""" |
|
|
67 |
module = import_module('%s.management.commands.%s' % (app_name, name)) |
|
|
68 |
return module.Command() |
|
|
69 |
|
|
|
70 |
def get_commands(): |
|
|
71 |
""" |
|
|
72 |
Returns a dictionary mapping command names to their callback applications. |
|
|
73 |
|
|
|
74 |
This works by looking for a management.commands package in django.core, and |
|
|
75 |
in each installed application -- if a commands package exists, all commands |
|
|
76 |
in that package are registered. |
|
|
77 |
|
|
|
78 |
Core commands are always included. If a settings module has been |
|
|
79 |
specified, user-defined commands will also be included, the |
|
|
80 |
startproject command will be disabled, and the startapp command |
|
|
81 |
will be modified to use the directory in which the settings module appears. |
|
|
82 |
|
|
|
83 |
The dictionary is in the format {command_name: app_name}. Key-value |
|
|
84 |
pairs from this dictionary can then be used in calls to |
|
|
85 |
load_command_class(app_name, command_name) |
|
|
86 |
|
|
|
87 |
If a specific version of a command must be loaded (e.g., with the |
|
|
88 |
startapp command), the instantiated module can be placed in the |
|
|
89 |
dictionary in place of the application name. |
|
|
90 |
|
|
|
91 |
The dictionary is cached on the first call and reused on subsequent |
|
|
92 |
calls. |
|
|
93 |
""" |
|
|
94 |
global _commands |
|
|
95 |
if _commands is None: |
|
|
96 |
_commands = dict([(name, 'django.core') for name in find_commands(__path__[0])]) |
|
|
97 |
|
|
|
98 |
# Find the installed apps |
|
|
99 |
try: |
|
|
100 |
from django.conf import settings |
|
|
101 |
apps = settings.INSTALLED_APPS |
|
|
102 |
except (AttributeError, EnvironmentError, ImportError): |
|
|
103 |
apps = [] |
|
|
104 |
|
|
|
105 |
# Find the project directory |
|
|
106 |
try: |
|
|
107 |
from django.conf import settings |
|
|
108 |
module = import_module(settings.SETTINGS_MODULE) |
|
|
109 |
project_directory = setup_environ(module, settings.SETTINGS_MODULE) |
|
|
110 |
except (AttributeError, EnvironmentError, ImportError, KeyError): |
|
|
111 |
project_directory = None |
|
|
112 |
|
|
|
113 |
# Find and load the management module for each installed app. |
|
|
114 |
for app_name in apps: |
|
|
115 |
try: |
|
|
116 |
path = find_management_module(app_name) |
|
|
117 |
_commands.update(dict([(name, app_name) |
|
|
118 |
for name in find_commands(path)])) |
|
|
119 |
except ImportError: |
|
|
120 |
pass # No management module - ignore this app |
|
|
121 |
|
|
|
122 |
if project_directory: |
|
|
123 |
# Remove the "startproject" command from self.commands, because |
|
|
124 |
# that's a django-admin.py command, not a manage.py command. |
|
|
125 |
del _commands['startproject'] |
|
|
126 |
|
|
|
127 |
# Override the startapp command so that it always uses the |
|
|
128 |
# project_directory, not the current working directory |
|
|
129 |
# (which is default). |
|
|
130 |
from django.core.management.commands.startapp import ProjectCommand |
|
|
131 |
_commands['startapp'] = ProjectCommand(project_directory) |
|
|
132 |
|
|
|
133 |
return _commands |
|
|
134 |
|
|
|
135 |
def call_command(name, *args, **options): |
|
|
136 |
""" |
|
|
137 |
Calls the given command, with the given options and args/kwargs. |
|
|
138 |
|
|
|
139 |
This is the primary API you should use for calling specific commands. |
|
|
140 |
|
|
|
141 |
Some examples: |
|
|
142 |
call_command('syncdb') |
|
|
143 |
call_command('shell', plain=True) |
|
|
144 |
call_command('sqlall', 'myapp') |
|
|
145 |
""" |
|
|
146 |
# Load the command object. |
|
|
147 |
try: |
|
|
148 |
app_name = get_commands()[name] |
|
|
149 |
if isinstance(app_name, BaseCommand): |
|
|
150 |
# If the command is already loaded, use it directly. |
|
|
151 |
klass = app_name |
|
|
152 |
else: |
|
|
153 |
klass = load_command_class(app_name, name) |
|
|
154 |
except KeyError: |
|
|
155 |
raise CommandError, "Unknown command: %r" % name |
|
|
156 |
|
|
|
157 |
# Grab out a list of defaults from the options. optparse does this for us |
|
|
158 |
# when the script runs from the command line, but since call_command can |
|
|
159 |
# be called programatically, we need to simulate the loading and handling |
|
|
160 |
# of defaults (see #10080 for details). |
|
|
161 |
defaults = dict([(o.dest, o.default) |
|
|
162 |
for o in klass.option_list |
|
|
163 |
if o.default is not NO_DEFAULT]) |
|
|
164 |
defaults.update(options) |
|
|
165 |
|
|
|
166 |
return klass.execute(*args, **defaults) |
|
|
167 |
|
|
|
168 |
class LaxOptionParser(OptionParser): |
|
|
169 |
""" |
|
|
170 |
An option parser that doesn't raise any errors on unknown options. |
|
|
171 |
|
|
|
172 |
This is needed because the --settings and --pythonpath options affect |
|
|
173 |
the commands (and thus the options) that are available to the user. |
|
|
174 |
""" |
|
|
175 |
def error(self, msg): |
|
|
176 |
pass |
|
|
177 |
|
|
|
178 |
def print_help(self): |
|
|
179 |
"""Output nothing. |
|
|
180 |
|
|
|
181 |
The lax options are included in the normal option parser, so under |
|
|
182 |
normal usage, we don't need to print the lax options. |
|
|
183 |
""" |
|
|
184 |
pass |
|
|
185 |
|
|
|
186 |
def print_lax_help(self): |
|
|
187 |
"""Output the basic options available to every command. |
|
|
188 |
|
|
|
189 |
This just redirects to the default print_help() behaviour. |
|
|
190 |
""" |
|
|
191 |
OptionParser.print_help(self) |
|
|
192 |
|
|
|
193 |
def _process_args(self, largs, rargs, values): |
|
|
194 |
""" |
|
|
195 |
Overrides OptionParser._process_args to exclusively handle default |
|
|
196 |
options and ignore args and other options. |
|
|
197 |
|
|
|
198 |
This overrides the behavior of the super class, which stop parsing |
|
|
199 |
at the first unrecognized option. |
|
|
200 |
""" |
|
|
201 |
while rargs: |
|
|
202 |
arg = rargs[0] |
|
|
203 |
try: |
|
|
204 |
if arg[0:2] == "--" and len(arg) > 2: |
|
|
205 |
# process a single long option (possibly with value(s)) |
|
|
206 |
# the superclass code pops the arg off rargs |
|
|
207 |
self._process_long_opt(rargs, values) |
|
|
208 |
elif arg[:1] == "-" and len(arg) > 1: |
|
|
209 |
# process a cluster of short options (possibly with |
|
|
210 |
# value(s) for the last one only) |
|
|
211 |
# the superclass code pops the arg off rargs |
|
|
212 |
self._process_short_opts(rargs, values) |
|
|
213 |
else: |
|
|
214 |
# it's either a non-default option or an arg |
|
|
215 |
# either way, add it to the args list so we can keep |
|
|
216 |
# dealing with options |
|
|
217 |
del rargs[0] |
|
|
218 |
raise Exception |
|
|
219 |
except: |
|
|
220 |
largs.append(arg) |
|
|
221 |
|
|
|
222 |
class ManagementUtility(object): |
|
|
223 |
""" |
|
|
224 |
Encapsulates the logic of the django-admin.py and manage.py utilities. |
|
|
225 |
|
|
|
226 |
A ManagementUtility has a number of commands, which can be manipulated |
|
|
227 |
by editing the self.commands dictionary. |
|
|
228 |
""" |
|
|
229 |
def __init__(self, argv=None): |
|
|
230 |
self.argv = argv or sys.argv[:] |
|
|
231 |
self.prog_name = os.path.basename(self.argv[0]) |
|
|
232 |
|
|
|
233 |
def main_help_text(self): |
|
|
234 |
""" |
|
|
235 |
Returns the script's main help text, as a string. |
|
|
236 |
""" |
|
|
237 |
usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,''] |
|
|
238 |
usage.append('Available subcommands:') |
|
|
239 |
commands = get_commands().keys() |
|
|
240 |
commands.sort() |
|
|
241 |
for cmd in commands: |
|
|
242 |
usage.append(' %s' % cmd) |
|
|
243 |
return '\n'.join(usage) |
|
|
244 |
|
|
|
245 |
def fetch_command(self, subcommand): |
|
|
246 |
""" |
|
|
247 |
Tries to fetch the given subcommand, printing a message with the |
|
|
248 |
appropriate command called from the command line (usually |
|
|
249 |
"django-admin.py" or "manage.py") if it can't be found. |
|
|
250 |
""" |
|
|
251 |
try: |
|
|
252 |
app_name = get_commands()[subcommand] |
|
|
253 |
if isinstance(app_name, BaseCommand): |
|
|
254 |
# If the command is already loaded, use it directly. |
|
|
255 |
klass = app_name |
|
|
256 |
else: |
|
|
257 |
klass = load_command_class(app_name, subcommand) |
|
|
258 |
except KeyError: |
|
|
259 |
sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \ |
|
|
260 |
(subcommand, self.prog_name)) |
|
|
261 |
sys.exit(1) |
|
|
262 |
return klass |
|
|
263 |
|
|
|
264 |
def execute(self): |
|
|
265 |
""" |
|
|
266 |
Given the command-line arguments, this figures out which subcommand is |
|
|
267 |
being run, creates a parser appropriate to that command, and runs it. |
|
|
268 |
""" |
|
|
269 |
# Preprocess options to extract --settings and --pythonpath. |
|
|
270 |
# These options could affect the commands that are available, so they |
|
|
271 |
# must be processed early. |
|
|
272 |
parser = LaxOptionParser(usage="%prog subcommand [options] [args]", |
|
|
273 |
version=get_version(), |
|
|
274 |
option_list=BaseCommand.option_list) |
|
|
275 |
try: |
|
|
276 |
options, args = parser.parse_args(self.argv) |
|
|
277 |
handle_default_options(options) |
|
|
278 |
except: |
|
|
279 |
pass # Ignore any option errors at this point. |
|
|
280 |
|
|
|
281 |
try: |
|
|
282 |
subcommand = self.argv[1] |
|
|
283 |
except IndexError: |
|
|
284 |
sys.stderr.write("Type '%s help' for usage.\n" % self.prog_name) |
|
|
285 |
sys.exit(1) |
|
|
286 |
|
|
|
287 |
if subcommand == 'help': |
|
|
288 |
if len(args) > 2: |
|
|
289 |
self.fetch_command(args[2]).print_help(self.prog_name, args[2]) |
|
|
290 |
else: |
|
|
291 |
parser.print_lax_help() |
|
|
292 |
sys.stderr.write(self.main_help_text() + '\n') |
|
|
293 |
sys.exit(1) |
|
|
294 |
# Special-cases: We want 'django-admin.py --version' and |
|
|
295 |
# 'django-admin.py --help' to work, for backwards compatibility. |
|
|
296 |
elif self.argv[1:] == ['--version']: |
|
|
297 |
# LaxOptionParser already takes care of printing the version. |
|
|
298 |
pass |
|
|
299 |
elif self.argv[1:] == ['--help']: |
|
|
300 |
parser.print_lax_help() |
|
|
301 |
sys.stderr.write(self.main_help_text() + '\n') |
|
|
302 |
else: |
|
|
303 |
self.fetch_command(subcommand).run_from_argv(self.argv) |
|
|
304 |
|
|
|
305 |
def setup_environ(settings_mod, original_settings_path=None): |
|
|
306 |
""" |
|
|
307 |
Configures the runtime environment. This can also be used by external |
|
|
308 |
scripts wanting to set up a similar environment to manage.py. |
|
|
309 |
Returns the project directory (assuming the passed settings module is |
|
|
310 |
directly in the project directory). |
|
|
311 |
|
|
|
312 |
The "original_settings_path" parameter is optional, but recommended, since |
|
|
313 |
trying to work out the original path from the module can be problematic. |
|
|
314 |
""" |
|
|
315 |
# Add this project to sys.path so that it's importable in the conventional |
|
|
316 |
# way. For example, if this file (manage.py) lives in a directory |
|
|
317 |
# "myproject", this code would add "/path/to/myproject" to sys.path. |
|
|
318 |
if '__init__.py' in settings_mod.__file__: |
|
|
319 |
p = os.path.dirname(settings_mod.__file__) |
|
|
320 |
else: |
|
|
321 |
p = settings_mod.__file__ |
|
|
322 |
project_directory, settings_filename = os.path.split(p) |
|
|
323 |
if project_directory == os.curdir or not project_directory: |
|
|
324 |
project_directory = os.getcwd() |
|
|
325 |
project_name = os.path.basename(project_directory) |
|
|
326 |
|
|
|
327 |
# Strip filename suffix to get the module name. |
|
|
328 |
settings_name = os.path.splitext(settings_filename)[0] |
|
|
329 |
|
|
|
330 |
# Strip $py for Jython compiled files (like settings$py.class) |
|
|
331 |
if settings_name.endswith("$py"): |
|
|
332 |
settings_name = settings_name[:-3] |
|
|
333 |
|
|
|
334 |
# Set DJANGO_SETTINGS_MODULE appropriately. |
|
|
335 |
if original_settings_path: |
|
|
336 |
os.environ['DJANGO_SETTINGS_MODULE'] = original_settings_path |
|
|
337 |
else: |
|
|
338 |
os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name) |
|
|
339 |
|
|
|
340 |
# Import the project module. We add the parent directory to PYTHONPATH to |
|
|
341 |
# avoid some of the path errors new users can have. |
|
|
342 |
sys.path.append(os.path.join(project_directory, os.pardir)) |
|
|
343 |
project_module = import_module(project_name) |
|
|
344 |
sys.path.pop() |
|
|
345 |
|
|
|
346 |
return project_directory |
|
|
347 |
|
|
|
348 |
def execute_from_command_line(argv=None): |
|
|
349 |
""" |
|
|
350 |
A simple method that runs a ManagementUtility. |
|
|
351 |
""" |
|
|
352 |
utility = ManagementUtility(argv) |
|
|
353 |
utility.execute() |
|
|
354 |
|
|
|
355 |
def execute_manager(settings_mod, argv=None): |
|
|
356 |
""" |
|
|
357 |
Like execute_from_command_line(), but for use by manage.py, a |
|
|
358 |
project-specific django-admin.py utility. |
|
|
359 |
""" |
|
|
360 |
setup_environ(settings_mod) |
|
|
361 |
utility = ManagementUtility(argv) |
|
|
362 |
utility.execute() |