Statistics
| Branch: | Tag: | Revision:

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

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 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:
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('--settings-dir',
226
                action='store',
227
                dest='settings_dir',
228
                default=None,
229
                help='Load *.conf files from directory as settings'),)
230

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

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

    
252
        try:
253
            subcommand = self.argv[1]
254
        except IndexError:
255
            subcommand = 'help'  # Display help if no arguments were given.
256

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

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

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

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

    
314

    
315
def configure_logging():
316
    try:
317
        from synnefo.settings import SNF_MANAGE_LOGGING_SETUP
318
        dictConfig(SNF_MANAGE_LOGGING_SETUP)
319
    except ImportError:
320
        import logging
321
        logging.basicConfig()
322
        log = logging.getLogger()
323
        log.warning("SNF_MANAGE_LOGGING_SETUP setting missing.")
324

    
325

    
326
class EncodedStdOut(object):
327
    def __init__(self, stdout):
328
        try:
329
            std_encoding = stdout.encoding
330
        except AttributeError:
331
            std_encoding = None
332
        self.encoding = std_encoding or locale.getpreferredencoding()
333
        self.original_stdout = stdout
334

    
335
    def write(self, string):
336
        if isinstance(string, unicode):
337
            string = string.encode(self.encoding)
338
        self.original_stdout.write(string)
339

    
340
    def __getattr__(self, name):
341
        return getattr(self.original_stdout, name)
342

    
343

    
344
def main():
345
    # no need to run setup_environ
346
    # we already know our project
347
    os.environ['DJANGO_SETTINGS_MODULE'] = os.environ.get('DJANGO_SETTINGS_MODULE',
348
                                                          'synnefo.settings')
349
    configure_logging()
350
    mu = SynnefoManagementUtility(sys.argv)
351
    mu.execute()
352

    
353
if __name__ == "__main__":
354
    main()