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