Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (12 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 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: path = sys.path
77
    for p in path:
78
        importer = sys.path_importer_cache.get(p, None)
79
        if importer is None:
80
            find_module = imp.find_module
81
        else:
82
            find_module = importer.find_module
83

    
84
        try:
85
            result = find_module(name, [p])
86
            if result is not None:
87
                results.append(result)
88
        except ImportError:
89
            if sys.modules.get(name, None):
90
                modpath = sys.modules[name].__path__
91
                if isinstance(modpath, basestring) and not ('', modpath) in results:
92
                    results.append(('', sys.modules[name].__path__))
93
                else:
94
                    for mp in modpath:
95
                        if not ('', mp) in results:
96
                            results.append(('', mp))
97
            pass
98

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

    
102
    return results
103

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

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

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

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

    
136

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

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

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

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

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

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

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

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

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

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

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

    
201
    return _commands
202

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

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

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

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

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

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

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

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

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

    
282
    def fetch_command(self, subcommand):
283
        """
284
        Tries to fetch the given subcommand, printing a message with the
285
        appropriate command called from the command line (usually
286
        "django-admin.py" or "manage.py") if it can't be found.
287
        """
288
        try:
289
            app_name = get_commands()[subcommand]
290
        except KeyError:
291
            sys.stderr.write("Unknown command: %r\nType '%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
def main():
314
    # no need to run setup_environ
315
    # we already know our project
316
    os.environ['DJANGO_SETTINGS_MODULE'] = os.environ.get('DJANGO_SETTINGS_MODULE',
317
                                                          'synnefo.settings')
318
    configure_logging()
319
    mu = SynnefoManagementUtility(sys.argv)
320
    mu.execute()
321

    
322
if __name__ == "__main__":
323
    main()