|
1 # Django management-related functions, including "CREATE TABLE" generation and |
|
2 # development-server initialization. |
|
3 |
|
4 import django |
|
5 from django.core.exceptions import ImproperlyConfigured |
|
6 import os, re, shutil, sys, textwrap |
|
7 from optparse import OptionParser |
|
8 from django.utils import termcolors |
|
9 |
|
10 # For Python 2.3 |
|
11 if not hasattr(__builtins__, 'set'): |
|
12 from sets import Set as set |
|
13 |
|
14 MODULE_TEMPLATE = ''' {%% if perms.%(app)s.%(addperm)s or perms.%(app)s.%(changeperm)s %%} |
|
15 <tr> |
|
16 <th>{%% if perms.%(app)s.%(changeperm)s %%}<a href="%(app)s/%(mod)s/">{%% endif %%}%(name)s{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</th> |
|
17 <td class="x50">{%% if perms.%(app)s.%(addperm)s %%}<a href="%(app)s/%(mod)s/add/" class="addlink">{%% endif %%}Add{%% if perms.%(app)s.%(addperm)s %%}</a>{%% endif %%}</td> |
|
18 <td class="x75">{%% if perms.%(app)s.%(changeperm)s %%}<a href="%(app)s/%(mod)s/" class="changelink">{%% endif %%}Change{%% if perms.%(app)s.%(changeperm)s %%}</a>{%% endif %%}</td> |
|
19 </tr> |
|
20 {%% endif %%}''' |
|
21 |
|
22 APP_ARGS = '[appname ...]' |
|
23 |
|
24 # Use django.__path__[0] because we don't know which directory django into |
|
25 # which has been installed. |
|
26 PROJECT_TEMPLATE_DIR = os.path.join(django.__path__[0], 'conf', '%s_template') |
|
27 |
|
28 INVALID_PROJECT_NAMES = ('django', 'site', 'test') |
|
29 |
|
30 # Set up the terminal color scheme. |
|
31 class dummy: pass |
|
32 style = dummy() |
|
33 style.ERROR = termcolors.make_style(fg='red', opts=('bold',)) |
|
34 style.ERROR_OUTPUT = termcolors.make_style(fg='red', opts=('bold',)) |
|
35 style.NOTICE = termcolors.make_style(fg='red') |
|
36 style.SQL_FIELD = termcolors.make_style(fg='green', opts=('bold',)) |
|
37 style.SQL_COLTYPE = termcolors.make_style(fg='green') |
|
38 style.SQL_KEYWORD = termcolors.make_style(fg='yellow') |
|
39 style.SQL_TABLE = termcolors.make_style(opts=('bold',)) |
|
40 del dummy |
|
41 |
|
42 def disable_termcolors(): |
|
43 class dummy: |
|
44 def __getattr__(self, attr): |
|
45 return lambda x: x |
|
46 global style |
|
47 style = dummy() |
|
48 |
|
49 # Disable terminal coloring on Windows, Pocket PC, or if somebody's piping the output. |
|
50 if sys.platform == 'win32' or sys.platform == 'Pocket PC' or not sys.stdout.isatty(): |
|
51 disable_termcolors() |
|
52 |
|
53 def _is_valid_dir_name(s): |
|
54 return bool(re.search(r'^\w+$', s)) |
|
55 |
|
56 def _get_installed_models(table_list): |
|
57 "Gets a set of all models that are installed, given a list of existing tables" |
|
58 from django.db import models |
|
59 all_models = [] |
|
60 for app in models.get_apps(): |
|
61 for model in models.get_models(app): |
|
62 all_models.append(model) |
|
63 return set([m for m in all_models if m._meta.db_table in table_list]) |
|
64 |
|
65 def _get_table_list(): |
|
66 "Gets a list of all db tables that are physically installed." |
|
67 from django.db import connection, get_introspection_module |
|
68 cursor = connection.cursor() |
|
69 return get_introspection_module().get_table_list(cursor) |
|
70 |
|
71 def _get_sequence_list(): |
|
72 "Returns a list of information about all DB sequences for all models in all apps" |
|
73 from django.db import models |
|
74 |
|
75 apps = models.get_apps() |
|
76 sequence_list = [] |
|
77 |
|
78 for app in apps: |
|
79 for model in models.get_models(app): |
|
80 for f in model._meta.fields: |
|
81 if isinstance(f, models.AutoField): |
|
82 sequence_list.append({'table':model._meta.db_table,'column':f.column,}) |
|
83 break # Only one AutoField is allowed per model, so don't bother continuing. |
|
84 |
|
85 for f in model._meta.many_to_many: |
|
86 sequence_list.append({'table':f.m2m_db_table(),'column':None,}) |
|
87 |
|
88 return sequence_list |
|
89 |
|
90 # If the foreign key points to an AutoField, a PositiveIntegerField or a |
|
91 # PositiveSmallIntegerField, the foreign key should be an IntegerField, not the |
|
92 # referred field type. Otherwise, the foreign key should be the same type of |
|
93 # field as the field to which it points. |
|
94 get_rel_data_type = lambda f: (f.get_internal_type() in ('AutoField', 'PositiveIntegerField', 'PositiveSmallIntegerField')) and 'IntegerField' or f.get_internal_type() |
|
95 |
|
96 def get_version(): |
|
97 "Returns the version as a human-format string." |
|
98 from django import VERSION |
|
99 v = '.'.join([str(i) for i in VERSION[:-1]]) |
|
100 if VERSION[-1]: |
|
101 v += '-' + VERSION[-1] |
|
102 return v |
|
103 |
|
104 def get_sql_create(app): |
|
105 "Returns a list of the CREATE TABLE SQL statements for the given app." |
|
106 from django.db import get_creation_module, models |
|
107 data_types = get_creation_module().DATA_TYPES |
|
108 |
|
109 if not data_types: |
|
110 # This must be the "dummy" database backend, which means the user |
|
111 # hasn't set DATABASE_ENGINE. |
|
112 sys.stderr.write(style.ERROR("Error: Django doesn't know which syntax to use for your SQL statements,\n" + |
|
113 "because you haven't specified the DATABASE_ENGINE setting.\n" + |
|
114 "Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.\n")) |
|
115 sys.exit(1) |
|
116 |
|
117 # Get installed models, so we generate REFERENCES right. |
|
118 # We trim models from the current app so that the sqlreset command does not |
|
119 # generate invalid SQL (leaving models out of known_models is harmless, so |
|
120 # we can be conservative). |
|
121 app_models = models.get_models(app) |
|
122 final_output = [] |
|
123 known_models = set([model for model in _get_installed_models(_get_table_list()) if model not in app_models]) |
|
124 pending_references = {} |
|
125 |
|
126 for model in app_models: |
|
127 output, references = _get_sql_model_create(model, known_models) |
|
128 final_output.extend(output) |
|
129 for refto, refs in references.items(): |
|
130 pending_references.setdefault(refto,[]).extend(refs) |
|
131 final_output.extend(_get_sql_for_pending_references(model, pending_references)) |
|
132 # Keep track of the fact that we've created the table for this model. |
|
133 known_models.add(model) |
|
134 |
|
135 # Create the many-to-many join tables. |
|
136 for model in app_models: |
|
137 final_output.extend(_get_many_to_many_sql_for_model(model)) |
|
138 |
|
139 # Handle references to tables that are from other apps |
|
140 # but don't exist physically |
|
141 not_installed_models = set(pending_references.keys()) |
|
142 if not_installed_models: |
|
143 alter_sql = [] |
|
144 for model in not_installed_models: |
|
145 alter_sql.extend(['-- ' + sql for sql in |
|
146 _get_sql_for_pending_references(model, pending_references)]) |
|
147 if alter_sql: |
|
148 final_output.append('-- The following references should be added but depend on non-existent tables:') |
|
149 final_output.extend(alter_sql) |
|
150 |
|
151 return final_output |
|
152 get_sql_create.help_doc = "Prints the CREATE TABLE SQL statements for the given app name(s)." |
|
153 get_sql_create.args = APP_ARGS |
|
154 |
|
155 def _get_sql_model_create(model, known_models=set()): |
|
156 """ |
|
157 Get the SQL required to create a single model. |
|
158 |
|
159 Returns list_of_sql, pending_references_dict |
|
160 """ |
|
161 from django.db import backend, get_creation_module, models |
|
162 data_types = get_creation_module().DATA_TYPES |
|
163 |
|
164 opts = model._meta |
|
165 final_output = [] |
|
166 table_output = [] |
|
167 pending_references = {} |
|
168 for f in opts.fields: |
|
169 if isinstance(f, (models.ForeignKey, models.OneToOneField)): |
|
170 rel_field = f.rel.get_related_field() |
|
171 data_type = get_rel_data_type(rel_field) |
|
172 else: |
|
173 rel_field = f |
|
174 data_type = f.get_internal_type() |
|
175 col_type = data_types[data_type] |
|
176 if col_type is not None: |
|
177 # Make the definition (e.g. 'foo VARCHAR(30)') for this field. |
|
178 field_output = [style.SQL_FIELD(backend.quote_name(f.column)), |
|
179 style.SQL_COLTYPE(col_type % rel_field.__dict__)] |
|
180 field_output.append(style.SQL_KEYWORD('%sNULL' % (not f.null and 'NOT ' or ''))) |
|
181 if f.unique: |
|
182 field_output.append(style.SQL_KEYWORD('UNIQUE')) |
|
183 if f.primary_key: |
|
184 field_output.append(style.SQL_KEYWORD('PRIMARY KEY')) |
|
185 if f.rel: |
|
186 if f.rel.to in known_models: |
|
187 field_output.append(style.SQL_KEYWORD('REFERENCES') + ' ' + \ |
|
188 style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)) + ' (' + \ |
|
189 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.get_field(f.rel.field_name).column)) + ')' + |
|
190 backend.get_deferrable_sql() |
|
191 ) |
|
192 else: |
|
193 # We haven't yet created the table to which this field |
|
194 # is related, so save it for later. |
|
195 pr = pending_references.setdefault(f.rel.to, []).append((model, f)) |
|
196 table_output.append(' '.join(field_output)) |
|
197 if opts.order_with_respect_to: |
|
198 table_output.append(style.SQL_FIELD(backend.quote_name('_order')) + ' ' + \ |
|
199 style.SQL_COLTYPE(data_types['IntegerField']) + ' ' + \ |
|
200 style.SQL_KEYWORD('NULL')) |
|
201 for field_constraints in opts.unique_together: |
|
202 table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ |
|
203 ", ".join([backend.quote_name(style.SQL_FIELD(opts.get_field(f).column)) for f in field_constraints])) |
|
204 |
|
205 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(backend.quote_name(opts.db_table)) + ' ('] |
|
206 for i, line in enumerate(table_output): # Combine and add commas. |
|
207 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) |
|
208 full_statement.append(');') |
|
209 final_output.append('\n'.join(full_statement)) |
|
210 |
|
211 return final_output, pending_references |
|
212 |
|
213 def _get_sql_for_pending_references(model, pending_references): |
|
214 """ |
|
215 Get any ALTER TABLE statements to add constraints after the fact. |
|
216 """ |
|
217 from django.db import backend, get_creation_module |
|
218 data_types = get_creation_module().DATA_TYPES |
|
219 |
|
220 final_output = [] |
|
221 if backend.supports_constraints: |
|
222 opts = model._meta |
|
223 if model in pending_references: |
|
224 for rel_class, f in pending_references[model]: |
|
225 rel_opts = rel_class._meta |
|
226 r_table = rel_opts.db_table |
|
227 r_col = f.column |
|
228 table = opts.db_table |
|
229 col = opts.get_field(f.rel.field_name).column |
|
230 # For MySQL, r_name must be unique in the first 64 characters. |
|
231 # So we are careful with character usage here. |
|
232 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) |
|
233 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ |
|
234 (backend.quote_name(r_table), r_name, |
|
235 backend.quote_name(r_col), backend.quote_name(table), backend.quote_name(col), |
|
236 backend.get_deferrable_sql())) |
|
237 del pending_references[model] |
|
238 return final_output |
|
239 |
|
240 def _get_many_to_many_sql_for_model(model): |
|
241 from django.db import backend, get_creation_module |
|
242 from django.db.models import GenericRel |
|
243 |
|
244 data_types = get_creation_module().DATA_TYPES |
|
245 |
|
246 opts = model._meta |
|
247 final_output = [] |
|
248 for f in opts.many_to_many: |
|
249 if not isinstance(f.rel, GenericRel): |
|
250 table_output = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + \ |
|
251 style.SQL_TABLE(backend.quote_name(f.m2m_db_table())) + ' ('] |
|
252 table_output.append(' %s %s %s,' % \ |
|
253 (style.SQL_FIELD(backend.quote_name('id')), |
|
254 style.SQL_COLTYPE(data_types['AutoField']), |
|
255 style.SQL_KEYWORD('NOT NULL PRIMARY KEY'))) |
|
256 table_output.append(' %s %s %s %s (%s)%s,' % \ |
|
257 (style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), |
|
258 style.SQL_COLTYPE(data_types[get_rel_data_type(opts.pk)] % opts.pk.__dict__), |
|
259 style.SQL_KEYWORD('NOT NULL REFERENCES'), |
|
260 style.SQL_TABLE(backend.quote_name(opts.db_table)), |
|
261 style.SQL_FIELD(backend.quote_name(opts.pk.column)), |
|
262 backend.get_deferrable_sql())) |
|
263 table_output.append(' %s %s %s %s (%s)%s,' % \ |
|
264 (style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())), |
|
265 style.SQL_COLTYPE(data_types[get_rel_data_type(f.rel.to._meta.pk)] % f.rel.to._meta.pk.__dict__), |
|
266 style.SQL_KEYWORD('NOT NULL REFERENCES'), |
|
267 style.SQL_TABLE(backend.quote_name(f.rel.to._meta.db_table)), |
|
268 style.SQL_FIELD(backend.quote_name(f.rel.to._meta.pk.column)), |
|
269 backend.get_deferrable_sql())) |
|
270 table_output.append(' %s (%s, %s)' % \ |
|
271 (style.SQL_KEYWORD('UNIQUE'), |
|
272 style.SQL_FIELD(backend.quote_name(f.m2m_column_name())), |
|
273 style.SQL_FIELD(backend.quote_name(f.m2m_reverse_name())))) |
|
274 table_output.append(');') |
|
275 final_output.append('\n'.join(table_output)) |
|
276 return final_output |
|
277 |
|
278 def get_sql_delete(app): |
|
279 "Returns a list of the DROP TABLE SQL statements for the given app." |
|
280 from django.db import backend, connection, models, get_introspection_module |
|
281 introspection = get_introspection_module() |
|
282 |
|
283 # This should work even if a connection isn't available |
|
284 try: |
|
285 cursor = connection.cursor() |
|
286 except: |
|
287 cursor = None |
|
288 |
|
289 # Figure out which tables already exist |
|
290 if cursor: |
|
291 table_names = introspection.get_table_list(cursor) |
|
292 else: |
|
293 table_names = [] |
|
294 |
|
295 output = [] |
|
296 |
|
297 # Output DROP TABLE statements for standard application tables. |
|
298 to_delete = set() |
|
299 |
|
300 references_to_delete = {} |
|
301 app_models = models.get_models(app) |
|
302 for model in app_models: |
|
303 if cursor and model._meta.db_table in table_names: |
|
304 # The table exists, so it needs to be dropped |
|
305 opts = model._meta |
|
306 for f in opts.fields: |
|
307 if f.rel and f.rel.to not in to_delete: |
|
308 references_to_delete.setdefault(f.rel.to, []).append( (model, f) ) |
|
309 |
|
310 to_delete.add(model) |
|
311 |
|
312 for model in app_models: |
|
313 if cursor and model._meta.db_table in table_names: |
|
314 # Drop the table now |
|
315 output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), |
|
316 style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) |
|
317 if backend.supports_constraints and references_to_delete.has_key(model): |
|
318 for rel_class, f in references_to_delete[model]: |
|
319 table = rel_class._meta.db_table |
|
320 col = f.column |
|
321 r_table = model._meta.db_table |
|
322 r_col = model._meta.get_field(f.rel.field_name).column |
|
323 output.append('%s %s %s %s;' % \ |
|
324 (style.SQL_KEYWORD('ALTER TABLE'), |
|
325 style.SQL_TABLE(backend.quote_name(table)), |
|
326 style.SQL_KEYWORD(backend.get_drop_foreignkey_sql()), |
|
327 style.SQL_FIELD(backend.quote_name('%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))))))) |
|
328 del references_to_delete[model] |
|
329 |
|
330 # Output DROP TABLE statements for many-to-many tables. |
|
331 for model in app_models: |
|
332 opts = model._meta |
|
333 for f in opts.many_to_many: |
|
334 if cursor and f.m2m_db_table() in table_names: |
|
335 output.append("%s %s;" % (style.SQL_KEYWORD('DROP TABLE'), |
|
336 style.SQL_TABLE(backend.quote_name(f.m2m_db_table())))) |
|
337 |
|
338 app_label = app_models[0]._meta.app_label |
|
339 |
|
340 # Close database connection explicitly, in case this output is being piped |
|
341 # directly into a database client, to avoid locking issues. |
|
342 if cursor: |
|
343 cursor.close() |
|
344 connection.close() |
|
345 |
|
346 return output[::-1] # Reverse it, to deal with table dependencies. |
|
347 get_sql_delete.help_doc = "Prints the DROP TABLE SQL statements for the given app name(s)." |
|
348 get_sql_delete.args = APP_ARGS |
|
349 |
|
350 def get_sql_reset(app): |
|
351 "Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module." |
|
352 return get_sql_delete(app) + get_sql_all(app) |
|
353 get_sql_reset.help_doc = "Prints the DROP TABLE SQL, then the CREATE TABLE SQL, for the given app name(s)." |
|
354 get_sql_reset.args = APP_ARGS |
|
355 |
|
356 def get_sql_flush(): |
|
357 "Returns a list of the SQL statements used to flush the database" |
|
358 from django.db import backend |
|
359 statements = backend.get_sql_flush(style, _get_table_list(), _get_sequence_list()) |
|
360 return statements |
|
361 get_sql_flush.help_doc = "Returns a list of the SQL statements required to return all tables in the database to the state they were in just after they were installed." |
|
362 get_sql_flush.args = '' |
|
363 |
|
364 def get_custom_sql_for_model(model): |
|
365 from django.db import models |
|
366 from django.conf import settings |
|
367 |
|
368 opts = model._meta |
|
369 app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql')) |
|
370 output = [] |
|
371 |
|
372 # Some backends can't execute more than one SQL statement at a time, |
|
373 # so split into separate statements. |
|
374 statements = re.compile(r";[ \t]*$", re.M) |
|
375 |
|
376 # Find custom SQL, if it's available. |
|
377 sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)), |
|
378 os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] |
|
379 for sql_file in sql_files: |
|
380 if os.path.exists(sql_file): |
|
381 fp = open(sql_file, 'U') |
|
382 for statement in statements.split(fp.read()): |
|
383 # Remove any comments from the file |
|
384 statement = re.sub(r"--.*[\n\Z]", "", statement) |
|
385 if statement.strip(): |
|
386 output.append(statement + ";") |
|
387 fp.close() |
|
388 |
|
389 return output |
|
390 |
|
391 def get_custom_sql(app): |
|
392 "Returns a list of the custom table modifying SQL statements for the given app." |
|
393 from django.db.models import get_models |
|
394 output = [] |
|
395 |
|
396 app_models = get_models(app) |
|
397 app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql')) |
|
398 |
|
399 for model in app_models: |
|
400 output.extend(get_custom_sql_for_model(model)) |
|
401 |
|
402 return output |
|
403 get_custom_sql.help_doc = "Prints the custom table modifying SQL statements for the given app name(s)." |
|
404 get_custom_sql.args = APP_ARGS |
|
405 |
|
406 def get_sql_initial_data(apps): |
|
407 "Returns a list of the initial INSERT SQL statements for the given app." |
|
408 return style.ERROR("This action has been renamed. Try './manage.py sqlcustom %s'." % ' '.join(apps and apps or ['app1', 'app2'])) |
|
409 get_sql_initial_data.help_doc = "RENAMED: see 'sqlcustom'" |
|
410 get_sql_initial_data.args = '' |
|
411 |
|
412 def get_sql_sequence_reset(app): |
|
413 "Returns a list of the SQL statements to reset PostgreSQL sequences for the given app." |
|
414 from django.db import backend, models |
|
415 output = [] |
|
416 for model in models.get_models(app): |
|
417 for f in model._meta.fields: |
|
418 if isinstance(f, models.AutoField): |
|
419 output.append("%s setval('%s', (%s max(%s) %s %s));" % \ |
|
420 (style.SQL_KEYWORD('SELECT'), |
|
421 style.SQL_FIELD('%s_%s_seq' % (model._meta.db_table, f.column)), |
|
422 style.SQL_KEYWORD('SELECT'), |
|
423 style.SQL_FIELD(backend.quote_name(f.column)), |
|
424 style.SQL_KEYWORD('FROM'), |
|
425 style.SQL_TABLE(backend.quote_name(model._meta.db_table)))) |
|
426 break # Only one AutoField is allowed per model, so don't bother continuing. |
|
427 for f in model._meta.many_to_many: |
|
428 output.append("%s setval('%s', (%s max(%s) %s %s));" % \ |
|
429 (style.SQL_KEYWORD('SELECT'), |
|
430 style.SQL_FIELD('%s_id_seq' % f.m2m_db_table()), |
|
431 style.SQL_KEYWORD('SELECT'), |
|
432 style.SQL_FIELD(backend.quote_name('id')), |
|
433 style.SQL_KEYWORD('FROM'), |
|
434 style.SQL_TABLE(f.m2m_db_table()))) |
|
435 return output |
|
436 get_sql_sequence_reset.help_doc = "Prints the SQL statements for resetting PostgreSQL sequences for the given app name(s)." |
|
437 get_sql_sequence_reset.args = APP_ARGS |
|
438 |
|
439 def get_sql_indexes(app): |
|
440 "Returns a list of the CREATE INDEX SQL statements for all models in the given app." |
|
441 from django.db import models |
|
442 output = [] |
|
443 for model in models.get_models(app): |
|
444 output.extend(get_sql_indexes_for_model(model)) |
|
445 return output |
|
446 get_sql_indexes.help_doc = "Prints the CREATE INDEX SQL statements for the given model module name(s)." |
|
447 get_sql_indexes.args = APP_ARGS |
|
448 |
|
449 def get_sql_indexes_for_model(model): |
|
450 "Returns the CREATE INDEX SQL statements for a single model" |
|
451 from django.db import backend |
|
452 output = [] |
|
453 |
|
454 for f in model._meta.fields: |
|
455 if f.db_index: |
|
456 unique = f.unique and 'UNIQUE ' or '' |
|
457 output.append( |
|
458 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ |
|
459 style.SQL_TABLE('%s_%s' % (model._meta.db_table, f.column)) + ' ' + \ |
|
460 style.SQL_KEYWORD('ON') + ' ' + \ |
|
461 style.SQL_TABLE(backend.quote_name(model._meta.db_table)) + ' ' + \ |
|
462 "(%s);" % style.SQL_FIELD(backend.quote_name(f.column)) |
|
463 ) |
|
464 return output |
|
465 |
|
466 def get_sql_all(app): |
|
467 "Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module." |
|
468 return get_sql_create(app) + get_custom_sql(app) + get_sql_indexes(app) |
|
469 get_sql_all.help_doc = "Prints the CREATE TABLE, initial-data and CREATE INDEX SQL statements for the given model module name(s)." |
|
470 get_sql_all.args = APP_ARGS |
|
471 |
|
472 def _emit_post_sync_signal(created_models, verbosity, interactive): |
|
473 from django.db import models |
|
474 from django.dispatch import dispatcher |
|
475 # Emit the post_sync signal for every application. |
|
476 for app in models.get_apps(): |
|
477 app_name = app.__name__.split('.')[-2] |
|
478 if verbosity >= 2: |
|
479 print "Running post-sync handlers for application", app_name |
|
480 dispatcher.send(signal=models.signals.post_syncdb, sender=app, |
|
481 app=app, created_models=created_models, |
|
482 verbosity=verbosity, interactive=interactive) |
|
483 |
|
484 def syncdb(verbosity=1, interactive=True): |
|
485 "Creates the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." |
|
486 from django.db import connection, transaction, models, get_creation_module |
|
487 from django.conf import settings |
|
488 |
|
489 disable_termcolors() |
|
490 |
|
491 # First, try validating the models. |
|
492 _check_for_validation_errors() |
|
493 |
|
494 # Import the 'management' module within each installed app, to register |
|
495 # dispatcher events. |
|
496 for app_name in settings.INSTALLED_APPS: |
|
497 try: |
|
498 __import__(app_name + '.management', {}, {}, ['']) |
|
499 except ImportError: |
|
500 pass |
|
501 |
|
502 data_types = get_creation_module().DATA_TYPES |
|
503 |
|
504 cursor = connection.cursor() |
|
505 |
|
506 # Get a list of all existing database tables, |
|
507 # so we know what needs to be added. |
|
508 table_list = _get_table_list() |
|
509 |
|
510 # Get a list of already installed *models* so that references work right. |
|
511 seen_models = _get_installed_models(table_list) |
|
512 created_models = set() |
|
513 pending_references = {} |
|
514 |
|
515 # Create the tables for each model |
|
516 for app in models.get_apps(): |
|
517 app_name = app.__name__.split('.')[-2] |
|
518 model_list = models.get_models(app) |
|
519 for model in model_list: |
|
520 # Create the model's database table, if it doesn't already exist. |
|
521 if verbosity >= 2: |
|
522 print "Processing %s.%s model" % (app_name, model._meta.object_name) |
|
523 if model._meta.db_table in table_list: |
|
524 continue |
|
525 sql, references = _get_sql_model_create(model, seen_models) |
|
526 seen_models.add(model) |
|
527 created_models.add(model) |
|
528 for refto, refs in references.items(): |
|
529 pending_references.setdefault(refto, []).extend(refs) |
|
530 sql.extend(_get_sql_for_pending_references(model, pending_references)) |
|
531 if verbosity >= 1: |
|
532 print "Creating table %s" % model._meta.db_table |
|
533 for statement in sql: |
|
534 cursor.execute(statement) |
|
535 table_list.append(model._meta.db_table) |
|
536 |
|
537 # Create the m2m tables. This must be done after all tables have been created |
|
538 # to ensure that all referred tables will exist. |
|
539 for app in models.get_apps(): |
|
540 app_name = app.__name__.split('.')[-2] |
|
541 model_list = models.get_models(app) |
|
542 for model in model_list: |
|
543 if model in created_models: |
|
544 sql = _get_many_to_many_sql_for_model(model) |
|
545 if sql: |
|
546 if verbosity >= 2: |
|
547 print "Creating many-to-many tables for %s.%s model" % (app_name, model._meta.object_name) |
|
548 for statement in sql: |
|
549 cursor.execute(statement) |
|
550 |
|
551 transaction.commit_unless_managed() |
|
552 |
|
553 # Send the post_syncdb signal, so individual apps can do whatever they need |
|
554 # to do at this point. |
|
555 _emit_post_sync_signal(created_models, verbosity, interactive) |
|
556 |
|
557 # Install custom SQL for the app (but only if this |
|
558 # is a model we've just created) |
|
559 for app in models.get_apps(): |
|
560 for model in models.get_models(app): |
|
561 if model in created_models: |
|
562 custom_sql = get_custom_sql_for_model(model) |
|
563 if custom_sql: |
|
564 if verbosity >= 1: |
|
565 print "Installing custom SQL for %s.%s model" % (app_name, model._meta.object_name) |
|
566 try: |
|
567 for sql in custom_sql: |
|
568 cursor.execute(sql) |
|
569 except Exception, e: |
|
570 sys.stderr.write("Failed to install custom SQL for %s.%s model: %s" % \ |
|
571 (app_name, model._meta.object_name, e)) |
|
572 transaction.rollback_unless_managed() |
|
573 else: |
|
574 transaction.commit_unless_managed() |
|
575 |
|
576 # Install SQL indicies for all newly created models |
|
577 for app in models.get_apps(): |
|
578 app_name = app.__name__.split('.')[-2] |
|
579 for model in models.get_models(app): |
|
580 if model in created_models: |
|
581 index_sql = get_sql_indexes_for_model(model) |
|
582 if index_sql: |
|
583 if verbosity >= 1: |
|
584 print "Installing index for %s.%s model" % (app_name, model._meta.object_name) |
|
585 try: |
|
586 for sql in index_sql: |
|
587 cursor.execute(sql) |
|
588 except Exception, e: |
|
589 sys.stderr.write("Failed to install index for %s.%s model: %s" % \ |
|
590 (app_name, model._meta.object_name, e)) |
|
591 transaction.rollback_unless_managed() |
|
592 else: |
|
593 transaction.commit_unless_managed() |
|
594 |
|
595 # Install the 'initialdata' fixture, using format discovery |
|
596 load_data(['initial_data'], verbosity=verbosity) |
|
597 syncdb.help_doc = "Create the database tables for all apps in INSTALLED_APPS whose tables haven't already been created." |
|
598 syncdb.args = '[--verbosity] [--interactive]' |
|
599 |
|
600 def get_admin_index(app): |
|
601 "Returns admin-index template snippet (in list form) for the given app." |
|
602 from django.utils.text import capfirst |
|
603 from django.db.models import get_models |
|
604 output = [] |
|
605 app_models = get_models(app) |
|
606 app_label = app_models[0]._meta.app_label |
|
607 output.append('{%% if perms.%s %%}' % app_label) |
|
608 output.append('<div class="module"><h2>%s</h2><table>' % app_label.title()) |
|
609 for model in app_models: |
|
610 if model._meta.admin: |
|
611 output.append(MODULE_TEMPLATE % { |
|
612 'app': app_label, |
|
613 'mod': model._meta.module_name, |
|
614 'name': capfirst(model._meta.verbose_name_plural), |
|
615 'addperm': model._meta.get_add_permission(), |
|
616 'changeperm': model._meta.get_change_permission(), |
|
617 }) |
|
618 output.append('</table></div>') |
|
619 output.append('{% endif %}') |
|
620 return output |
|
621 get_admin_index.help_doc = "Prints the admin-index template snippet for the given app name(s)." |
|
622 get_admin_index.args = APP_ARGS |
|
623 |
|
624 def _module_to_dict(module, omittable=lambda k: k.startswith('_')): |
|
625 "Converts a module namespace to a Python dictionary. Used by get_settings_diff." |
|
626 return dict([(k, repr(v)) for k, v in module.__dict__.items() if not omittable(k)]) |
|
627 |
|
628 def diffsettings(): |
|
629 """ |
|
630 Displays differences between the current settings.py and Django's |
|
631 default settings. Settings that don't appear in the defaults are |
|
632 followed by "###". |
|
633 """ |
|
634 # Inspired by Postfix's "postconf -n". |
|
635 from django.conf import settings, global_settings |
|
636 |
|
637 user_settings = _module_to_dict(settings._target) |
|
638 default_settings = _module_to_dict(global_settings) |
|
639 |
|
640 output = [] |
|
641 keys = user_settings.keys() |
|
642 keys.sort() |
|
643 for key in keys: |
|
644 if key not in default_settings: |
|
645 output.append("%s = %s ###" % (key, user_settings[key])) |
|
646 elif user_settings[key] != default_settings[key]: |
|
647 output.append("%s = %s" % (key, user_settings[key])) |
|
648 print '\n'.join(output) |
|
649 diffsettings.args = "" |
|
650 |
|
651 def reset(app, interactive=True): |
|
652 "Executes the equivalent of 'get_sql_reset' in the current database." |
|
653 from django.db import connection, transaction |
|
654 from django.conf import settings |
|
655 app_name = app.__name__.split('.')[-2] |
|
656 |
|
657 disable_termcolors() |
|
658 |
|
659 # First, try validating the models. |
|
660 _check_for_validation_errors(app) |
|
661 sql_list = get_sql_reset(app) |
|
662 |
|
663 if interactive: |
|
664 confirm = raw_input(""" |
|
665 You have requested a database reset. |
|
666 This will IRREVERSIBLY DESTROY any data for |
|
667 the "%s" application in the database "%s". |
|
668 Are you sure you want to do this? |
|
669 |
|
670 Type 'yes' to continue, or 'no' to cancel: """ % (app_name, settings.DATABASE_NAME)) |
|
671 else: |
|
672 confirm = 'yes' |
|
673 |
|
674 if confirm == 'yes': |
|
675 try: |
|
676 cursor = connection.cursor() |
|
677 for sql in sql_list: |
|
678 cursor.execute(sql) |
|
679 except Exception, e: |
|
680 sys.stderr.write(style.ERROR("""Error: %s couldn't be reset. Possible reasons: |
|
681 * The database isn't running or isn't configured correctly. |
|
682 * At least one of the database tables doesn't exist. |
|
683 * The SQL was invalid. |
|
684 Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run. |
|
685 The full error: """ % (app_name, app_name)) + style.ERROR_OUTPUT(str(e)) + '\n') |
|
686 transaction.rollback_unless_managed() |
|
687 sys.exit(1) |
|
688 transaction.commit_unless_managed() |
|
689 else: |
|
690 print "Reset cancelled." |
|
691 reset.help_doc = "Executes ``sqlreset`` for the given app(s) in the current database." |
|
692 reset.args = '[--interactive]' + APP_ARGS |
|
693 |
|
694 def flush(verbosity=1, interactive=True): |
|
695 "Returns all tables in the database to the same state they were in immediately after syncdb." |
|
696 from django.conf import settings |
|
697 from django.db import connection, transaction, models |
|
698 from django.dispatch import dispatcher |
|
699 |
|
700 disable_termcolors() |
|
701 |
|
702 # First, try validating the models. |
|
703 _check_for_validation_errors() |
|
704 |
|
705 # Import the 'management' module within each installed app, to register |
|
706 # dispatcher events. |
|
707 for app_name in settings.INSTALLED_APPS: |
|
708 try: |
|
709 __import__(app_name + '.management', {}, {}, ['']) |
|
710 except ImportError: |
|
711 pass |
|
712 |
|
713 sql_list = get_sql_flush() |
|
714 |
|
715 if interactive: |
|
716 confirm = raw_input(""" |
|
717 You have requested a flush of the database. |
|
718 This will IRREVERSIBLY DESTROY all data currently in the database, |
|
719 and return each table to the state it was in after syncdb. |
|
720 Are you sure you want to do this? |
|
721 |
|
722 Type 'yes' to continue, or 'no' to cancel: """) |
|
723 else: |
|
724 confirm = 'yes' |
|
725 |
|
726 if confirm == 'yes': |
|
727 try: |
|
728 cursor = connection.cursor() |
|
729 for sql in sql_list: |
|
730 cursor.execute(sql) |
|
731 except Exception, e: |
|
732 sys.stderr.write(style.ERROR("""Error: Database %s couldn't be flushed. Possible reasons: |
|
733 * The database isn't running or isn't configured correctly. |
|
734 * At least one of the expected database tables doesn't exist. |
|
735 * The SQL was invalid. |
|
736 Hint: Look at the output of 'django-admin.py sqlflush'. That's the SQL this command wasn't able to run. |
|
737 The full error: """ % settings.DATABASE_NAME + style.ERROR_OUTPUT(str(e)) + '\n')) |
|
738 transaction.rollback_unless_managed() |
|
739 sys.exit(1) |
|
740 transaction.commit_unless_managed() |
|
741 |
|
742 # Emit the post sync signal. This allows individual |
|
743 # applications to respond as if the database had been |
|
744 # sync'd from scratch. |
|
745 _emit_post_sync_signal(models.get_models(), verbosity, interactive) |
|
746 |
|
747 # Reinstall the initial_data fixture |
|
748 load_data(['initial_data'], verbosity=verbosity) |
|
749 |
|
750 else: |
|
751 print "Flush cancelled." |
|
752 flush.help_doc = "Executes ``sqlflush`` on the current database." |
|
753 flush.args = '[--verbosity] [--interactive]' |
|
754 |
|
755 def _start_helper(app_or_project, name, directory, other_name=''): |
|
756 other = {'project': 'app', 'app': 'project'}[app_or_project] |
|
757 if not _is_valid_dir_name(name): |
|
758 sys.stderr.write(style.ERROR("Error: %r is not a valid %s name. Please use only numbers, letters and underscores.\n" % (name, app_or_project))) |
|
759 sys.exit(1) |
|
760 top_dir = os.path.join(directory, name) |
|
761 try: |
|
762 os.mkdir(top_dir) |
|
763 except OSError, e: |
|
764 sys.stderr.write(style.ERROR("Error: %s\n" % e)) |
|
765 sys.exit(1) |
|
766 template_dir = PROJECT_TEMPLATE_DIR % app_or_project |
|
767 for d, subdirs, files in os.walk(template_dir): |
|
768 relative_dir = d[len(template_dir)+1:].replace('%s_name' % app_or_project, name) |
|
769 if relative_dir: |
|
770 os.mkdir(os.path.join(top_dir, relative_dir)) |
|
771 for i, subdir in enumerate(subdirs): |
|
772 if subdir.startswith('.'): |
|
773 del subdirs[i] |
|
774 for f in files: |
|
775 if f.endswith('.pyc'): |
|
776 continue |
|
777 path_old = os.path.join(d, f) |
|
778 path_new = os.path.join(top_dir, relative_dir, f.replace('%s_name' % app_or_project, name)) |
|
779 fp_old = open(path_old, 'r') |
|
780 fp_new = open(path_new, 'w') |
|
781 fp_new.write(fp_old.read().replace('{{ %s_name }}' % app_or_project, name).replace('{{ %s_name }}' % other, other_name)) |
|
782 fp_old.close() |
|
783 fp_new.close() |
|
784 try: |
|
785 shutil.copymode(path_old, path_new) |
|
786 except OSError: |
|
787 sys.stderr.write(style.NOTICE("Notice: Couldn't set permission bits on %s. You're probably using an uncommon filesystem setup. No problem.\n" % path_new)) |
|
788 |
|
789 def startproject(project_name, directory): |
|
790 "Creates a Django project for the given project_name in the given directory." |
|
791 from random import choice |
|
792 if project_name in INVALID_PROJECT_NAMES: |
|
793 sys.stderr.write(style.ERROR("Error: '%r' conflicts with the name of an existing Python module and cannot be used as a project name. Please try another name.\n" % project_name)) |
|
794 sys.exit(1) |
|
795 _start_helper('project', project_name, directory) |
|
796 # Create a random SECRET_KEY hash, and put it in the main settings. |
|
797 main_settings_file = os.path.join(directory, project_name, 'settings.py') |
|
798 settings_contents = open(main_settings_file, 'r').read() |
|
799 fp = open(main_settings_file, 'w') |
|
800 secret_key = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) |
|
801 settings_contents = re.sub(r"(?<=SECRET_KEY = ')'", secret_key + "'", settings_contents) |
|
802 fp.write(settings_contents) |
|
803 fp.close() |
|
804 startproject.help_doc = "Creates a Django project directory structure for the given project name in the current directory." |
|
805 startproject.args = "[projectname]" |
|
806 |
|
807 def startapp(app_name, directory): |
|
808 "Creates a Django app for the given app_name in the given directory." |
|
809 # Determine the project_name a bit naively -- by looking at the name of |
|
810 # the parent directory. |
|
811 project_dir = os.path.normpath(os.path.join(directory, '..')) |
|
812 project_name = os.path.basename(project_dir) |
|
813 if app_name == os.path.basename(directory): |
|
814 sys.stderr.write(style.ERROR("Error: You cannot create an app with the same name (%r) as your project.\n" % app_name)) |
|
815 sys.exit(1) |
|
816 _start_helper('app', app_name, directory, project_name) |
|
817 startapp.help_doc = "Creates a Django app directory structure for the given app name in the current directory." |
|
818 startapp.args = "[appname]" |
|
819 |
|
820 def inspectdb(): |
|
821 "Generator that introspects the tables in the given database name and returns a Django model, one line at a time." |
|
822 from django.db import connection, get_introspection_module |
|
823 import keyword |
|
824 |
|
825 introspection_module = get_introspection_module() |
|
826 |
|
827 table2model = lambda table_name: table_name.title().replace('_', '') |
|
828 |
|
829 cursor = connection.cursor() |
|
830 yield "# This is an auto-generated Django model module." |
|
831 yield "# You'll have to do the following manually to clean this up:" |
|
832 yield "# * Rearrange models' order" |
|
833 yield "# * Make sure each model has one field with primary_key=True" |
|
834 yield "# Feel free to rename the models, but don't rename db_table values or field names." |
|
835 yield "#" |
|
836 yield "# Also note: You'll have to insert the output of 'django-admin.py sqlcustom [appname]'" |
|
837 yield "# into your database." |
|
838 yield '' |
|
839 yield 'from django.db import models' |
|
840 yield '' |
|
841 for table_name in introspection_module.get_table_list(cursor): |
|
842 yield 'class %s(models.Model):' % table2model(table_name) |
|
843 try: |
|
844 relations = introspection_module.get_relations(cursor, table_name) |
|
845 except NotImplementedError: |
|
846 relations = {} |
|
847 try: |
|
848 indexes = introspection_module.get_indexes(cursor, table_name) |
|
849 except NotImplementedError: |
|
850 indexes = {} |
|
851 for i, row in enumerate(introspection_module.get_table_description(cursor, table_name)): |
|
852 att_name = row[0] |
|
853 comment_notes = [] # Holds Field notes, to be displayed in a Python comment. |
|
854 extra_params = {} # Holds Field parameters such as 'db_column'. |
|
855 |
|
856 if ' ' in att_name: |
|
857 extra_params['db_column'] = att_name |
|
858 att_name = att_name.replace(' ', '') |
|
859 comment_notes.append('Field renamed to remove spaces.') |
|
860 if keyword.iskeyword(att_name): |
|
861 extra_params['db_column'] = att_name |
|
862 att_name += '_field' |
|
863 comment_notes.append('Field renamed because it was a Python reserved word.') |
|
864 |
|
865 if relations.has_key(i): |
|
866 rel_to = relations[i][1] == table_name and "'self'" or table2model(relations[i][1]) |
|
867 field_type = 'ForeignKey(%s' % rel_to |
|
868 if att_name.endswith('_id'): |
|
869 att_name = att_name[:-3] |
|
870 else: |
|
871 extra_params['db_column'] = att_name |
|
872 else: |
|
873 try: |
|
874 field_type = introspection_module.DATA_TYPES_REVERSE[row[1]] |
|
875 except KeyError: |
|
876 field_type = 'TextField' |
|
877 comment_notes.append('This field type is a guess.') |
|
878 |
|
879 # This is a hook for DATA_TYPES_REVERSE to return a tuple of |
|
880 # (field_type, extra_params_dict). |
|
881 if type(field_type) is tuple: |
|
882 field_type, new_params = field_type |
|
883 extra_params.update(new_params) |
|
884 |
|
885 # Add maxlength for all CharFields. |
|
886 if field_type == 'CharField' and row[3]: |
|
887 extra_params['maxlength'] = row[3] |
|
888 |
|
889 if field_type == 'FloatField': |
|
890 extra_params['max_digits'] = row[4] |
|
891 extra_params['decimal_places'] = row[5] |
|
892 |
|
893 # Add primary_key and unique, if necessary. |
|
894 column_name = extra_params.get('db_column', att_name) |
|
895 if column_name in indexes: |
|
896 if indexes[column_name]['primary_key']: |
|
897 extra_params['primary_key'] = True |
|
898 elif indexes[column_name]['unique']: |
|
899 extra_params['unique'] = True |
|
900 |
|
901 field_type += '(' |
|
902 |
|
903 # Don't output 'id = meta.AutoField(primary_key=True)', because |
|
904 # that's assumed if it doesn't exist. |
|
905 if att_name == 'id' and field_type == 'AutoField(' and extra_params == {'primary_key': True}: |
|
906 continue |
|
907 |
|
908 # Add 'null' and 'blank', if the 'null_ok' flag was present in the |
|
909 # table description. |
|
910 if row[6]: # If it's NULL... |
|
911 extra_params['blank'] = True |
|
912 if not field_type in ('TextField(', 'CharField('): |
|
913 extra_params['null'] = True |
|
914 |
|
915 field_desc = '%s = models.%s' % (att_name, field_type) |
|
916 if extra_params: |
|
917 if not field_desc.endswith('('): |
|
918 field_desc += ', ' |
|
919 field_desc += ', '.join(['%s=%r' % (k, v) for k, v in extra_params.items()]) |
|
920 field_desc += ')' |
|
921 if comment_notes: |
|
922 field_desc += ' # ' + ' '.join(comment_notes) |
|
923 yield ' %s' % field_desc |
|
924 yield ' class Meta:' |
|
925 yield ' db_table = %r' % table_name |
|
926 yield '' |
|
927 inspectdb.help_doc = "Introspects the database tables in the given database and outputs a Django model module." |
|
928 inspectdb.args = "" |
|
929 |
|
930 class ModelErrorCollection: |
|
931 def __init__(self, outfile=sys.stdout): |
|
932 self.errors = [] |
|
933 self.outfile = outfile |
|
934 |
|
935 def add(self, context, error): |
|
936 self.errors.append((context, error)) |
|
937 self.outfile.write(style.ERROR("%s: %s\n" % (context, error))) |
|
938 |
|
939 def get_validation_errors(outfile, app=None): |
|
940 """ |
|
941 Validates all models that are part of the specified app. If no app name is provided, |
|
942 validates all models of all installed apps. Writes errors, if any, to outfile. |
|
943 Returns number of errors. |
|
944 """ |
|
945 from django.conf import settings |
|
946 from django.db import models, connection |
|
947 from django.db.models.loading import get_app_errors |
|
948 from django.db.models.fields.related import RelatedObject |
|
949 |
|
950 e = ModelErrorCollection(outfile) |
|
951 |
|
952 for (app_name, error) in get_app_errors().items(): |
|
953 e.add(app_name, error) |
|
954 |
|
955 for cls in models.get_models(app): |
|
956 opts = cls._meta |
|
957 |
|
958 # Do field-specific validation. |
|
959 for f in opts.fields: |
|
960 if f.name == 'id' and not f.primary_key and opts.pk.name == 'id': |
|
961 e.add(opts, '"%s": You can\'t use "id" as a field name, because each model automatically gets an "id" field if none of the fields have primary_key=True. You need to either remove/rename your "id" field or add primary_key=True to a field.' % f.name) |
|
962 if isinstance(f, models.CharField) and f.maxlength in (None, 0): |
|
963 e.add(opts, '"%s": CharFields require a "maxlength" attribute.' % f.name) |
|
964 if isinstance(f, models.FloatField): |
|
965 if f.decimal_places is None: |
|
966 e.add(opts, '"%s": FloatFields require a "decimal_places" attribute.' % f.name) |
|
967 if f.max_digits is None: |
|
968 e.add(opts, '"%s": FloatFields require a "max_digits" attribute.' % f.name) |
|
969 if isinstance(f, models.FileField) and not f.upload_to: |
|
970 e.add(opts, '"%s": FileFields require an "upload_to" attribute.' % f.name) |
|
971 if isinstance(f, models.ImageField): |
|
972 try: |
|
973 from PIL import Image |
|
974 except ImportError: |
|
975 e.add(opts, '"%s": To use ImageFields, you need to install the Python Imaging Library. Get it at http://www.pythonware.com/products/pil/ .' % f.name) |
|
976 if f.prepopulate_from is not None and type(f.prepopulate_from) not in (list, tuple): |
|
977 e.add(opts, '"%s": prepopulate_from should be a list or tuple.' % f.name) |
|
978 if f.choices: |
|
979 if not hasattr(f.choices, '__iter__'): |
|
980 e.add(opts, '"%s": "choices" should be iterable (e.g., a tuple or list).' % f.name) |
|
981 else: |
|
982 for c in f.choices: |
|
983 if not type(c) in (tuple, list) or len(c) != 2: |
|
984 e.add(opts, '"%s": "choices" should be a sequence of two-tuples.' % f.name) |
|
985 if f.db_index not in (None, True, False): |
|
986 e.add(opts, '"%s": "db_index" should be either None, True or False.' % f.name) |
|
987 |
|
988 # Check that maxlength <= 255 if using older MySQL versions. |
|
989 if settings.DATABASE_ENGINE == 'mysql': |
|
990 db_version = connection.get_server_version() |
|
991 if db_version < (5, 0, 3) and isinstance(f, (models.CharField, models.CommaSeparatedIntegerField, models.SlugField)) and f.maxlength > 255: |
|
992 e.add(opts, '"%s": %s cannot have a "maxlength" greater than 255 when you are using a version of MySQL prior to 5.0.3 (you are using %s).' % (f.name, f.__class__.__name__, '.'.join([str(n) for n in db_version[:3]]))) |
|
993 |
|
994 # Check to see if the related field will clash with any |
|
995 # existing fields, m2m fields, m2m related objects or related objects |
|
996 if f.rel: |
|
997 rel_opts = f.rel.to._meta |
|
998 if f.rel.to not in models.get_models(): |
|
999 e.add(opts, "'%s' has relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) |
|
1000 |
|
1001 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() |
|
1002 rel_query_name = f.related_query_name() |
|
1003 for r in rel_opts.fields: |
|
1004 if r.name == rel_name: |
|
1005 e.add(opts, "Accessor for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1006 if r.name == rel_query_name: |
|
1007 e.add(opts, "Reverse query name for field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1008 for r in rel_opts.many_to_many: |
|
1009 if r.name == rel_name: |
|
1010 e.add(opts, "Accessor for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1011 if r.name == rel_query_name: |
|
1012 e.add(opts, "Reverse query name for field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1013 for r in rel_opts.get_all_related_many_to_many_objects(): |
|
1014 if r.get_accessor_name() == rel_name: |
|
1015 e.add(opts, "Accessor for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1016 if r.get_accessor_name() == rel_query_name: |
|
1017 e.add(opts, "Reverse query name for field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1018 for r in rel_opts.get_all_related_objects(): |
|
1019 if r.field is not f: |
|
1020 if r.get_accessor_name() == rel_name: |
|
1021 e.add(opts, "Accessor for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1022 if r.get_accessor_name() == rel_query_name: |
|
1023 e.add(opts, "Reverse query name for field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1024 |
|
1025 |
|
1026 for i, f in enumerate(opts.many_to_many): |
|
1027 # Check to see if the related m2m field will clash with any |
|
1028 # existing fields, m2m fields, m2m related objects or related objects |
|
1029 rel_opts = f.rel.to._meta |
|
1030 if f.rel.to not in models.get_models(): |
|
1031 e.add(opts, "'%s' has m2m relation with model %s, which has not been installed" % (f.name, rel_opts.object_name)) |
|
1032 |
|
1033 rel_name = RelatedObject(f.rel.to, cls, f).get_accessor_name() |
|
1034 rel_query_name = f.related_query_name() |
|
1035 # If rel_name is none, there is no reverse accessor. |
|
1036 # (This only occurs for symmetrical m2m relations to self). |
|
1037 # If this is the case, there are no clashes to check for this field, as |
|
1038 # there are no reverse descriptors for this field. |
|
1039 if rel_name is not None: |
|
1040 for r in rel_opts.fields: |
|
1041 if r.name == rel_name: |
|
1042 e.add(opts, "Accessor for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1043 if r.name == rel_query_name: |
|
1044 e.add(opts, "Reverse query name for m2m field '%s' clashes with field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1045 for r in rel_opts.many_to_many: |
|
1046 if r.name == rel_name: |
|
1047 e.add(opts, "Accessor for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1048 if r.name == rel_query_name: |
|
1049 e.add(opts, "Reverse query name for m2m field '%s' clashes with m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.name, f.name)) |
|
1050 for r in rel_opts.get_all_related_many_to_many_objects(): |
|
1051 if r.field is not f: |
|
1052 if r.get_accessor_name() == rel_name: |
|
1053 e.add(opts, "Accessor for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1054 if r.get_accessor_name() == rel_query_name: |
|
1055 e.add(opts, "Reverse query name for m2m field '%s' clashes with related m2m field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1056 for r in rel_opts.get_all_related_objects(): |
|
1057 if r.get_accessor_name() == rel_name: |
|
1058 e.add(opts, "Accessor for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1059 if r.get_accessor_name() == rel_query_name: |
|
1060 e.add(opts, "Reverse query name for m2m field '%s' clashes with related field '%s.%s'. Add a related_name argument to the definition for '%s'." % (f.name, rel_opts.object_name, r.get_accessor_name(), f.name)) |
|
1061 |
|
1062 # Check admin attribute. |
|
1063 if opts.admin is not None: |
|
1064 if not isinstance(opts.admin, models.AdminOptions): |
|
1065 e.add(opts, '"admin" attribute, if given, must be set to a models.AdminOptions() instance.') |
|
1066 else: |
|
1067 # list_display |
|
1068 if not isinstance(opts.admin.list_display, (list, tuple)): |
|
1069 e.add(opts, '"admin.list_display", if given, must be set to a list or tuple.') |
|
1070 else: |
|
1071 for fn in opts.admin.list_display: |
|
1072 try: |
|
1073 f = opts.get_field(fn) |
|
1074 except models.FieldDoesNotExist: |
|
1075 if not hasattr(cls, fn): |
|
1076 e.add(opts, '"admin.list_display" refers to %r, which isn\'t an attribute, method or property.' % fn) |
|
1077 else: |
|
1078 if isinstance(f, models.ManyToManyField): |
|
1079 e.add(opts, '"admin.list_display" doesn\'t support ManyToManyFields (%r).' % fn) |
|
1080 # list_display_links |
|
1081 if opts.admin.list_display_links and not opts.admin.list_display: |
|
1082 e.add(opts, '"admin.list_display" must be defined for "admin.list_display_links" to be used.') |
|
1083 if not isinstance(opts.admin.list_display_links, (list, tuple)): |
|
1084 e.add(opts, '"admin.list_display_links", if given, must be set to a list or tuple.') |
|
1085 else: |
|
1086 for fn in opts.admin.list_display_links: |
|
1087 try: |
|
1088 f = opts.get_field(fn) |
|
1089 except models.FieldDoesNotExist: |
|
1090 if not hasattr(cls, fn): |
|
1091 e.add(opts, '"admin.list_display_links" refers to %r, which isn\'t an attribute, method or property.' % fn) |
|
1092 if fn not in opts.admin.list_display: |
|
1093 e.add(opts, '"admin.list_display_links" refers to %r, which is not defined in "admin.list_display".' % fn) |
|
1094 # list_filter |
|
1095 if not isinstance(opts.admin.list_filter, (list, tuple)): |
|
1096 e.add(opts, '"admin.list_filter", if given, must be set to a list or tuple.') |
|
1097 else: |
|
1098 for fn in opts.admin.list_filter: |
|
1099 try: |
|
1100 f = opts.get_field(fn) |
|
1101 except models.FieldDoesNotExist: |
|
1102 e.add(opts, '"admin.list_filter" refers to %r, which isn\'t a field.' % fn) |
|
1103 # date_hierarchy |
|
1104 if opts.admin.date_hierarchy: |
|
1105 try: |
|
1106 f = opts.get_field(opts.admin.date_hierarchy) |
|
1107 except models.FieldDoesNotExist: |
|
1108 e.add(opts, '"admin.date_hierarchy" refers to %r, which isn\'t a field.' % opts.admin.date_hierarchy) |
|
1109 |
|
1110 # Check ordering attribute. |
|
1111 if opts.ordering: |
|
1112 for field_name in opts.ordering: |
|
1113 if field_name == '?': continue |
|
1114 if field_name.startswith('-'): |
|
1115 field_name = field_name[1:] |
|
1116 if opts.order_with_respect_to and field_name == '_order': |
|
1117 continue |
|
1118 if '.' in field_name: continue # Skip ordering in the format 'table.field'. |
|
1119 try: |
|
1120 opts.get_field(field_name, many_to_many=False) |
|
1121 except models.FieldDoesNotExist: |
|
1122 e.add(opts, '"ordering" refers to "%s", a field that doesn\'t exist.' % field_name) |
|
1123 |
|
1124 # Check core=True, if needed. |
|
1125 for related in opts.get_followed_related_objects(): |
|
1126 if not related.edit_inline: |
|
1127 continue |
|
1128 try: |
|
1129 for f in related.opts.fields: |
|
1130 if f.core: |
|
1131 raise StopIteration |
|
1132 e.add(related.opts, "At least one field in %s should have core=True, because it's being edited inline by %s.%s." % (related.opts.object_name, opts.module_name, opts.object_name)) |
|
1133 except StopIteration: |
|
1134 pass |
|
1135 |
|
1136 # Check unique_together. |
|
1137 for ut in opts.unique_together: |
|
1138 for field_name in ut: |
|
1139 try: |
|
1140 f = opts.get_field(field_name, many_to_many=True) |
|
1141 except models.FieldDoesNotExist: |
|
1142 e.add(opts, '"unique_together" refers to %s, a field that doesn\'t exist. Check your syntax.' % field_name) |
|
1143 else: |
|
1144 if isinstance(f.rel, models.ManyToManyRel): |
|
1145 e.add(opts, '"unique_together" refers to %s. ManyToManyFields are not supported in unique_together.' % f.name) |
|
1146 |
|
1147 return len(e.errors) |
|
1148 |
|
1149 def validate(outfile=sys.stdout, silent_success=False): |
|
1150 "Validates all installed models." |
|
1151 try: |
|
1152 num_errors = get_validation_errors(outfile) |
|
1153 if silent_success and num_errors == 0: |
|
1154 return |
|
1155 outfile.write('%s error%s found.\n' % (num_errors, num_errors != 1 and 's' or '')) |
|
1156 except ImproperlyConfigured: |
|
1157 outfile.write("Skipping validation because things aren't configured properly.") |
|
1158 validate.args = '' |
|
1159 |
|
1160 def _check_for_validation_errors(app=None): |
|
1161 """Check that an app has no validation errors, and exit with errors if it does.""" |
|
1162 try: |
|
1163 from cStringIO import StringIO |
|
1164 except ImportError: |
|
1165 from StringIO import StringIO |
|
1166 s = StringIO() |
|
1167 num_errors = get_validation_errors(s, app) |
|
1168 if num_errors: |
|
1169 if app: |
|
1170 sys.stderr.write(style.ERROR("Error: %s couldn't be installed, because there were errors in your model:\n" % app)) |
|
1171 else: |
|
1172 sys.stderr.write(style.ERROR("Error: Couldn't install apps, because there were errors in one or more models:\n")) |
|
1173 s.seek(0) |
|
1174 sys.stderr.write(s.read()) |
|
1175 sys.exit(1) |
|
1176 |
|
1177 def runserver(addr, port, use_reloader=True, admin_media_dir=''): |
|
1178 "Starts a lightweight Web server for development." |
|
1179 from django.core.servers.basehttp import run, AdminMediaHandler, WSGIServerException |
|
1180 from django.core.handlers.wsgi import WSGIHandler |
|
1181 if not addr: |
|
1182 addr = '127.0.0.1' |
|
1183 if not port.isdigit(): |
|
1184 sys.stderr.write(style.ERROR("Error: %r is not a valid port number.\n" % port)) |
|
1185 sys.exit(1) |
|
1186 quit_command = sys.platform == 'win32' and 'CTRL-BREAK' or 'CONTROL-C' |
|
1187 def inner_run(): |
|
1188 from django.conf import settings |
|
1189 print "Validating models..." |
|
1190 validate() |
|
1191 print "\nDjango version %s, using settings %r" % (get_version(), settings.SETTINGS_MODULE) |
|
1192 print "Development server is running at http://%s:%s/" % (addr, port) |
|
1193 print "Quit the server with %s." % quit_command |
|
1194 try: |
|
1195 import django |
|
1196 path = admin_media_dir or django.__path__[0] + '/contrib/admin/media' |
|
1197 handler = AdminMediaHandler(WSGIHandler(), path) |
|
1198 run(addr, int(port), handler) |
|
1199 except WSGIServerException, e: |
|
1200 # Use helpful error messages instead of ugly tracebacks. |
|
1201 ERRORS = { |
|
1202 13: "You don't have permission to access that port.", |
|
1203 98: "That port is already in use.", |
|
1204 99: "That IP address can't be assigned-to.", |
|
1205 } |
|
1206 try: |
|
1207 error_text = ERRORS[e.args[0].args[0]] |
|
1208 except (AttributeError, KeyError): |
|
1209 error_text = str(e) |
|
1210 sys.stderr.write(style.ERROR("Error: %s" % error_text) + '\n') |
|
1211 sys.exit(1) |
|
1212 except KeyboardInterrupt: |
|
1213 sys.exit(0) |
|
1214 if use_reloader: |
|
1215 from django.utils import autoreload |
|
1216 autoreload.main(inner_run) |
|
1217 else: |
|
1218 inner_run() |
|
1219 runserver.args = '[--noreload] [--adminmedia=ADMIN_MEDIA_PATH] [optional port number, or ipaddr:port]' |
|
1220 |
|
1221 def createcachetable(tablename): |
|
1222 "Creates the table needed to use the SQL cache backend" |
|
1223 from django.db import backend, connection, transaction, get_creation_module, models |
|
1224 data_types = get_creation_module().DATA_TYPES |
|
1225 fields = ( |
|
1226 # "key" is a reserved word in MySQL, so use "cache_key" instead. |
|
1227 models.CharField(name='cache_key', maxlength=255, unique=True, primary_key=True), |
|
1228 models.TextField(name='value'), |
|
1229 models.DateTimeField(name='expires', db_index=True), |
|
1230 ) |
|
1231 table_output = [] |
|
1232 index_output = [] |
|
1233 for f in fields: |
|
1234 field_output = [backend.quote_name(f.name), data_types[f.get_internal_type()] % f.__dict__] |
|
1235 field_output.append("%sNULL" % (not f.null and "NOT " or "")) |
|
1236 if f.unique: |
|
1237 field_output.append("UNIQUE") |
|
1238 if f.primary_key: |
|
1239 field_output.append("PRIMARY KEY") |
|
1240 if f.db_index: |
|
1241 unique = f.unique and "UNIQUE " or "" |
|
1242 index_output.append("CREATE %sINDEX %s_%s ON %s (%s);" % \ |
|
1243 (unique, tablename, f.name, backend.quote_name(tablename), |
|
1244 backend.quote_name(f.name))) |
|
1245 table_output.append(" ".join(field_output)) |
|
1246 full_statement = ["CREATE TABLE %s (" % backend.quote_name(tablename)] |
|
1247 for i, line in enumerate(table_output): |
|
1248 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) |
|
1249 full_statement.append(');') |
|
1250 curs = connection.cursor() |
|
1251 curs.execute("\n".join(full_statement)) |
|
1252 for statement in index_output: |
|
1253 curs.execute(statement) |
|
1254 transaction.commit_unless_managed() |
|
1255 createcachetable.args = "[tablename]" |
|
1256 |
|
1257 def run_shell(use_plain=False): |
|
1258 "Runs a Python interactive interpreter. Tries to use IPython, if it's available." |
|
1259 # XXX: (Temporary) workaround for ticket #1796: force early loading of all |
|
1260 # models from installed apps. |
|
1261 from django.db.models.loading import get_models |
|
1262 loaded_models = get_models() |
|
1263 |
|
1264 try: |
|
1265 if use_plain: |
|
1266 # Don't bother loading IPython, because the user wants plain Python. |
|
1267 raise ImportError |
|
1268 import IPython |
|
1269 # Explicitly pass an empty list as arguments, because otherwise IPython |
|
1270 # would use sys.argv from this script. |
|
1271 shell = IPython.Shell.IPShell(argv=[]) |
|
1272 shell.mainloop() |
|
1273 except ImportError: |
|
1274 import code |
|
1275 try: # Try activating rlcompleter, because it's handy. |
|
1276 import readline |
|
1277 except ImportError: |
|
1278 pass |
|
1279 else: |
|
1280 # We don't have to wrap the following import in a 'try', because |
|
1281 # we already know 'readline' was imported successfully. |
|
1282 import rlcompleter |
|
1283 readline.parse_and_bind("tab:complete") |
|
1284 code.interact() |
|
1285 run_shell.args = '[--plain]' |
|
1286 |
|
1287 def dbshell(): |
|
1288 "Runs the command-line client for the current DATABASE_ENGINE." |
|
1289 from django.db import runshell |
|
1290 runshell() |
|
1291 dbshell.args = "" |
|
1292 |
|
1293 def runfcgi(args): |
|
1294 "Runs this project as a FastCGI application. Requires flup." |
|
1295 from django.conf import settings |
|
1296 from django.utils import translation |
|
1297 # Activate the current language, because it won't get activated later. |
|
1298 try: |
|
1299 translation.activate(settings.LANGUAGE_CODE) |
|
1300 except AttributeError: |
|
1301 pass |
|
1302 from django.core.servers.fastcgi import runfastcgi |
|
1303 runfastcgi(args) |
|
1304 runfcgi.args = '[various KEY=val options, use `runfcgi help` for help]' |
|
1305 |
|
1306 def test(app_labels, verbosity=1): |
|
1307 "Runs the test suite for the specified applications" |
|
1308 from django.conf import settings |
|
1309 from django.db.models import get_app, get_apps |
|
1310 |
|
1311 if len(app_labels) == 0: |
|
1312 app_list = get_apps() |
|
1313 else: |
|
1314 app_list = [get_app(app_label) for app_label in app_labels] |
|
1315 |
|
1316 test_path = settings.TEST_RUNNER.split('.') |
|
1317 # Allow for Python 2.5 relative paths |
|
1318 if len(test_path) > 1: |
|
1319 test_module_name = '.'.join(test_path[:-1]) |
|
1320 else: |
|
1321 test_module_name = '.' |
|
1322 test_module = __import__(test_module_name, {}, {}, test_path[-1]) |
|
1323 test_runner = getattr(test_module, test_path[-1]) |
|
1324 |
|
1325 failures = test_runner(app_list, verbosity) |
|
1326 if failures: |
|
1327 sys.exit(failures) |
|
1328 |
|
1329 test.help_doc = 'Runs the test suite for the specified applications, or the entire site if no apps are specified' |
|
1330 test.args = '[--verbosity] ' + APP_ARGS |
|
1331 |
|
1332 def load_data(fixture_labels, verbosity=1): |
|
1333 "Installs the provided fixture file(s) as data in the database." |
|
1334 from django.db.models import get_apps |
|
1335 from django.core import serializers |
|
1336 from django.db import connection, transaction |
|
1337 from django.conf import settings |
|
1338 import sys |
|
1339 |
|
1340 # Keep a count of the installed objects and fixtures |
|
1341 count = [0,0] |
|
1342 |
|
1343 humanize = lambda dirname: dirname and "'%s'" % dirname or 'absolute path' |
|
1344 |
|
1345 # Get a cursor (even though we don't need one yet). This has |
|
1346 # the side effect of initializing the test database (if |
|
1347 # it isn't already initialized). |
|
1348 cursor = connection.cursor() |
|
1349 |
|
1350 # Start transaction management. All fixtures are installed in a |
|
1351 # single transaction to ensure that all references are resolved. |
|
1352 transaction.commit_unless_managed() |
|
1353 transaction.enter_transaction_management() |
|
1354 transaction.managed(True) |
|
1355 |
|
1356 app_fixtures = [os.path.join(os.path.dirname(app.__file__),'fixtures') for app in get_apps()] |
|
1357 for fixture_label in fixture_labels: |
|
1358 if verbosity > 0: |
|
1359 print "Loading '%s' fixtures..." % fixture_label |
|
1360 for fixture_dir in app_fixtures + list(settings.FIXTURE_DIRS) + ['']: |
|
1361 if verbosity > 1: |
|
1362 print "Checking %s for fixtures..." % humanize(fixture_dir) |
|
1363 parts = fixture_label.split('.') |
|
1364 if len(parts) == 1: |
|
1365 fixture_name = fixture_label |
|
1366 formats = serializers.get_serializer_formats() |
|
1367 else: |
|
1368 fixture_name, format = '.'.join(parts[:-1]), parts[-1] |
|
1369 formats = [format] |
|
1370 |
|
1371 label_found = False |
|
1372 for format in formats: |
|
1373 serializer = serializers.get_serializer(format) |
|
1374 if verbosity > 1: |
|
1375 print "Trying %s for %s fixture '%s'..." % \ |
|
1376 (humanize(fixture_dir), format, fixture_name) |
|
1377 try: |
|
1378 full_path = os.path.join(fixture_dir, '.'.join([fixture_name, format])) |
|
1379 fixture = open(full_path, 'r') |
|
1380 if label_found: |
|
1381 fixture.close() |
|
1382 print style.ERROR("Multiple fixtures named '%s' in %s. Aborting." % |
|
1383 (fixture_name, humanize(fixture_dir))) |
|
1384 transaction.rollback() |
|
1385 transaction.leave_transaction_management() |
|
1386 return |
|
1387 else: |
|
1388 count[1] += 1 |
|
1389 if verbosity > 0: |
|
1390 print "Installing %s fixture '%s' from %s." % \ |
|
1391 (format, fixture_name, humanize(fixture_dir)) |
|
1392 try: |
|
1393 objects = serializers.deserialize(format, fixture) |
|
1394 for obj in objects: |
|
1395 count[0] += 1 |
|
1396 obj.save() |
|
1397 label_found = True |
|
1398 except Exception, e: |
|
1399 fixture.close() |
|
1400 sys.stderr.write( |
|
1401 style.ERROR("Problem installing fixture '%s': %s\n" % |
|
1402 (full_path, str(e)))) |
|
1403 transaction.rollback() |
|
1404 transaction.leave_transaction_management() |
|
1405 return |
|
1406 fixture.close() |
|
1407 except: |
|
1408 if verbosity > 1: |
|
1409 print "No %s fixture '%s' in %s." % \ |
|
1410 (format, fixture_name, humanize(fixture_dir)) |
|
1411 if count[0] == 0: |
|
1412 if verbosity > 0: |
|
1413 print "No fixtures found." |
|
1414 else: |
|
1415 if verbosity > 0: |
|
1416 print "Installed %d object(s) from %d fixture(s)" % tuple(count) |
|
1417 transaction.commit() |
|
1418 transaction.leave_transaction_management() |
|
1419 |
|
1420 load_data.help_doc = 'Installs the named fixture(s) in the database' |
|
1421 load_data.args = "[--verbosity] fixture, fixture, ..." |
|
1422 |
|
1423 def dump_data(app_labels, format='json', indent=None): |
|
1424 "Output the current contents of the database as a fixture of the given format" |
|
1425 from django.db.models import get_app, get_apps, get_models |
|
1426 from django.core import serializers |
|
1427 |
|
1428 if len(app_labels) == 0: |
|
1429 app_list = get_apps() |
|
1430 else: |
|
1431 app_list = [get_app(app_label) for app_label in app_labels] |
|
1432 |
|
1433 # Check that the serialization format exists; this is a shortcut to |
|
1434 # avoid collating all the objects and _then_ failing. |
|
1435 try: |
|
1436 serializers.get_serializer(format) |
|
1437 except KeyError: |
|
1438 sys.stderr.write(style.ERROR("Unknown serialization format: %s\n" % format)) |
|
1439 |
|
1440 objects = [] |
|
1441 for app in app_list: |
|
1442 for model in get_models(app): |
|
1443 objects.extend(model.objects.all()) |
|
1444 try: |
|
1445 return serializers.serialize(format, objects, indent=indent) |
|
1446 except Exception, e: |
|
1447 sys.stderr.write(style.ERROR("Unable to serialize database: %s\n" % e)) |
|
1448 dump_data.help_doc = 'Output the contents of the database as a fixture of the given format' |
|
1449 dump_data.args = '[--format]' + APP_ARGS |
|
1450 |
|
1451 # Utilities for command-line script |
|
1452 |
|
1453 DEFAULT_ACTION_MAPPING = { |
|
1454 'adminindex': get_admin_index, |
|
1455 'createcachetable' : createcachetable, |
|
1456 'dbshell': dbshell, |
|
1457 'diffsettings': diffsettings, |
|
1458 'dumpdata': dump_data, |
|
1459 'flush': flush, |
|
1460 'inspectdb': inspectdb, |
|
1461 'loaddata': load_data, |
|
1462 'reset': reset, |
|
1463 'runfcgi': runfcgi, |
|
1464 'runserver': runserver, |
|
1465 'shell': run_shell, |
|
1466 'sql': get_sql_create, |
|
1467 'sqlall': get_sql_all, |
|
1468 'sqlclear': get_sql_delete, |
|
1469 'sqlcustom': get_custom_sql, |
|
1470 'sqlflush': get_sql_flush, |
|
1471 'sqlindexes': get_sql_indexes, |
|
1472 'sqlinitialdata': get_sql_initial_data, |
|
1473 'sqlreset': get_sql_reset, |
|
1474 'sqlsequencereset': get_sql_sequence_reset, |
|
1475 'startapp': startapp, |
|
1476 'startproject': startproject, |
|
1477 'syncdb': syncdb, |
|
1478 'validate': validate, |
|
1479 'test':test, |
|
1480 } |
|
1481 |
|
1482 NO_SQL_TRANSACTION = ( |
|
1483 'adminindex', |
|
1484 'createcachetable', |
|
1485 'dbshell', |
|
1486 'diffsettings', |
|
1487 'reset', |
|
1488 'sqlindexes', |
|
1489 'syncdb', |
|
1490 ) |
|
1491 |
|
1492 class DjangoOptionParser(OptionParser): |
|
1493 def print_usage_and_exit(self): |
|
1494 self.print_help(sys.stderr) |
|
1495 sys.exit(1) |
|
1496 |
|
1497 def get_usage(action_mapping): |
|
1498 """ |
|
1499 Returns a usage string. Doesn't do the options stuff, because optparse |
|
1500 takes care of that. |
|
1501 """ |
|
1502 usage = ["%prog action [options]\nactions:"] |
|
1503 available_actions = action_mapping.keys() |
|
1504 available_actions.sort() |
|
1505 for a in available_actions: |
|
1506 func = action_mapping[a] |
|
1507 usage.append(" %s %s" % (a, func.args)) |
|
1508 usage.extend(textwrap.wrap(getattr(func, 'help_doc', textwrap.dedent(func.__doc__.strip())), initial_indent=' ', subsequent_indent=' ')) |
|
1509 usage.append("") |
|
1510 return '\n'.join(usage[:-1]) # Cut off last list element, an empty space. |
|
1511 |
|
1512 def print_error(msg, cmd): |
|
1513 sys.stderr.write(style.ERROR('Error: %s' % msg) + '\nRun "%s --help" for help.\n' % cmd) |
|
1514 sys.exit(1) |
|
1515 |
|
1516 def execute_from_command_line(action_mapping=DEFAULT_ACTION_MAPPING, argv=None): |
|
1517 # Use sys.argv if we've not passed in a custom argv |
|
1518 if argv is None: |
|
1519 argv = sys.argv |
|
1520 |
|
1521 # Parse the command-line arguments. optparse handles the dirty work. |
|
1522 parser = DjangoOptionParser(usage=get_usage(action_mapping), version=get_version()) |
|
1523 parser.add_option('--settings', |
|
1524 help='Python path to settings module, e.g. "myproject.settings.main". If this isn\'t provided, the DJANGO_SETTINGS_MODULE environment variable will be used.') |
|
1525 parser.add_option('--pythonpath', |
|
1526 help='Lets you manually add a directory the Python path, e.g. "/home/djangoprojects/myproject".') |
|
1527 parser.add_option('--plain', action='store_true', dest='plain', |
|
1528 help='Tells Django to use plain Python, not IPython, for "shell" command.') |
|
1529 parser.add_option('--noinput', action='store_false', dest='interactive', default=True, |
|
1530 help='Tells Django to NOT prompt the user for input of any kind.') |
|
1531 parser.add_option('--noreload', action='store_false', dest='use_reloader', default=True, |
|
1532 help='Tells Django to NOT use the auto-reloader when running the development server.') |
|
1533 parser.add_option('--format', default='json', dest='format', |
|
1534 help='Specifies the output serialization format for fixtures') |
|
1535 parser.add_option('--indent', default=None, dest='indent', |
|
1536 type='int', help='Specifies the indent level to use when pretty-printing output') |
|
1537 parser.add_option('--verbosity', action='store', dest='verbosity', default='1', |
|
1538 type='choice', choices=['0', '1', '2'], |
|
1539 help='Verbosity level; 0=minimal output, 1=normal output, 2=all output'), |
|
1540 parser.add_option('--adminmedia', dest='admin_media_path', default='', help='Specifies the directory from which to serve admin media for runserver.'), |
|
1541 |
|
1542 options, args = parser.parse_args(argv[1:]) |
|
1543 |
|
1544 # Take care of options. |
|
1545 if options.settings: |
|
1546 os.environ['DJANGO_SETTINGS_MODULE'] = options.settings |
|
1547 if options.pythonpath: |
|
1548 sys.path.insert(0, options.pythonpath) |
|
1549 |
|
1550 # Run the appropriate action. Unfortunately, optparse can't handle |
|
1551 # positional arguments, so this has to parse/validate them. |
|
1552 try: |
|
1553 action = args[0] |
|
1554 except IndexError: |
|
1555 parser.print_usage_and_exit() |
|
1556 if not action_mapping.has_key(action): |
|
1557 print_error("Your action, %r, was invalid." % action, argv[0]) |
|
1558 |
|
1559 # Switch to English, because django-admin.py creates database content |
|
1560 # like permissions, and those shouldn't contain any translations. |
|
1561 # But only do this if we should have a working settings file. |
|
1562 if action not in ('startproject', 'startapp'): |
|
1563 from django.utils import translation |
|
1564 translation.activate('en-us') |
|
1565 |
|
1566 if action == 'shell': |
|
1567 action_mapping[action](options.plain is True) |
|
1568 elif action in ('validate', 'diffsettings', 'dbshell'): |
|
1569 action_mapping[action]() |
|
1570 elif action in ('flush', 'syncdb'): |
|
1571 action_mapping[action](int(options.verbosity), options.interactive) |
|
1572 elif action == 'inspectdb': |
|
1573 try: |
|
1574 for line in action_mapping[action](): |
|
1575 print line |
|
1576 except NotImplementedError: |
|
1577 sys.stderr.write(style.ERROR("Error: %r isn't supported for the currently selected database backend.\n" % action)) |
|
1578 sys.exit(1) |
|
1579 elif action == 'createcachetable': |
|
1580 try: |
|
1581 action_mapping[action](args[1]) |
|
1582 except IndexError: |
|
1583 parser.print_usage_and_exit() |
|
1584 elif action in ('test', 'loaddata'): |
|
1585 try: |
|
1586 action_mapping[action](args[1:], int(options.verbosity)) |
|
1587 except IndexError: |
|
1588 parser.print_usage_and_exit() |
|
1589 elif action == 'dumpdata': |
|
1590 try: |
|
1591 print action_mapping[action](args[1:], options.format, options.indent) |
|
1592 except IndexError: |
|
1593 parser.print_usage_and_exit() |
|
1594 elif action in ('startapp', 'startproject'): |
|
1595 try: |
|
1596 name = args[1] |
|
1597 except IndexError: |
|
1598 parser.print_usage_and_exit() |
|
1599 action_mapping[action](name, os.getcwd()) |
|
1600 elif action == 'runserver': |
|
1601 if len(args) < 2: |
|
1602 addr = '' |
|
1603 port = '8000' |
|
1604 else: |
|
1605 try: |
|
1606 addr, port = args[1].split(':') |
|
1607 except ValueError: |
|
1608 addr, port = '', args[1] |
|
1609 action_mapping[action](addr, port, options.use_reloader, options.admin_media_path) |
|
1610 elif action == 'runfcgi': |
|
1611 action_mapping[action](args[1:]) |
|
1612 elif action == 'sqlinitialdata': |
|
1613 print action_mapping[action](args[1:]) |
|
1614 elif action == 'sqlflush': |
|
1615 print '\n'.join(action_mapping[action]()) |
|
1616 else: |
|
1617 from django.db import models |
|
1618 validate(silent_success=True) |
|
1619 try: |
|
1620 mod_list = [models.get_app(app_label) for app_label in args[1:]] |
|
1621 except ImportError, e: |
|
1622 sys.stderr.write(style.ERROR("Error: %s. Are you sure your INSTALLED_APPS setting is correct?\n" % e)) |
|
1623 sys.exit(1) |
|
1624 if not mod_list: |
|
1625 parser.print_usage_and_exit() |
|
1626 if action not in NO_SQL_TRANSACTION: |
|
1627 print style.SQL_KEYWORD("BEGIN;") |
|
1628 for mod in mod_list: |
|
1629 if action == 'reset': |
|
1630 output = action_mapping[action](mod, options.interactive) |
|
1631 else: |
|
1632 output = action_mapping[action](mod) |
|
1633 if output: |
|
1634 print '\n'.join(output) |
|
1635 if action not in NO_SQL_TRANSACTION: |
|
1636 print style.SQL_KEYWORD("COMMIT;") |
|
1637 |
|
1638 def setup_environ(settings_mod): |
|
1639 """ |
|
1640 Configure the runtime environment. This can also be used by external |
|
1641 scripts wanting to set up a similar environment to manage.py. |
|
1642 """ |
|
1643 # Add this project to sys.path so that it's importable in the conventional |
|
1644 # way. For example, if this file (manage.py) lives in a directory |
|
1645 # "myproject", this code would add "/path/to/myproject" to sys.path. |
|
1646 project_directory = os.path.dirname(settings_mod.__file__) |
|
1647 project_name = os.path.basename(project_directory) |
|
1648 sys.path.append(os.path.join(project_directory, '..')) |
|
1649 project_module = __import__(project_name, {}, {}, ['']) |
|
1650 sys.path.pop() |
|
1651 |
|
1652 # Set DJANGO_SETTINGS_MODULE appropriately. |
|
1653 os.environ['DJANGO_SETTINGS_MODULE'] = '%s.settings' % project_name |
|
1654 return project_directory |
|
1655 |
|
1656 def execute_manager(settings_mod, argv=None): |
|
1657 project_directory = setup_environ(settings_mod) |
|
1658 action_mapping = DEFAULT_ACTION_MAPPING.copy() |
|
1659 |
|
1660 # Remove the "startproject" command from the action_mapping, because that's |
|
1661 # a django-admin.py command, not a manage.py command. |
|
1662 del action_mapping['startproject'] |
|
1663 |
|
1664 # Override the startapp handler so that it always uses the |
|
1665 # project_directory, not the current working directory (which is default). |
|
1666 action_mapping['startapp'] = lambda app_name, directory: startapp(app_name, project_directory) |
|
1667 action_mapping['startapp'].__doc__ = startapp.__doc__ |
|
1668 action_mapping['startapp'].help_doc = startapp.help_doc |
|
1669 action_mapping['startapp'].args = startapp.args |
|
1670 |
|
1671 # Run the django-admin.py command. |
|
1672 execute_from_command_line(action_mapping, argv) |