|
1 import os |
|
2 import sys |
|
3 from optparse import OptionParser |
|
4 from imp import find_module |
|
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 path = None |
|
41 while parts: |
|
42 part = parts.pop() |
|
43 f, path, descr = find_module(part, path and [path] or None) |
|
44 return path |
|
45 |
|
46 def load_command_class(app_name, name): |
|
47 """ |
|
48 Given a command name and an application name, returns the Command |
|
49 class instance. All errors raised by the import process |
|
50 (ImportError, AttributeError) are allowed to propagate. |
|
51 """ |
|
52 return getattr(__import__('%s.management.commands.%s' % (app_name, name), |
|
53 {}, {}, ['Command']), 'Command')() |
|
54 |
|
55 def get_commands(load_user_commands=True, project_directory=None): |
|
56 """ |
|
57 Returns a dictionary mapping command names to their callback applications. |
|
58 |
|
59 This works by looking for a management.commands package in django.core, and |
|
60 in each installed application -- if a commands package exists, all commands |
|
61 in that package are registered. |
|
62 |
|
63 Core commands are always included. If a settings module has been |
|
64 specified, user-defined commands will also be included, the |
|
65 startproject command will be disabled, and the startapp command |
|
66 will be modified to use the directory in which that module appears. |
|
67 |
|
68 The dictionary is in the format {command_name: app_name}. Key-value |
|
69 pairs from this dictionary can then be used in calls to |
|
70 load_command_class(app_name, command_name) |
|
71 |
|
72 If a specific version of a command must be loaded (e.g., with the |
|
73 startapp command), the instantiated module can be placed in the |
|
74 dictionary in place of the application name. |
|
75 |
|
76 The dictionary is cached on the first call and reused on subsequent |
|
77 calls. |
|
78 """ |
|
79 global _commands |
|
80 if _commands is None: |
|
81 _commands = dict([(name, 'django.core') for name in find_commands(__path__[0])]) |
|
82 |
|
83 if load_user_commands: |
|
84 # Get commands from all installed apps. |
|
85 from django.conf import settings |
|
86 for app_name in settings.INSTALLED_APPS: |
|
87 try: |
|
88 path = find_management_module(app_name) |
|
89 _commands.update(dict([(name, app_name) for name in find_commands(path)])) |
|
90 except ImportError: |
|
91 pass # No management module -- ignore this app. |
|
92 |
|
93 if project_directory: |
|
94 # Remove the "startproject" command from self.commands, because |
|
95 # that's a django-admin.py command, not a manage.py command. |
|
96 del _commands['startproject'] |
|
97 |
|
98 # Override the startapp command so that it always uses the |
|
99 # project_directory, not the current working directory |
|
100 # (which is default). |
|
101 from django.core.management.commands.startapp import ProjectCommand |
|
102 _commands['startapp'] = ProjectCommand(project_directory) |
|
103 |
|
104 return _commands |
|
105 |
|
106 def call_command(name, *args, **options): |
|
107 """ |
|
108 Calls the given command, with the given options and args/kwargs. |
|
109 |
|
110 This is the primary API you should use for calling specific commands. |
|
111 |
|
112 Some examples: |
|
113 call_command('syncdb') |
|
114 call_command('shell', plain=True) |
|
115 call_command('sqlall', 'myapp') |
|
116 """ |
|
117 try: |
|
118 app_name = get_commands()[name] |
|
119 if isinstance(app_name, BaseCommand): |
|
120 # If the command is already loaded, use it directly. |
|
121 klass = app_name |
|
122 else: |
|
123 klass = load_command_class(app_name, name) |
|
124 except KeyError: |
|
125 raise CommandError, "Unknown command: %r" % name |
|
126 return klass.execute(*args, **options) |
|
127 |
|
128 class LaxOptionParser(OptionParser): |
|
129 """ |
|
130 An option parser that doesn't raise any errors on unknown options. |
|
131 |
|
132 This is needed because the --settings and --pythonpath options affect |
|
133 the commands (and thus the options) that are available to the user. |
|
134 """ |
|
135 def error(self, msg): |
|
136 pass |
|
137 |
|
138 class ManagementUtility(object): |
|
139 """ |
|
140 Encapsulates the logic of the django-admin.py and manage.py utilities. |
|
141 |
|
142 A ManagementUtility has a number of commands, which can be manipulated |
|
143 by editing the self.commands dictionary. |
|
144 """ |
|
145 def __init__(self, argv=None): |
|
146 self.argv = argv or sys.argv[:] |
|
147 self.prog_name = os.path.basename(self.argv[0]) |
|
148 self.project_directory = None |
|
149 self.user_commands = False |
|
150 |
|
151 def main_help_text(self): |
|
152 """ |
|
153 Returns the script's main help text, as a string. |
|
154 """ |
|
155 usage = ['%s <subcommand> [options] [args]' % self.prog_name] |
|
156 usage.append('Django command line tool, version %s' % django.get_version()) |
|
157 usage.append("Type '%s help <subcommand>' for help on a specific subcommand." % self.prog_name) |
|
158 usage.append('Available subcommands:') |
|
159 commands = get_commands(self.user_commands, self.project_directory).keys() |
|
160 commands.sort() |
|
161 for cmd in commands: |
|
162 usage.append(' %s' % cmd) |
|
163 return '\n'.join(usage) |
|
164 |
|
165 def fetch_command(self, subcommand): |
|
166 """ |
|
167 Tries to fetch the given subcommand, printing a message with the |
|
168 appropriate command called from the command line (usually |
|
169 "django-admin.py" or "manage.py") if it can't be found. |
|
170 """ |
|
171 try: |
|
172 app_name = get_commands(self.user_commands, self.project_directory)[subcommand] |
|
173 if isinstance(app_name, BaseCommand): |
|
174 # If the command is already loaded, use it directly. |
|
175 klass = app_name |
|
176 else: |
|
177 klass = load_command_class(app_name, subcommand) |
|
178 except KeyError: |
|
179 sys.stderr.write("Unknown command: %r\nType '%s help' for usage.\n" % \ |
|
180 (subcommand, self.prog_name)) |
|
181 sys.exit(1) |
|
182 return klass |
|
183 |
|
184 def execute(self): |
|
185 """ |
|
186 Given the command-line arguments, this figures out which subcommand is |
|
187 being run, creates a parser appropriate to that command, and runs it. |
|
188 """ |
|
189 # Preprocess options to extract --settings and --pythonpath. |
|
190 # These options could affect the commands that are available, so they |
|
191 # must be processed early. |
|
192 parser = LaxOptionParser(version=get_version(), option_list=BaseCommand.option_list) |
|
193 try: |
|
194 options, args = parser.parse_args(self.argv) |
|
195 handle_default_options(options) |
|
196 except: |
|
197 pass # Ignore any option errors at this point. |
|
198 |
|
199 try: |
|
200 subcommand = self.argv[1] |
|
201 except IndexError: |
|
202 sys.stderr.write("Type '%s help' for usage.\n" % self.prog_name) |
|
203 sys.exit(1) |
|
204 |
|
205 if subcommand == 'help': |
|
206 if len(args) > 2: |
|
207 self.fetch_command(args[2]).print_help(self.prog_name, args[2]) |
|
208 else: |
|
209 sys.stderr.write(self.main_help_text() + '\n') |
|
210 sys.exit(1) |
|
211 # Special-cases: We want 'django-admin.py --version' and |
|
212 # 'django-admin.py --help' to work, for backwards compatibility. |
|
213 elif self.argv[1:] == ['--version']: |
|
214 # LaxOptionParser already takes care of printing the version. |
|
215 pass |
|
216 elif self.argv[1:] == ['--help']: |
|
217 sys.stderr.write(self.main_help_text() + '\n') |
|
218 else: |
|
219 self.fetch_command(subcommand).run_from_argv(self.argv) |
|
220 |
|
221 class ProjectManagementUtility(ManagementUtility): |
|
222 """ |
|
223 A ManagementUtility that is specific to a particular Django project. |
|
224 As such, its commands are slightly different than those of its parent |
|
225 class. |
|
226 |
|
227 In practice, this class represents manage.py, whereas ManagementUtility |
|
228 represents django-admin.py. |
|
229 """ |
|
230 def __init__(self, argv, project_directory): |
|
231 super(ProjectManagementUtility, self).__init__(argv) |
|
232 self.project_directory = project_directory |
|
233 self.user_commands = True |
|
234 |
|
235 def setup_environ(settings_mod): |
|
236 """ |
|
237 Configures the runtime environment. This can also be used by external |
|
238 scripts wanting to set up a similar environment to manage.py. |
|
239 Returns the project directory (assuming the passed settings module is |
|
240 directly in the project directory). |
|
241 """ |
|
242 # Add this project to sys.path so that it's importable in the conventional |
|
243 # way. For example, if this file (manage.py) lives in a directory |
|
244 # "myproject", this code would add "/path/to/myproject" to sys.path. |
|
245 project_directory, settings_filename = os.path.split(settings_mod.__file__) |
|
246 if project_directory == os.curdir or not project_directory: |
|
247 project_directory = os.getcwd() |
|
248 project_name = os.path.basename(project_directory) |
|
249 settings_name = os.path.splitext(settings_filename)[0] |
|
250 sys.path.append(os.path.join(project_directory, os.pardir)) |
|
251 project_module = __import__(project_name, {}, {}, ['']) |
|
252 sys.path.pop() |
|
253 |
|
254 # Set DJANGO_SETTINGS_MODULE appropriately. |
|
255 os.environ['DJANGO_SETTINGS_MODULE'] = '%s.%s' % (project_name, settings_name) |
|
256 return project_directory |
|
257 |
|
258 def execute_from_command_line(argv=None): |
|
259 """ |
|
260 A simple method that runs a ManagementUtility. |
|
261 """ |
|
262 utility = ManagementUtility(argv) |
|
263 utility.execute() |
|
264 |
|
265 def execute_manager(settings_mod, argv=None): |
|
266 """ |
|
267 Like execute_from_command_line(), but for use by manage.py, a |
|
268 project-specific django-admin.py utility. |
|
269 """ |
|
270 project_directory = setup_environ(settings_mod) |
|
271 utility = ProjectManagementUtility(argv, project_directory) |
|
272 utility.execute() |