Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (11.6 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

    
60
import sys
61
import os
62
import imp
63

    
64
_commands = None
65

    
66
def find_modules(name, path=None):
67
    """Find all modules with name 'name'
68

69
    Unlike find_module in the imp package this returns a list of all
70
    matched modules.
71
    """
72

    
73
    results = []
74
    if path is None: path = sys.path
75
    for p in path:
76
        importer = sys.path_importer_cache.get(p, None)
77
        if importer is None:
78
            find_module = imp.find_module
79
        else:
80
            find_module = importer.find_module
81

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

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

    
100
    return results
101

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

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

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

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

    
134

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

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

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

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

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

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

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

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

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

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

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

    
199
    return _commands
200

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

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

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

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

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

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

    
245
        try:
246
            subcommand = self.argv[1]
247
        except IndexError:
248
            subcommand = 'help' # Display help if no arguments were given.
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 on a specific subcommand." % self.prog_name,'']
273
        usage.append('Available subcommands:')
274
        commands = get_commands().keys()
275
        commands.sort()
276
        for cmd in commands:
277
            usage.append('  %s' % cmd)
278
        return '\n'.join(usage)
279

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

    
299
def main():
300
    # no need to run setup_environ
301
    # we already know our project
302
    os.environ['DJANGO_SETTINGS_MODULE'] = os.environ.get('DJANGO_SETTINGS_MODULE',
303
                                                          'synnefo.settings')
304
    mu = SynnefoManagementUtility(sys.argv)
305
    mu.execute()
306

    
307
if __name__ == "__main__":
308
    main()