Statistics
| Branch: | Tag: | Revision:

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()