~ubuntu-branches/ubuntu/quantal/python-django/quantal

« back to all changes in this revision

Viewing changes to django/core/management/__init__.py

  • Committer: Bazaar Package Importer
  • Author(s): Scott James Remnant, Eddy Mulyono
  • Date: 2008-09-16 12:18:47 UTC
  • mfrom: (1.1.5 upstream) (4.1.1 lenny)
  • Revision ID: james.westby@ubuntu.com-20080916121847-mg225rg5mnsdqzr0
Tags: 1.0-1ubuntu1
* Merge from Debian (LP: #264191), remaining changes:
  - Run test suite on build.

[Eddy Mulyono]
* Update patch to workaround network test case failures.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import os
 
2
import sys
 
3
from optparse import OptionParser
 
4
import imp
 
5
 
 
6
import django
 
7
from django.core.management.base import BaseCommand, CommandError, handle_default_options
 
8
 
 
9
# For backwards compatibility: get_version() used to be in this module.
 
10
get_version = django.get_version
 
11
 
 
12
# A cache of loaded commands, so that call_command
 
13
# doesn't have to reload every time it's called.
 
14
_commands = None
 
15
 
 
16
def find_commands(management_dir):
 
17
    """
 
18
    Given a path to a management directory, returns a list of all the command
 
19
    names that are available.
 
20
 
 
21
    Returns an empty list if no commands are defined.
 
22
    """
 
23
    command_dir = os.path.join(management_dir, 'commands')
 
24
    try:
 
25
        return [f[:-3] for f in os.listdir(command_dir)
 
26
                if not f.startswith('_') and f.endswith('.py')]
 
27
    except OSError:
 
28
        return []
 
29
 
 
30
def find_management_module(app_name):
 
31
    """
 
32
    Determines the path to the management module for the given app_name,
 
33
    without actually importing the application or the management module.
 
34
 
 
35
    Raises ImportError if the management module cannot be found for any reason.
 
36
    """
 
37
    parts = app_name.split('.')
 
38
    parts.append('management')
 
39
    parts.reverse()
 
40
    part = parts.pop()
 
41
    path = None
 
42
 
 
43
    # When using manage.py, the project module is added to the path,
 
44
    # loaded, then removed from the path. This means that
 
45
    # testproject.testapp.models can be loaded in future, even if
 
46
    # testproject isn't in the path. When looking for the management
 
47
    # module, we need look for the case where the project name is part
 
48
    # of the app_name but the project directory itself isn't on the path.
 
49
    try:
 
50
        f, path, descr = imp.find_module(part,path)
 
51
    except ImportError,e:
 
52
        if os.path.basename(os.getcwd()) != part:
 
53
            raise e
 
54
 
 
55
    while parts:
 
56
        part = parts.pop()
 
57
        f, path, descr = imp.find_module(part, path and [path] or None)
 
58
    return path
 
59
 
 
60
def load_command_class(app_name, name):
 
61
    """
 
62
    Given a command name and an application name, returns the Command
 
63
    class instance. All errors raised by the import process
 
64
    (ImportError, AttributeError) are allowed to propagate.
 
65
    """
 
66
    return getattr(__import__('%s.management.commands.%s' % (app_name, name),
 
67
                   {}, {}, ['Command']), 'Command')()
 
68
 
 
69
def get_commands():
 
70
    """
 
71
    Returns a dictionary mapping command names to their callback applications.
 
72
 
 
73
    This works by looking for a management.commands package in django.core, and
 
74
    in each installed application -- if a commands package exists, all commands
 
75
    in that package are registered.
 
76
 
 
77
    Core commands are always included. If a settings module has been
 
78
    specified, user-defined commands will also be included, the
 
79
    startproject command will be disabled, and the startapp command
 
80
    will be modified to use the directory in which the settings module appears.
 
81
 
 
82
    The dictionary is in the format {command_name: app_name}. Key-value
 
83
    pairs from this dictionary can then be used in calls to
 
84
    load_command_class(app_name, command_name)
 
85
 
 
86
    If a specific version of a command must be loaded (e.g., with the
 
87
    startapp command), the instantiated module can be placed in the
 
88
    dictionary in place of the application name.
 
89
 
 
90
    The dictionary is cached on the first call and reused on subsequent
 
91
    calls.
 
92
    """
 
93
    global _commands
 
94
    if _commands is None:
 
95
        _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])])
 
96
 
 
97
        # Find the installed apps
 
98
        try:
 
99
            from django.conf import settings
 
100
            apps = settings.INSTALLED_APPS
 
101
        except (AttributeError, EnvironmentError, ImportError):
 
102
            apps = []
 
103
 
 
104
        # Find the project directory
 
105
        try:
 
106
            from django.conf import settings
 
107
            project_directory = setup_environ(
 
108
                __import__(
 
109
                    settings.SETTINGS_MODULE, {}, {},
 
110
                    (settings.SETTINGS_MODULE.split(".")[-1],)
 
111
                ), settings.SETTINGS_MODULE
 
112
            )
 
113
        except (AttributeError, EnvironmentError, ImportError):
 
114
            project_directory = None
 
115
 
 
116
        # Find and load the management module for each installed app.
 
117
        for app_name in apps:
 
118
            try:
 
119
                path = find_management_module(app_name)
 
120
                _commands.update(dict([(name, app_name)
 
121
                                       for name in find_commands(path)]))
 
122
            except ImportError:
 
123
                pass # No management module - ignore this app
 
124
 
 
125
        if project_directory:
 
126
            # Remove the "startproject" command from self.commands, because
 
127
            # that's a django-admin.py command, not a manage.py command.
 
128
            del _commands['startproject']
 
129
 
 
130
            # Override the startapp command so that it always uses the
 
131
            # project_directory, not the current working directory
 
132
            # (which is default).
 
133
            from django.core.management.commands.startapp import ProjectCommand
 
134
            _commands['startapp'] = ProjectCommand(project_directory)
 
135
 
 
136
    return _commands
 
137
 
 
138
def call_command(name, *args, **options):
 
139
    """
 
140
    Calls the given command, with the given options and args/kwargs.
 
141
 
 
142
    This is the primary API you should use for calling specific commands.
 
143
 
 
144
    Some examples:
 
145
        call_command('syncdb')
 
146
        call_command('shell', plain=True)
 
147
        call_command('sqlall', 'myapp')
 
148
    """
 
149
    try:
 
150
        app_name = get_commands()[name]
 
151
        if isinstance(app_name, BaseCommand):
 
152
            # If the command is already loaded, use it directly.
 
153
            klass = app_name
 
154
        else:
 
155
            klass = load_command_class(app_name, name)
 
156
    except KeyError:
 
157
        raise CommandError, "Unknown command: %r" % name
 
158
    return klass.execute(*args, **options)
 
159
 
 
160
class LaxOptionParser(OptionParser):
 
161
    """
 
162
    An option parser that doesn't raise any errors on unknown options.
 
163
 
 
164
    This is needed because the --settings and --pythonpath options affect
 
165
    the commands (and thus the options) that are available to the user.
 
166
    """
 
167
    def error(self, msg):
 
168
        pass
 
169
 
 
170
    def print_help(self):
 
171
        """Output nothing.
 
172
 
 
173
        The lax options are included in the normal option parser, so under
 
174
        normal usage, we don't need to print the lax options.
 
175
        """
 
176
        pass
 
177
 
 
178
    def print_lax_help(self):
 
179
        """Output the basic options available to every command.
 
180
 
 
181
        This just redirects to the default print_help() behaviour.
 
182
        """
 
183
        OptionParser.print_help(self)
 
184
 
 
185
    def _process_args(self, largs, rargs, values):
 
186
        """
 
187
        Overrides OptionParser._process_args to exclusively handle default
 
188
        options and ignore args and other options.
 
189
 
 
190
        This overrides the behavior of the super class, which stop parsing
 
191
        at the first unrecognized option.
 
192
        """
 
193
        while rargs:
 
194
            arg = rargs[0]
 
195
            try:
 
196
                if arg[0:2] == "--" and len(arg) > 2:
 
197
                    # process a single long option (possibly with value(s))
 
198
                    # the superclass code pops the arg off rargs
 
199
                    self._process_long_opt(rargs, values)
 
200
                elif arg[:1] == "-" and len(arg) > 1:
 
201
                    # process a cluster of short options (possibly with
 
202
                    # value(s) for the last one only)
 
203
                    # the superclass code pops the arg off rargs
 
204
                    self._process_short_opts(rargs, values)
 
205
                else:
 
206
                    # it's either a non-default option or an arg
 
207
                    # either way, add it to the args list so we can keep
 
208
                    # dealing with options
 
209
                    del rargs[0]
 
210
                    raise error
 
211
            except:
 
212
                largs.append(arg)
 
213
 
 
214
class ManagementUtility(object):
 
215
    """
 
216
    Encapsulates the logic of the django-admin.py and manage.py utilities.
 
217
 
 
218
    A ManagementUtility has a number of commands, which can be manipulated
 
219
    by editing the self.commands dictionary.
 
220
    """
 
221
    def __init__(self, argv=None):
 
222
        self.argv = argv or sys.argv[:]
 
223
        self.prog_name = os.path.basename(self.argv[0])
 
224
 
 
225
    def main_help_text(self):
 
226
        """
 
227
        Returns the script's main help text, as a string.
 
228
        """
 
229
        usage = ['',"Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name,'']
 
230
        usage.append('Available subcommands:')
 
231
        commands = get_commands().keys()
 
232
        commands.sort()
 
233
        for cmd in commands:
 
234
            usage.append('  %s' % cmd)
 
235
        return '\n'.join(usage)
 
236
 
 
237
    def fetch_command(self, subcommand):
 
238
        """
 
239
        Tries to fetch the given subcommand, printing a message with the
 
240
        appropriate command called from the command line (usually
 
241
        "django-admin.py" or "manage.py") if it can't be found.
 
242
        """
 
243
        try:
 
244
            app_name = get_commands()[subcommand]
 
245
            if isinstance(app_name, BaseCommand):
 
246
                # If the command is already loaded, use it directly.
 
247
                klass = app_name
 
248
            else:
 
249
                klass = load_command_class(app_name, subcommand)
 
250
        except KeyError:
 
251
            sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \
 
252
                (subcommand, self.prog_name))
 
253
            sys.exit(1)
 
254
        return klass
 
255
 
 
256
    def execute(self):
 
257
        """
 
258
        Given the command-line arguments, this figures out which subcommand is
 
259
        being run, creates a parser appropriate to that command, and runs it.
 
260
        """
 
261
        # Preprocess options to extract --settings and --pythonpath.
 
262
        # These options could affect the commands that are available, so they
 
263
        # must be processed early.
 
264
        parser = LaxOptionParser(usage="%prog subcommand [options] [args]",
 
265
                                 version=get_version(),
 
266
                                 option_list=BaseCommand.option_list)
 
267
        try:
 
268
            options, args = parser.parse_args(self.argv)
 
269
            handle_default_options(options)
 
270
        except:
 
271
            pass # Ignore any option errors at this point.
 
272
 
 
273
        try:
 
274
            subcommand = self.argv[1]
 
275
        except IndexError:
 
276
            sys.stderr.write("Type '%s help' for usage.\n" % self.prog_name)
 
277
            sys.exit(1)
 
278
 
 
279
        if subcommand == 'help':
 
280
            if len(args) > 2:
 
281
                self.fetch_command(args[2]).print_help(self.prog_name, args[2])
 
282
            else:
 
283
                parser.print_lax_help()
 
284
                sys.stderr.write(self.main_help_text() + '\n')
 
285
                sys.exit(1)
 
286
        # Special-cases: We want 'django-admin.py --version' and
 
287
        # 'django-admin.py --help' to work, for backwards compatibility.
 
288
        elif self.argv[1:] == ['--version']:
 
289
            # LaxOptionParser already takes care of printing the version.
 
290
            pass
 
291
        elif self.argv[1:] == ['--help']:
 
292
            parser.print_lax_help()
 
293
            sys.stderr.write(self.main_help_text() + '\n')
 
294
        else:
 
295
            self.fetch_command(subcommand).run_from_argv(self.argv)
 
296
 
 
297
def setup_environ(settings_mod, original_settings_path=None):
 
298
    """
 
299
    Configures the runtime environment. This can also be used by external
 
300
    scripts wanting to set up a similar environment to manage.py.
 
301
    Returns the project directory (assuming the passed settings module is
 
302
    directly in the project directory).
 
303
 
 
304
    The "original_settings_path" parameter is optional, but recommended, since
 
305
    trying to work out the original path from the module can be problematic.
 
306
    """
 
307
    # Add this project to sys.path so that it's importable in the conventional
 
308
    # way. For example, if this file (manage.py) lives in a directory
 
309
    # "myproject", this code would add "/path/to/myproject" to sys.path.
 
310
    project_directory, settings_filename = os.path.split(settings_mod.__file__)
 
311
    if project_directory == os.curdir or not project_directory:
 
312
        project_directory = os.getcwd()
 
313
    project_name = os.path.basename(project_directory)
 
314
    settings_name = os.path.splitext(settings_filename)[0]
 
315
    sys.path.append(os.path.join(project_directory, os.pardir))
 
316
    project_module = __import__(project_name, {}, {}, [''])
 
317
    sys.path.pop()
 
318
 
 
319
    # Set DJANGO_SETTINGS_MODULE appropriately.
 
320
    if original_settings_path:
 
321
        os.environ['DJANGO_SETTINGS_MODULE'] = original_settings_path
 
322
    else:
 
323
        os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name)
 
324
    return project_directory
 
325
 
 
326
def execute_from_command_line(argv=None):
 
327
    """
 
328
    A simple method that runs a ManagementUtility.
 
329
    """
 
330
    utility = ManagementUtility(argv)
 
331
    utility.execute()
 
332
 
 
333
def execute_manager(settings_mod, argv=None):
 
334
    """
 
335
    Like execute_from_command_line(), but for use by manage.py, a
 
336
    project-specific django-admin.py utility.
 
337
    """
 
338
    setup_environ(settings_mod)
 
339
    utility = ManagementUtility(argv)
 
340
    utility.execute()