root / snf-webproject / synnefo / webproject / manage.py @ 4691814d
History | View | Annotate | Download (12.9 kB)
1 |
# Copyright 2011 GRNET S.A. All rights reserved.
|
---|---|
2 |
#
|
3 |
# Redistribution and use in source and binary forms, with or
|
4 |
# without modification, are permitted provided that the following
|
5 |
# conditions are met:
|
6 |
#
|
7 |
# 1. Redistributions of source code must retain the above
|
8 |
# copyright notice, this list of conditions and the following
|
9 |
# disclaimer.
|
10 |
#
|
11 |
# 2. Redistributions in binary form must reproduce the above
|
12 |
# copyright notice, this list of conditions and the following
|
13 |
# disclaimer in the documentation and/or other materials
|
14 |
# provided with the distribution.
|
15 |
#
|
16 |
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
|
17 |
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
18 |
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
19 |
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
|
20 |
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
21 |
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
22 |
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
|
23 |
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
24 |
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
25 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
26 |
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
27 |
# POSSIBILITY OF SUCH DAMAGE.
|
28 |
#
|
29 |
# The views and conclusions contained in the software and
|
30 |
# documentation are those of the authors and should not be
|
31 |
# interpreted as representing official policies, either expressed
|
32 |
# or implied, of GRNET S.A.
|
33 |
|
34 |
"""
|
35 |
Extented django management module
|
36 |
|
37 |
Most of the code is shared from django.core.management module
|
38 |
to allow us extend the default django ManagementUtility object
|
39 |
used to provide command line interface of the django project
|
40 |
included in snf-webproject package.
|
41 |
|
42 |
The extended class provides the following:
|
43 |
|
44 |
- additional argument for the configuration of the SYNNEFO_SETTINGS_DIR
|
45 |
environmental variable (--settings-dir).
|
46 |
- a fix for management utility to handle custom commands defined in
|
47 |
applications living in namespaced packages (django ticket #14087)
|
48 |
- override of --version command to display the snf-webproject version
|
49 |
"""
|
50 |
|
51 |
from django.core.management import ManagementUtility, setup_environ, \ |
52 |
BaseCommand, LaxOptionParser, handle_default_options, find_commands, \ |
53 |
load_command_class
|
54 |
|
55 |
from django.core import management |
56 |
from django.utils.importlib import import_module |
57 |
from optparse import make_option |
58 |
from synnefo.util.version import get_component_version |
59 |
from synnefo.lib.dictconfig import dictConfig |
60 |
|
61 |
import sys |
62 |
import locale |
63 |
import os |
64 |
import imp |
65 |
|
66 |
_commands = None
|
67 |
|
68 |
|
69 |
def find_modules(name, path=None): |
70 |
"""Find all modules with name 'name'
|
71 |
|
72 |
Unlike find_module in the imp package this returns a list of all
|
73 |
matched modules.
|
74 |
"""
|
75 |
|
76 |
results = [] |
77 |
if path is None: |
78 |
path = sys.path |
79 |
for p in path: |
80 |
importer = sys.path_importer_cache.get(p, None)
|
81 |
if importer is None: |
82 |
find_module = imp.find_module |
83 |
else:
|
84 |
find_module = importer.find_module |
85 |
|
86 |
try:
|
87 |
result = find_module(name, [p]) |
88 |
if result is not None: |
89 |
results.append(result) |
90 |
except ImportError: |
91 |
if sys.modules.get(name, None): |
92 |
modpath = sys.modules[name].__path__ |
93 |
if isinstance(modpath, basestring) \ |
94 |
and not ('', modpath) in results: |
95 |
results.append(('', sys.modules[name].__path__))
|
96 |
else:
|
97 |
for mp in modpath: |
98 |
if not ('', mp) in results: |
99 |
results.append(('', mp))
|
100 |
pass
|
101 |
|
102 |
if not results: |
103 |
raise ImportError("No module named %.200s" % name) |
104 |
|
105 |
return results
|
106 |
|
107 |
|
108 |
def find_management_module(app_name): |
109 |
"""
|
110 |
Determines the path to the management module for the given app_name,
|
111 |
without actually importing the application or the management module.
|
112 |
|
113 |
Raises ImportError if the management module cannot be found for any reason.
|
114 |
"""
|
115 |
parts = app_name.split('.')
|
116 |
parts.append('management')
|
117 |
parts.reverse() |
118 |
part = parts.pop() |
119 |
paths = None
|
120 |
|
121 |
# When using manage.py, the project module is added to the path,
|
122 |
# loaded, then removed from the path. This means that
|
123 |
# testproject.testapp.models can be loaded in future, even if
|
124 |
# testproject isn't in the path. When looking for the management
|
125 |
# module, we need look for the case where the project name is part
|
126 |
# of the app_name but the project directory itself isn't on the path.
|
127 |
try:
|
128 |
modules = find_modules(part, paths) |
129 |
paths = [m[1] for m in modules] |
130 |
except ImportError: |
131 |
if os.path.basename(os.getcwd()) != part:
|
132 |
raise
|
133 |
|
134 |
while parts:
|
135 |
part = parts.pop() |
136 |
modules = find_modules(part, paths) |
137 |
paths = [m[1] for m in modules] |
138 |
return paths[0] |
139 |
|
140 |
|
141 |
def get_commands(): |
142 |
"""
|
143 |
Returns a dictionary mapping command names to their callback applications.
|
144 |
|
145 |
This works by looking for a management.commands package in django.core, and
|
146 |
in each installed application -- if a commands package exists, all commands
|
147 |
in that package are registered.
|
148 |
|
149 |
Core commands are always included. If a settings module has been
|
150 |
specified, user-defined commands will also be included, the
|
151 |
startproject command will be disabled, and the startapp command
|
152 |
will be modified to use the directory in which the settings module appears.
|
153 |
|
154 |
The dictionary is in the format {command_name: app_name}. Key-value
|
155 |
pairs from this dictionary can then be used in calls to
|
156 |
load_command_class(app_name, command_name)
|
157 |
|
158 |
If a specific version of a command must be loaded (e.g., with the
|
159 |
startapp command), the instantiated module can be placed in the
|
160 |
dictionary in place of the application name.
|
161 |
|
162 |
The dictionary is cached on the first call and reused on subsequent
|
163 |
calls.
|
164 |
"""
|
165 |
global _commands
|
166 |
if _commands is None: |
167 |
_commands = dict([(name, 'django.core') for name in |
168 |
find_commands(management.__path__[0])])
|
169 |
|
170 |
# Find the installed apps
|
171 |
try:
|
172 |
from django.conf import settings |
173 |
apps = settings.INSTALLED_APPS |
174 |
except (AttributeError, EnvironmentError, ImportError): |
175 |
apps = [] |
176 |
|
177 |
# Find the project directory
|
178 |
try:
|
179 |
from django.conf import settings |
180 |
module = import_module(settings.SETTINGS_MODULE) |
181 |
project_directory = setup_environ(module, settings.SETTINGS_MODULE) |
182 |
except (AttributeError, EnvironmentError, ImportError, KeyError): |
183 |
project_directory = None
|
184 |
|
185 |
# Find and load the management module for each installed app.
|
186 |
for app_name in apps: |
187 |
try:
|
188 |
path = find_management_module(app_name) |
189 |
_commands.update(dict([(name, app_name)
|
190 |
for name in find_commands(path)])) |
191 |
except ImportError: |
192 |
pass # No management module - ignore this app |
193 |
|
194 |
if project_directory:
|
195 |
# Remove the "startproject" command from self.commands, because
|
196 |
# that's a django-admin.py command, not a manage.py command.
|
197 |
del _commands['startproject'] |
198 |
|
199 |
# Override the startapp command so that it always uses the
|
200 |
# project_directory, not the current working directory
|
201 |
# (which is default).
|
202 |
from django.core.management.commands.startapp import ProjectCommand |
203 |
_commands['startapp'] = ProjectCommand(project_directory)
|
204 |
|
205 |
return _commands
|
206 |
|
207 |
|
208 |
class SynnefoManagementUtility(ManagementUtility): |
209 |
"""
|
210 |
Override django ManagementUtility to allow us provide a custom
|
211 |
--settings-dir option for synnefo application.
|
212 |
|
213 |
Most of the following code is a copy from django.core.management module
|
214 |
"""
|
215 |
|
216 |
def execute(self): |
217 |
"""
|
218 |
Given the command-line arguments, this figures out which subcommand is
|
219 |
being run, creates a parser appropriate to that command, and runs it.
|
220 |
"""
|
221 |
|
222 |
# --settings-dir option
|
223 |
# will remove it later to avoid django commands from raising errors
|
224 |
option_list = BaseCommand.option_list + ( |
225 |
make_option( |
226 |
'--settings-dir',
|
227 |
action='store',
|
228 |
dest='settings_dir',
|
229 |
default=None,
|
230 |
help='Load *.conf files from directory as settings'),)
|
231 |
|
232 |
# Preprocess options to extract --settings and --pythonpath.
|
233 |
# These options could affect the commands that are available, so they
|
234 |
# must be processed early.
|
235 |
parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
|
236 |
version=get_component_version('webproject'),
|
237 |
option_list=option_list) |
238 |
self.autocomplete()
|
239 |
try:
|
240 |
options, args = parser.parse_args(self.argv)
|
241 |
handle_default_options(options) |
242 |
except:
|
243 |
pass # Ignore any option errors at this point. |
244 |
|
245 |
# user provides custom settings dir
|
246 |
# set it as environmental variable and remove it from self.argv
|
247 |
if options.settings_dir:
|
248 |
os.environ['SYNNEFO_SETTINGS_DIR'] = options.settings_dir
|
249 |
for arg in self.argv: |
250 |
if arg.startswith('--settings-dir'): |
251 |
self.argv.remove(arg)
|
252 |
|
253 |
try:
|
254 |
subcommand = self.argv[1] |
255 |
except IndexError: |
256 |
subcommand = 'help' # Display help if no arguments were given. |
257 |
|
258 |
# Encode stdout. This check is required because of the way python
|
259 |
# checks if something is tty:
|
260 |
# https://bugzilla.redhat.com/show_bug.cgi?id=841152
|
261 |
if not subcommand in ['test'] and not 'shell' in subcommand: |
262 |
sys.stdout = EncodedStdOut(sys.stdout) |
263 |
|
264 |
if subcommand == 'help': |
265 |
if len(args) > 2: |
266 |
self.fetch_command(args[2]).print_help(self.prog_name, args[2]) |
267 |
else:
|
268 |
parser.print_lax_help() |
269 |
sys.stdout.write(self.main_help_text() + '\n') |
270 |
sys.exit(1)
|
271 |
# Special-cases: We want 'django-admin.py --version' and
|
272 |
# 'django-admin.py --help' to work, for backwards compatibility.
|
273 |
elif self.argv[1:] == ['--version']: |
274 |
# LaxOptionParser already takes care of printing the version.
|
275 |
pass
|
276 |
elif self.argv[1:] in (['--help'], ['-h']): |
277 |
parser.print_lax_help() |
278 |
sys.stdout.write(self.main_help_text() + '\n') |
279 |
else:
|
280 |
self.fetch_command(subcommand).run_from_argv(self.argv) |
281 |
|
282 |
def main_help_text(self): |
283 |
"""
|
284 |
Returns the script's main help text, as a string.
|
285 |
"""
|
286 |
usage = ['', ("Type '%s help <subcommand>' for help" |
287 |
"on a specific subcommand.") % self.prog_name, ''] |
288 |
usage.append('Available subcommands:')
|
289 |
commands = get_commands().keys() |
290 |
commands.sort() |
291 |
for cmd in commands: |
292 |
usage.append(' %s' % cmd)
|
293 |
return '\n'.join(usage) |
294 |
|
295 |
def fetch_command(self, subcommand): |
296 |
"""
|
297 |
Tries to fetch the given subcommand, printing a message with the
|
298 |
appropriate command called from the command line (usually
|
299 |
"django-admin.py" or "manage.py") if it can't be found.
|
300 |
"""
|
301 |
try:
|
302 |
app_name = get_commands()[subcommand] |
303 |
except KeyError: |
304 |
sys.stderr.write(("Unknown command: %r\n"
|
305 |
"Type '%s help' for usage.\n") %
|
306 |
(subcommand, self.prog_name))
|
307 |
sys.exit(1)
|
308 |
if isinstance(app_name, BaseCommand): |
309 |
# If the command is already loaded, use it directly.
|
310 |
klass = app_name |
311 |
else:
|
312 |
klass = load_command_class(app_name, subcommand) |
313 |
return klass
|
314 |
|
315 |
|
316 |
def configure_logging(): |
317 |
try:
|
318 |
from synnefo.settings import SNF_MANAGE_LOGGING_SETUP |
319 |
dictConfig(SNF_MANAGE_LOGGING_SETUP) |
320 |
except ImportError: |
321 |
import logging |
322 |
logging.basicConfig() |
323 |
log = logging.getLogger() |
324 |
log.warning("SNF_MANAGE_LOGGING_SETUP setting missing.")
|
325 |
|
326 |
|
327 |
class EncodedStdOut(object): |
328 |
def __init__(self, stdout): |
329 |
try:
|
330 |
std_encoding = stdout.encoding |
331 |
except AttributeError: |
332 |
std_encoding = None
|
333 |
self.encoding = std_encoding or locale.getpreferredencoding() |
334 |
self.original_stdout = stdout
|
335 |
|
336 |
def write(self, string): |
337 |
if isinstance(string, unicode): |
338 |
string = string.encode(self.encoding)
|
339 |
self.original_stdout.write(string)
|
340 |
|
341 |
def __getattr__(self, name): |
342 |
return getattr(self.original_stdout, name) |
343 |
|
344 |
|
345 |
def main(): |
346 |
# no need to run setup_environ
|
347 |
# we already know our project
|
348 |
os.environ['DJANGO_SETTINGS_MODULE'] = \
|
349 |
os.environ.get('DJANGO_SETTINGS_MODULE', 'synnefo.settings') |
350 |
configure_logging() |
351 |
mu = SynnefoManagementUtility(sys.argv) |
352 |
mu.execute() |
353 |
|
354 |
if __name__ == "__main__": |
355 |
main() |