Statistics
| Branch: | Tag: | Revision:

root / snf-webproject / synnefo / webproject / manage.py @ 18a544f5

History | View | Annotate | Download (9.7 kB)

1
"""
2
Extented django management module
3

4
Most of the code is shared from django.core.management module
5
to allow us extend the default django ManagementUtility object
6
used to provide command line interface of the django project
7
included in snf-webproject package.
8

9
The extended class provides the following:
10

11
- additional argument for the configuration of the SYNNEFO_SETTINGS_DIR
12
  environmental variable (--settings-dir).
13
- a fix for management utility to handle custom commands defined in
14
  applications living in namespaced packages (django ticket #14087)
15
- override of --version command to display the snf-webproject version
16
"""
17

    
18
from django.core.management import ManagementUtility, setup_environ, \
19
BaseCommand, LaxOptionParser, handle_default_options, find_commands, \
20
load_command_class
21

    
22
from django.core import management
23
from django.utils.importlib import import_module
24
from optparse import Option, make_option
25
from synnefo.util.version import get_component_version
26

    
27
import sys
28
import os
29
import imp
30

    
31
_commands = None
32

    
33
def find_modules(name, path=None):
34
    """Find all modules with name 'name'
35

36
    Unlike find_module in the imp package this returns a list of all
37
    matched modules.
38
    """
39
    results = []
40
    if path is None: path = sys.path
41
    for p in path:
42
        importer = sys.path_importer_cache.get(p, None)
43
        if importer is None:
44
            find_module = imp.find_module
45
        else:
46
            find_module = importer.find_module
47

    
48
        try:
49
            result = find_module(name, [p])
50
            if result is not None:
51
                results.append(result)
52
        except ImportError:
53
            pass
54
    if not results:
55
        raise ImportError("No module named %.200s" % name)
56
    return results
57

    
58
def find_management_module(app_name):
59
    """
60
    Determines the path to the management module for the given app_name,
61
    without actually importing the application or the management module.
62

63
    Raises ImportError if the management module cannot be found for any reason.
64
    """
65
    parts = app_name.split('.')
66
    parts.append('management')
67
    parts.reverse()
68
    part = parts.pop()
69
    paths = None
70

    
71
    # When using manage.py, the project module is added to the path,
72
    # loaded, then removed from the path. This means that
73
    # testproject.testapp.models can be loaded in future, even if
74
    # testproject isn't in the path. When looking for the management
75
    # module, we need look for the case where the project name is part
76
    # of the app_name but the project directory itself isn't on the path.
77
    try:
78
        modules = find_modules(part, paths)
79
        paths = [m[1] for m in modules]
80
    except ImportError,e:
81
        if os.path.basename(os.getcwd()) != part:
82
            raise e
83

    
84
    while parts:
85
        part = parts.pop()
86
        modules = find_modules(part, paths)
87
        paths = [m[1] for m in modules]
88
    return paths[0]
89

    
90

    
91
def get_commands():
92
    """
93
    Returns a dictionary mapping command names to their callback applications.
94

95
    This works by looking for a management.commands package in django.core, and
96
    in each installed application -- if a commands package exists, all commands
97
    in that package are registered.
98

99
    Core commands are always included. If a settings module has been
100
    specified, user-defined commands will also be included, the
101
    startproject command will be disabled, and the startapp command
102
    will be modified to use the directory in which the settings module appears.
103

104
    The dictionary is in the format {command_name: app_name}. Key-value
105
    pairs from this dictionary can then be used in calls to
106
    load_command_class(app_name, command_name)
107

108
    If a specific version of a command must be loaded (e.g., with the
109
    startapp command), the instantiated module can be placed in the
110
    dictionary in place of the application name.
111

112
    The dictionary is cached on the first call and reused on subsequent
113
    calls.
114
    """
115
    global _commands
116
    if _commands is None:
117
        _commands = dict([(name, 'django.core') for name in \
118
            find_commands(management.__path__[0])])
119

    
120
        # Find the installed apps
121
        try:
122
            from django.conf import settings
123
            apps = settings.INSTALLED_APPS
124
        except (AttributeError, EnvironmentError, ImportError):
125
            apps = []
126

    
127
        # Find the project directory
128
        try:
129
            from django.conf import settings
130
            module = import_module(settings.SETTINGS_MODULE)
131
            project_directory = setup_environ(module, settings.SETTINGS_MODULE)
132
        except (AttributeError, EnvironmentError, ImportError, KeyError):
133
            project_directory = None
134

    
135
        # Find and load the management module for each installed app.
136
        for app_name in apps:
137
            try:
138
                path = find_management_module(app_name)
139
                _commands.update(dict([(name, app_name)
140
                                       for name in find_commands(path)]))
141
            except ImportError:
142
                pass # No management module - ignore this app
143

    
144
        if project_directory:
145
            # Remove the "startproject" command from self.commands, because
146
            # that's a django-admin.py command, not a manage.py command.
147
            del _commands['startproject']
148

    
149
            # Override the startapp command so that it always uses the
150
            # project_directory, not the current working directory
151
            # (which is default).
152
            from django.core.management.commands.startapp import ProjectCommand
153
            _commands['startapp'] = ProjectCommand(project_directory)
154

    
155
    return _commands
156

    
157
class SynnefoManagementUtility(ManagementUtility):
158
    """
159
    Override django ManagementUtility to allow us provide a custom
160
    --settings-dir option for synnefo application.
161

162
    Most of the following code is a copy from django.core.management module
163
    """
164

    
165
    def execute(self):
166
        """
167
        Given the command-line arguments, this figures out which subcommand is
168
        being run, creates a parser appropriate to that command, and runs it.
169
        """
170

    
171
        # --settings-dir option
172
        # will remove it later to avoid django commands from raising errors
173
        option_list = BaseCommand.option_list + (
174
            make_option('--settings-dir',
175
                action='store',
176
                dest='settings_dir',
177
                default=None,
178
                help='Load *.conf files from directory as settings'),)
179

    
180
        # Preprocess options to extract --settings and --pythonpath.
181
        # These options could affect the commands that are available, so they
182
        # must be processed early.
183
        parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
184
                                 version=get_component_version('webproject'),
185
                                 option_list=option_list)
186
        self.autocomplete()
187
        try:
188
            options, args = parser.parse_args(self.argv)
189
            handle_default_options(options)
190
        except:
191
            pass # Ignore any option errors at this point.
192

    
193
        # user provides custom settings dir
194
        # set it as environmental variable and remove it from self.argv
195
        if options.settings_dir:
196
            os.environ['SYNNEFO_SETTINGS_DIR'] = options.settings_dir
197
            for arg in self.argv:
198
                if arg.startswith('--settings-dir'):
199
                    self.argv.remove(arg)
200

    
201
        try:
202
            subcommand = self.argv[1]
203
        except IndexError:
204
            subcommand = 'help' # Display help if no arguments were given.
205

    
206
        if subcommand == 'help':
207
            if len(args) > 2:
208
                self.fetch_command(args[2]).print_help(self.prog_name, args[2])
209
            else:
210
                parser.print_lax_help()
211
                sys.stdout.write(self.main_help_text() + '\n')
212
                sys.exit(1)
213
        # Special-cases: We want 'django-admin.py --version' and
214
        # 'django-admin.py --help' to work, for backwards compatibility.
215
        elif self.argv[1:] == ['--version']:
216
            # LaxOptionParser already takes care of printing the version.
217
            pass
218
        elif self.argv[1:] in (['--help'], ['-h']):
219
            parser.print_lax_help()
220
            sys.stdout.write(self.main_help_text() + '\n')
221
        else:
222
            self.fetch_command(subcommand).run_from_argv(self.argv)
223

    
224
    def main_help_text(self):
225
        """
226
        Returns the script's main help text, as a string.
227
        """
228
        usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,'']
229
        usage.append('Available subcommands:')
230
        commands = get_commands().keys()
231
        commands.sort()
232
        for cmd in commands:
233
            usage.append('  %s' % cmd)
234
        return '\n'.join(usage)
235

    
236
    def fetch_command(self, subcommand):
237
        """
238
        Tries to fetch the given subcommand, printing a message with the
239
        appropriate command called from the command line (usually
240
        "django-admin.py" or "manage.py") if it can't be found.
241
        """
242
        try:
243
            app_name = get_commands()[subcommand]
244
        except KeyError:
245
            sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \
246
                (subcommand, self.prog_name))
247
            sys.exit(1)
248
        if isinstance(app_name, BaseCommand):
249
            # If the command is already loaded, use it directly.
250
            klass = app_name
251
        else:
252
            klass = load_command_class(app_name, subcommand)
253
        return klass
254

    
255
def main():
256
    # no need to run setup_environ
257
    # we already know our project
258
    os.environ['DJANGO_SETTINGS_MODULE'] = os.environ.get('DJANGO_SETTINGS_MODULE',
259
                                                          'synnefo.settings')
260
    mu = SynnefoManagementUtility(sys.argv)
261
    mu.execute()
262

    
263
if __name__ == "__main__":
264
    main()