Django

Code

root/django/trunk/django/core/management/__init__.py

Revision 9470, 12.7 kB (checked in by mtredinnick, 2 months ago)

Fixed #9455 -- Tiny cleanup to avoid an "undefined variable" warning. The net
effect is the same in any case (bailing out to the global exception catcher).

  • Property svn:eol-style set to native
Line 
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 Exception
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()
Note: See TracBrowser for help on using the browser.