Statistics
| Branch: | Tag: | Revision:

root / snf-webproject / synnefo / webproject / manage.py @ 1f0030e6

History | View | Annotate | Download (12.8 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 Option, 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: 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) and not ('', modpath) in results:
93
                    results.append(('', sys.modules[name].__path__))
94
                else:
95
                    for mp in modpath:
96
                        if not ('', mp) in results:
97
                            results.append(('', mp))
98
            pass
99

    
100
    if not results:
101
        raise ImportError("No module named %.200s" % name)
102

    
103
    return results
104

    
105
def find_management_module(app_name):
106
    """
107
    Determines the path to the management module for the given app_name,
108
    without actually importing the application or the management module.
109

110
    Raises ImportError if the management module cannot be found for any reason.
111
    """
112
    parts = app_name.split('.')
113
    parts.append('management')
114
    parts.reverse()
115
    part = parts.pop()
116
    paths = None
117

    
118
    # When using manage.py, the project module is added to the path,
119
    # loaded, then removed from the path. This means that
120
    # testproject.testapp.models can be loaded in future, even if
121
    # testproject isn't in the path. When looking for the management
122
    # module, we need look for the case where the project name is part
123
    # of the app_name but the project directory itself isn't on the path.
124
    try:
125
        modules = find_modules(part, paths)
126
        paths = [m[1] for m in modules]
127
    except ImportError,e:
128
        if os.path.basename(os.getcwd()) != part:
129
            raise e
130

    
131
    while parts:
132
        part = parts.pop()
133
        modules = find_modules(part, paths)
134
        paths = [m[1] for m in modules]
135
    return paths[0]
136

    
137

    
138
def get_commands():
139
    """
140
    Returns a dictionary mapping command names to their callback applications.
141

142
    This works by looking for a management.commands package in django.core, and
143
    in each installed application -- if a commands package exists, all commands
144
    in that package are registered.
145

146
    Core commands are always included. If a settings module has been
147
    specified, user-defined commands will also be included, the
148
    startproject command will be disabled, and the startapp command
149
    will be modified to use the directory in which the settings module appears.
150

151
    The dictionary is in the format {command_name: app_name}. Key-value
152
    pairs from this dictionary can then be used in calls to
153
    load_command_class(app_name, command_name)
154

155
    If a specific version of a command must be loaded (e.g., with the
156
    startapp command), the instantiated module can be placed in the
157
    dictionary in place of the application name.
158

159
    The dictionary is cached on the first call and reused on subsequent
160
    calls.
161
    """
162
    global _commands
163
    if _commands is None:
164
        _commands = dict([(name, 'django.core') for name in \
165
            find_commands(management.__path__[0])])
166

    
167
        # Find the installed apps
168
        try:
169
            from django.conf import settings
170
            apps = settings.INSTALLED_APPS
171
        except (AttributeError, EnvironmentError, ImportError):
172
            apps = []
173

    
174
        # Find the project directory
175
        try:
176
            from django.conf import settings
177
            module = import_module(settings.SETTINGS_MODULE)
178
            project_directory = setup_environ(module, settings.SETTINGS_MODULE)
179
        except (AttributeError, EnvironmentError, ImportError, KeyError):
180
            project_directory = None
181

    
182
        # Find and load the management module for each installed app.
183
        for app_name in apps:
184
            try:
185
                path = find_management_module(app_name)
186
                _commands.update(dict([(name, app_name)
187
                                       for name in find_commands(path)]))
188
            except ImportError:
189
                pass # No management module - ignore this app
190

    
191
        if project_directory:
192
            # Remove the "startproject" command from self.commands, because
193
            # that's a django-admin.py command, not a manage.py command.
194
            del _commands['startproject']
195

    
196
            # Override the startapp command so that it always uses the
197
            # project_directory, not the current working directory
198
            # (which is default).
199
            from django.core.management.commands.startapp import ProjectCommand
200
            _commands['startapp'] = ProjectCommand(project_directory)
201

    
202
    return _commands
203

    
204
class SynnefoManagementUtility(ManagementUtility):
205
    """
206
    Override django ManagementUtility to allow us provide a custom
207
    --settings-dir option for synnefo application.
208

209
    Most of the following code is a copy from django.core.management module
210
    """
211

    
212
    def execute(self):
213
        """
214
        Given the command-line arguments, this figures out which subcommand is
215
        being run, creates a parser appropriate to that command, and runs it.
216
        """
217

    
218
        # --settings-dir option
219
        # will remove it later to avoid django commands from raising errors
220
        option_list = BaseCommand.option_list + (
221
            make_option('--settings-dir',
222
                action='store',
223
                dest='settings_dir',
224
                default=None,
225
                help='Load *.conf files from directory as settings'),)
226

    
227
        # Preprocess options to extract --settings and --pythonpath.
228
        # These options could affect the commands that are available, so they
229
        # must be processed early.
230
        parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
231
                                 version=get_component_version('webproject'),
232
                                 option_list=option_list)
233
        self.autocomplete()
234
        try:
235
            options, args = parser.parse_args(self.argv)
236
            handle_default_options(options)
237
        except:
238
            pass # Ignore any option errors at this point.
239

    
240
        # user provides custom settings dir
241
        # set it as environmental variable and remove it from self.argv
242
        if options.settings_dir:
243
            os.environ['SYNNEFO_SETTINGS_DIR'] = options.settings_dir
244
            for arg in self.argv:
245
                if arg.startswith('--settings-dir'):
246
                    self.argv.remove(arg)
247

    
248
        try:
249
            subcommand = self.argv[1]
250
        except IndexError:
251
            subcommand = 'help' # Display help if no arguments were given.
252

    
253
        # Encode stdout. This check is required because of the way python
254
        # checks if something is tty: https://bugzilla.redhat.com/show_bug.cgi?id=841152
255
        if not subcommand in ['test'] and not 'shell' in subcommand:
256
            sys.stdout = EncodedStdOut(sys.stdout)
257

    
258
        if subcommand == 'help':
259
            if len(args) > 2:
260
                self.fetch_command(args[2]).print_help(self.prog_name, args[2])
261
            else:
262
                parser.print_lax_help()
263
                sys.stdout.write(self.main_help_text() + '\n')
264
                sys.exit(1)
265
        # Special-cases: We want 'django-admin.py --version' and
266
        # 'django-admin.py --help' to work, for backwards compatibility.
267
        elif self.argv[1:] == ['--version']:
268
            # LaxOptionParser already takes care of printing the version.
269
            pass
270
        elif self.argv[1:] in (['--help'], ['-h']):
271
            parser.print_lax_help()
272
            sys.stdout.write(self.main_help_text() + '\n')
273
        else:
274
            self.fetch_command(subcommand).run_from_argv(self.argv)
275

    
276
    def main_help_text(self):
277
        """
278
        Returns the script's main help text, as a string.
279
        """
280
        usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,'']
281
        usage.append('Available subcommands:')
282
        commands = get_commands().keys()
283
        commands.sort()
284
        for cmd in commands:
285
            usage.append('  %s' % cmd)
286
        return '\n'.join(usage)
287

    
288
    def fetch_command(self, subcommand):
289
        """
290
        Tries to fetch the given subcommand, printing a message with the
291
        appropriate command called from the command line (usually
292
        "django-admin.py" or "manage.py") if it can't be found.
293
        """
294
        try:
295
            app_name = get_commands()[subcommand]
296
        except KeyError:
297
            sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \
298
                (subcommand, self.prog_name))
299
            sys.exit(1)
300
        if isinstance(app_name, BaseCommand):
301
            # If the command is already loaded, use it directly.
302
            klass = app_name
303
        else:
304
            klass = load_command_class(app_name, subcommand)
305
        return klass
306

    
307

    
308
def configure_logging():
309
    try:
310
        from synnefo.settings import SNF_MANAGE_LOGGING_SETUP
311
        dictConfig(SNF_MANAGE_LOGGING_SETUP)
312
    except ImportError:
313
        import logging
314
        logging.basicConfig()
315
        log = logging.getLogger()
316
        log.warning("SNF_MANAGE_LOGGING_SETUP setting missing.")
317

    
318

    
319
class EncodedStdOut(object):
320
    def __init__(self, stdout):
321
        try:
322
            std_encoding = stdout.encoding
323
        except AttributeError:
324
            std_encoding = None
325
        self.encoding = std_encoding or locale.getpreferredencoding()
326
        self.original_stdout = stdout
327

    
328
    def write(self, string):
329
        if isinstance(string, unicode):
330
            string = string.encode(self.encoding)
331
        self.original_stdout.write(string)
332

    
333
    def __getattr__(self, name):
334
        return getattr(self.original_stdout, name)
335

    
336

    
337
def main():
338
    # no need to run setup_environ
339
    # we already know our project
340
    os.environ['DJANGO_SETTINGS_MODULE'] = os.environ.get('DJANGO_SETTINGS_MODULE',
341
                                                          'synnefo.settings')
342
    configure_logging()
343
    mu = SynnefoManagementUtility(sys.argv)
344
    mu.execute()
345

    
346
if __name__ == "__main__":
347
    main()