| 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(s |
|---|