root / snf-webproject / synnefo / webproject / manage.py @ a868c831
History | View | Annotate | Download (12.1 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, \ |
52 |
BaseCommand, LaxOptionParser, handle_default_options, find_commands, \ |
53 |
load_command_class
|
54 |
|
55 |
from django.core import management |
56 |
from optparse import make_option |
57 |
from synnefo.util.version import get_component_version |
58 |
from synnefo.lib.dictconfig import dictConfig |
59 |
|
60 |
import sys |
61 |
import locale |
62 |
import os |
63 |
import imp |
64 |
|
65 |
_commands = None
|
66 |
|
67 |
|
68 |
def find_modules(name, path=None): |
69 |
"""Find all modules with name 'name'
|
70 |
|
71 |
Unlike find_module in the imp package this returns a list of all
|
72 |
matched modules.
|
73 |
"""
|
74 |
|
75 |
results = [] |
76 |
if path is None: |
77 |
path = sys.path |
78 |
for p in path: |
79 |
importer = sys.path_importer_cache.get(p, None)
|
80 |
if importer is None: |
81 |
find_module = imp.find_module |
82 |
else:
|
83 |
find_module = importer.find_module |
84 |
|
85 |
try:
|
86 |
result = find_module(name, [p]) |
87 |
if result is not None: |
88 |
results.append(result) |
89 |
except ImportError: |
90 |
if sys.modules.get(name, None): |
91 |
modpath = sys.modules[name].__path__ |
92 |
if isinstance(modpath, basestring) \ |
93 |
and not ('', modpath) in results: |
94 |
results.append(('', sys.modules[name].__path__))
|
95 |
else:
|
96 |
for mp in modpath: |
97 |
if not ('', mp) in results: |
98 |
results.append(('', mp))
|
99 |
pass
|
100 |
|
101 |
if not results: |
102 |
raise ImportError("No module named %.200s" % name) |
103 |
|
104 |
return results
|
105 |
|
106 |
|
107 |
def find_management_module(app_name): |
108 |
"""
|
109 |
Determines the path to the management module for the given app_name,
|
110 |
without actually importing the application or the management module.
|
111 |
|
112 |
Raises ImportError if the management module cannot be found for any reason.
|
113 |
"""
|
114 |
parts = app_name.split('.')
|
115 |
parts.append('management')
|
116 |
parts.reverse() |
117 |
part = parts.pop() |
118 |
paths = None
|
119 |
|
120 |
# When using manage.py, the project module is added to the path,
|
121 |
# loaded, then removed from the path. This means that
|
122 |
# testproject.testapp.models can be loaded in future, even if
|
123 |
# testproject isn't in the path. When looking for the management
|
124 |
# module, we need look for the case where the project name is part
|
125 |
# of the app_name but the project directory itself isn't on the path.
|
126 |
try:
|
127 |
modules = find_modules(part, paths) |
128 |
paths = [m[1] for m in modules] |
129 |
except ImportError: |
130 |
if os.path.basename(os.getcwd()) != part:
|
131 |
raise
|
132 |
|
133 |
while parts:
|
134 |
part = parts.pop() |
135 |
modules = find_modules(part, paths) |
136 |
paths = [m[1] for m in modules] |
137 |
return paths[0] |
138 |
|
139 |
|
140 |
def get_commands(): |
141 |
"""
|
142 |
Returns a dictionary mapping command names to their callback applications.
|
143 |
|
144 |
This works by looking for a management.commands package in django.core, and
|
145 |
in each installed application -- if a commands package exists, all commands
|
146 |
in that package are registered.
|
147 |
|
148 |
Core commands are always included. If a settings module has been
|
149 |
specified, user-defined commands will also be included, the
|
150 |
startproject command will be disabled, and the startapp command
|
151 |
will be modified to use the directory in which the settings module appears.
|
152 |
|
153 |
The dictionary is in the format {command_name: app_name}. Key-value
|
154 |
pairs from this dictionary can then be used in calls to
|
155 |
load_command_class(app_name, command_name)
|
156 |
|
157 |
If a specific version of a command must be loaded (e.g., with the
|
158 |
startapp command), the instantiated module can be placed in the
|
159 |
dictionary in place of the application name.
|
160 |
|
161 |
The dictionary is cached on the first call and reused on subsequent
|
162 |
calls.
|
163 |
"""
|
164 |
global _commands
|
165 |
if _commands is None: |
166 |
_commands = dict([(name, 'django.core') for name in |
167 |
find_commands(management.__path__[0])])
|
168 |
|
169 |
# Find the installed apps
|
170 |
try:
|
171 |
from django.conf import settings |
172 |
apps = settings.INSTALLED_APPS |
173 |
except (AttributeError, EnvironmentError, ImportError): |
174 |
apps = [] |
175 |
|
176 |
# Find and load the management module for each installed app.
|
177 |
for app_name in apps: |
178 |
try:
|
179 |
path = find_management_module(app_name) |
180 |
_commands.update(dict([(name, app_name)
|
181 |
for name in find_commands(path)])) |
182 |
except ImportError: |
183 |
pass # No management module - ignore this app |
184 |
|
185 |
if apps:
|
186 |
# Remove the "startproject" command from self.commands, because
|
187 |
# that's a django-admin.py command, not a manage.py command.
|
188 |
del _commands['startproject'] |
189 |
|
190 |
return _commands
|
191 |
|
192 |
|
193 |
class SynnefoManagementUtility(ManagementUtility): |
194 |
"""
|
195 |
Override django ManagementUtility to allow us provide a custom
|
196 |
--settings-dir option for synnefo application.
|
197 |
|
198 |
Most of the following code is a copy from django.core.management module
|
199 |
"""
|
200 |
|
201 |
def execute(self): |
202 |
"""
|
203 |
Given the command-line arguments, this figures out which subcommand is
|
204 |
being run, creates a parser appropriate to that command, and runs it.
|
205 |
"""
|
206 |
|
207 |
# --settings-dir option
|
208 |
# will remove it later to avoid django commands from raising errors
|
209 |
option_list = BaseCommand.option_list + ( |
210 |
make_option( |
211 |
'--settings-dir',
|
212 |
action='store',
|
213 |
dest='settings_dir',
|
214 |
default=None,
|
215 |
help='Load *.conf files from directory as settings'),)
|
216 |
|
217 |
# Preprocess options to extract --settings and --pythonpath.
|
218 |
# These options could affect the commands that are available, so they
|
219 |
# must be processed early.
|
220 |
parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
|
221 |
version=get_component_version('webproject'),
|
222 |
option_list=option_list) |
223 |
self.autocomplete()
|
224 |
try:
|
225 |
options, args = parser.parse_args(self.argv)
|
226 |
handle_default_options(options) |
227 |
except:
|
228 |
pass # Ignore any option errors at this point. |
229 |
|
230 |
# user provides custom settings dir
|
231 |
# set it as environmental variable and remove it from self.argv
|
232 |
if options.settings_dir:
|
233 |
os.environ['SYNNEFO_SETTINGS_DIR'] = options.settings_dir
|
234 |
for arg in self.argv: |
235 |
if arg.startswith('--settings-dir'): |
236 |
self.argv.remove(arg)
|
237 |
|
238 |
try:
|
239 |
subcommand = self.argv[1] |
240 |
except IndexError: |
241 |
subcommand = 'help' # Display help if no arguments were given. |
242 |
|
243 |
# Encode stdout. This check is required because of the way python
|
244 |
# checks if something is tty:
|
245 |
# https://bugzilla.redhat.com/show_bug.cgi?id=841152
|
246 |
if not subcommand in ['test'] and not 'shell' in subcommand: |
247 |
sys.stdout = EncodedStdOut(sys.stdout) |
248 |
|
249 |
if subcommand == 'help': |
250 |
if len(args) > 2: |
251 |
self.fetch_command(args[2]).print_help(self.prog_name, args[2]) |
252 |
else:
|
253 |
parser.print_lax_help() |
254 |
sys.stdout.write(self.main_help_text() + '\n') |
255 |
sys.exit(1)
|
256 |
# Special-cases: We want 'django-admin.py --version' and
|
257 |
# 'django-admin.py --help' to work, for backwards compatibility.
|
258 |
elif self.argv[1:] == ['--version']: |
259 |
# LaxOptionParser already takes care of printing the version.
|
260 |
pass
|
261 |
elif self.argv[1:] in (['--help'], ['-h']): |
262 |
parser.print_lax_help() |
263 |
sys.stdout.write(self.main_help_text() + '\n') |
264 |
else:
|
265 |
self.fetch_command(subcommand).run_from_argv(self.argv) |
266 |
|
267 |
def main_help_text(self): |
268 |
"""
|
269 |
Returns the script's main help text, as a string.
|
270 |
"""
|
271 |
usage = ['', ("Type '%s help <subcommand>' for help" |
272 |
"on a specific subcommand.") % self.prog_name, ''] |
273 |
usage.append('Available subcommands:')
|
274 |
commands = get_commands().keys() |
275 |
commands.sort() |
276 |
for cmd in commands: |
277 |
usage.append(' %s' % cmd)
|
278 |
return '\n'.join(usage) |
279 |
|
280 |
def fetch_command(self, subcommand): |
281 |
"""
|
282 |
Tries to fetch the given subcommand, printing a message with the
|
283 |
appropriate command called from the command line (usually
|
284 |
"django-admin.py" or "manage.py") if it can't be found.
|
285 |
"""
|
286 |
try:
|
287 |
app_name = get_commands()[subcommand] |
288 |
except KeyError: |
289 |
sys.stderr.write(("Unknown command: %r\n"
|
290 |
"Type '%s help' for usage.\n") %
|
291 |
(subcommand, self.prog_name))
|
292 |
sys.exit(1)
|
293 |
if isinstance(app_name, BaseCommand): |
294 |
# If the command is already loaded, use it directly.
|
295 |
klass = app_name |
296 |
else:
|
297 |
klass = load_command_class(app_name, subcommand) |
298 |
return klass
|
299 |
|
300 |
|
301 |
def configure_logging(): |
302 |
try:
|
303 |
from synnefo.settings import SNF_MANAGE_LOGGING_SETUP |
304 |
dictConfig(SNF_MANAGE_LOGGING_SETUP) |
305 |
except ImportError: |
306 |
import logging |
307 |
logging.basicConfig() |
308 |
log = logging.getLogger() |
309 |
log.warning("SNF_MANAGE_LOGGING_SETUP setting missing.")
|
310 |
|
311 |
|
312 |
class EncodedStdOut(object): |
313 |
def __init__(self, stdout): |
314 |
try:
|
315 |
std_encoding = stdout.encoding |
316 |
except AttributeError: |
317 |
std_encoding = None
|
318 |
self.encoding = std_encoding or locale.getpreferredencoding() |
319 |
self.original_stdout = stdout
|
320 |
|
321 |
def write(self, string): |
322 |
if isinstance(string, unicode): |
323 |
string = string.encode(self.encoding)
|
324 |
self.original_stdout.write(string)
|
325 |
|
326 |
def __getattr__(self, name): |
327 |
return getattr(self.original_stdout, name) |
328 |
|
329 |
|
330 |
def main(): |
331 |
os.environ['DJANGO_SETTINGS_MODULE'] = \
|
332 |
os.environ.get('DJANGO_SETTINGS_MODULE', 'synnefo.settings') |
333 |
configure_logging() |
334 |
mu = SynnefoManagementUtility(sys.argv) |
335 |
mu.execute() |
336 |
|
337 |
if __name__ == "__main__": |
338 |
main() |