Statistics
| Branch: | Tag: | Revision:

root / snf-webproject / synnefo / webproject / manage.py @ d1c9cf66

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 = EncodedStream(sys.stdout)
248
            sys.stderr = EncodedStream(sys.stderr)
249

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

    
268
    def main_help_text(self):
269
        """
270
        Returns the script's main help text, as a string.
271
        """
272
        usage = ['', ("Type '%s help <subcommand>' for help"
273
                      "on a specific subcommand.") % self.prog_name, '']
274
        usage.append('Available subcommands:')
275
        commands = get_commands().keys()
276
        commands.sort()
277
        for cmd in commands:
278
            usage.append('  %s' % cmd)
279
        return '\n'.join(usage)
280

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

    
301

    
302
def configure_logging():
303
    try:
304
        from synnefo.settings import SNF_MANAGE_LOGGING_SETUP
305
        dictConfig(SNF_MANAGE_LOGGING_SETUP)
306
    except ImportError:
307
        import logging
308
        logging.basicConfig()
309
        log = logging.getLogger()
310
        log.warning("SNF_MANAGE_LOGGING_SETUP setting missing.")
311

    
312

    
313
class EncodedStream(object):
314
    def __init__(self, stream):
315
        try:
316
            std_encoding = stream.encoding
317
        except AttributeError:
318
            std_encoding = None
319
        self.encoding = std_encoding or locale.getpreferredencoding()
320
        self.original_stream = stream
321

    
322
    def write(self, string):
323
        if isinstance(string, unicode):
324
            string = string.encode(self.encoding, errors="replace")
325
        self.original_stream.write(string)
326

    
327
    def __getattr__(self, name):
328
        return getattr(self.original_stream, name)
329

    
330

    
331
def main():
332
    os.environ['DJANGO_SETTINGS_MODULE'] = \
333
        os.environ.get('DJANGO_SETTINGS_MODULE', 'synnefo.settings')
334
    configure_logging()
335
    mu = SynnefoManagementUtility(sys.argv)
336
    mu.execute()
337

    
338
if __name__ == "__main__":
339
    main()