| 1 |
from django.core.management.base import CommandError |
|---|
| 2 |
import os |
|---|
| 3 |
import re |
|---|
| 4 |
|
|---|
| 5 |
try: |
|---|
| 6 |
set |
|---|
| 7 |
except NameError: |
|---|
| 8 |
from sets import Set as set # Python 2.3 fallback |
|---|
| 9 |
|
|---|
| 10 |
def sql_create(app, style): |
|---|
| 11 |
"Returns a list of the CREATE TABLE SQL statements for the given app." |
|---|
| 12 |
from django.db import connection, models |
|---|
| 13 |
from django.conf import settings |
|---|
| 14 |
|
|---|
| 15 |
if settings.DATABASE_ENGINE == 'dummy': |
|---|
| 16 |
# This must be the "dummy" database backend, which means the user |
|---|
| 17 |
# hasn't set DATABASE_ENGINE. |
|---|
| 18 |
raise CommandError("Django doesn't know which syntax to use for your SQL statements,\n" + |
|---|
| 19 |
"because you haven't specified the DATABASE_ENGINE setting.\n" + |
|---|
| 20 |
"Edit your settings file and change DATABASE_ENGINE to something like 'postgresql' or 'mysql'.") |
|---|
| 21 |
|
|---|
| 22 |
# Get installed models, so we generate REFERENCES right. |
|---|
| 23 |
# We trim models from the current app so that the sqlreset command does not |
|---|
| 24 |
# generate invalid SQL (leaving models out of known_models is harmless, so |
|---|
| 25 |
# we can be conservative). |
|---|
| 26 |
app_models = models.get_models(app) |
|---|
| 27 |
final_output = [] |
|---|
| 28 |
tables = connection.introspection.table_names() |
|---|
| 29 |
known_models = set([model for model in connection.introspection.installed_models(tables) if model not in app_models]) |
|---|
| 30 |
pending_references = {} |
|---|
| 31 |
|
|---|
| 32 |
for model in app_models: |
|---|
| 33 |
output, references = connection.creation.sql_create_model(model, style, known_models) |
|---|
| 34 |
final_output.extend(output) |
|---|
| 35 |
for refto, refs in references.items(): |
|---|
| 36 |
pending_references.setdefault(refto, []).extend(refs) |
|---|
| 37 |
if refto in known_models: |
|---|
| 38 |
final_output.extend(connection.creation.sql_for_pending_references(refto, style, pending_references)) |
|---|
| 39 |
final_output.extend(connection.creation.sql_for_pending_references(model, style, pending_references)) |
|---|
| 40 |
# Keep track of the fact that we've created the table for this model. |
|---|
| 41 |
known_models.add(model) |
|---|
| 42 |
|
|---|
| 43 |
# Create the many-to-many join tables. |
|---|
| 44 |
for model in app_models: |
|---|
| 45 |
final_output.extend(connection.creation.sql_for_many_to_many(model, style)) |
|---|
| 46 |
|
|---|
| 47 |
# Handle references to tables that are from other apps |
|---|
| 48 |
# but don't exist physically. |
|---|
| 49 |
not_installed_models = set(pending_references.keys()) |
|---|
| 50 |
if not_installed_models: |
|---|
| 51 |
alter_sql = [] |
|---|
| 52 |
for model in not_installed_models: |
|---|
| 53 |
alter_sql.extend(['-- ' + sql for sql in |
|---|
| 54 |
connection.creation.sql_for_pending_references(model, style, pending_references)]) |
|---|
| 55 |
if alter_sql: |
|---|
| 56 |
final_output.append('-- The following references should be added but depend on non-existent tables:') |
|---|
| 57 |
final_output.extend(alter_sql) |
|---|
| 58 |
|
|---|
| 59 |
return final_output |
|---|
| 60 |
|
|---|
| 61 |
def sql_delete(app, style): |
|---|
| 62 |
"Returns a list of the DROP TABLE SQL statements for the given app." |
|---|
| 63 |
from django.db import connection, models |
|---|
| 64 |
from django.db.backends.util import truncate_name |
|---|
| 65 |
from django.contrib.contenttypes import generic |
|---|
| 66 |
|
|---|
| 67 |
# This should work even if a connection isn't available |
|---|
| 68 |
try: |
|---|
| 69 |
cursor = connection.cursor() |
|---|
| 70 |
except: |
|---|
| 71 |
cursor = None |
|---|
| 72 |
|
|---|
| 73 |
# Figure out which tables already exist |
|---|
| 74 |
if cursor: |
|---|
| 75 |
table_names = connection.introspection.get_table_list(cursor) |
|---|
| 76 |
else: |
|---|
| 77 |
table_names = [] |
|---|
| 78 |
|
|---|
| 79 |
output = [] |
|---|
| 80 |
|
|---|
| 81 |
# Output DROP TABLE statements for standard application tables. |
|---|
| 82 |
to_delete = set() |
|---|
| 83 |
|
|---|
| 84 |
references_to_delete = {} |
|---|
| 85 |
app_models = models.get_models(app) |
|---|
| 86 |
for model in app_models: |
|---|
| 87 |
if cursor and connection.introspection.table_name_converter(model._meta.db_table) in table_names: |
|---|
| 88 |
# The table exists, so it needs to be dropped |
|---|
| 89 |
opts = model._meta |
|---|
| 90 |
for f in opts.local_fields: |
|---|
| 91 |
if f.rel and f.rel.to not in to_delete: |
|---|
| 92 |
references_to_delete.setdefault(f.rel.to, []).append( (model, f) ) |
|---|
| 93 |
|
|---|
| 94 |
to_delete.add(model) |
|---|
| 95 |
|
|---|
| 96 |
for model in app_models: |
|---|
| 97 |
if connection.introspection.table_name_converter(model._meta.db_table) in table_names: |
|---|
| 98 |
output.extend(connection.creation.sql_destroy_model(model, references_to_delete, style)) |
|---|
| 99 |
|
|---|
| 100 |
# Output DROP TABLE statements for many-to-many tables. |
|---|
| 101 |
for model in app_models: |
|---|
| 102 |
opts = model._meta |
|---|
| 103 |
for f in opts.local_many_to_many: |
|---|
| 104 |
if cursor and connection.introspection.table_name_converter(f.m2m_db_table()) in table_names: |
|---|
| 105 |
output.extend(connection.creation.sql_destroy_many_to_many(model, f, style)) |
|---|
| 106 |
|
|---|
| 107 |
# Close database connection explicitly, in case this output is being piped |
|---|
| 108 |
# directly into a database client, to avoid locking issues. |
|---|
| 109 |
if cursor: |
|---|
| 110 |
cursor.close() |
|---|
| 111 |
connection.close() |
|---|
| 112 |
|
|---|
| 113 |
return output[::-1] # Reverse it, to deal with table dependencies. |
|---|
| 114 |
|
|---|
| 115 |
def sql_reset(app, style): |
|---|
| 116 |
"Returns a list of the DROP TABLE SQL, then the CREATE TABLE SQL, for the given module." |
|---|
| 117 |
return sql_delete(app, style) + sql_all(app, style) |
|---|
| 118 |
|
|---|
| 119 |
def sql_flush(style, only_django=False): |
|---|
| 120 |
""" |
|---|
| 121 |
Returns a list of the SQL statements used to flush the database. |
|---|
| 122 |
|
|---|
| 123 |
If only_django is True, then only table names that have associated Django |
|---|
| 124 |
models and are in INSTALLED_APPS will be included. |
|---|
| 125 |
""" |
|---|
| 126 |
from django.db import connection |
|---|
| 127 |
if only_django: |
|---|
| 128 |
tables = connection.introspection.django_table_names(only_existing=True) |
|---|
| 129 |
else: |
|---|
| 130 |
tables = connection.introspection.table_names() |
|---|
| 131 |
statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list()) |
|---|
| 132 |
return statements |
|---|
| 133 |
|
|---|
| 134 |
def sql_custom(app, style): |
|---|
| 135 |
"Returns a list of the custom table modifying SQL statements for the given app." |
|---|
| 136 |
from django.db.models import get_models |
|---|
| 137 |
output = [] |
|---|
| 138 |
|
|---|
| 139 |
app_models = get_models(app) |
|---|
| 140 |
app_dir = os.path.normpath(os.path.join(os.path.dirname(app.__file__), 'sql')) |
|---|
| 141 |
|
|---|
| 142 |
for model in app_models: |
|---|
| 143 |
output.extend(custom_sql_for_model(model, style)) |
|---|
| 144 |
|
|---|
| 145 |
return output |
|---|
| 146 |
|
|---|
| 147 |
def sql_indexes(app, style): |
|---|
| 148 |
"Returns a list of the CREATE INDEX SQL statements for all models in the given app." |
|---|
| 149 |
from django.db import connection, models |
|---|
| 150 |
output = [] |
|---|
| 151 |
for model in models.get_models(app): |
|---|
| 152 |
output.extend(connection.creation.sql_indexes_for_model(model, style)) |
|---|
| 153 |
return output |
|---|
| 154 |
|
|---|
| 155 |
def sql_all(app, style): |
|---|
| 156 |
"Returns a list of CREATE TABLE SQL, initial-data inserts, and CREATE INDEX SQL for the given module." |
|---|
| 157 |
return sql_create(app, style) + sql_custom(app, style) + sql_indexes(app, style) |
|---|
| 158 |
|
|---|
| 159 |
def custom_sql_for_model(model, style): |
|---|
| 160 |
from django.db import models |
|---|
| 161 |
from django.conf import settings |
|---|
| 162 |
|
|---|
| 163 |
opts = model._meta |
|---|
| 164 |
app_dir = os.path.normpath(os.path.join(os.path.dirname(models.get_app(model._meta.app_label).__file__), 'sql')) |
|---|
| 165 |
output = [] |
|---|
| 166 |
|
|---|
| 167 |
# Post-creation SQL should come before any initial SQL data is loaded. |
|---|
| 168 |
# However, this should not be done for fields that are part of a a parent |
|---|
| 169 |
# model (via model inheritance). |
|---|
| 170 |
nm = opts.init_name_map() |
|---|
| 171 |
post_sql_fields = [f for f in opts.local_fields if hasattr(f, 'post_create_sql')] |
|---|
| 172 |
for f in post_sql_fields: |
|---|
| 173 |
output.extend(f.post_create_sql(style, model._meta.db_table)) |
|---|
| 174 |
|
|---|
| 175 |
# Some backends can't execute more than one SQL statement at a time, |
|---|
| 176 |
# so split into separate statements. |
|---|
| 177 |
statements = re.compile(r";[ \t]*$", re.M) |
|---|
| 178 |
|
|---|
| 179 |
# Find custom SQL, if it's available. |
|---|
| 180 |
sql_files = [os.path.join(app_dir, "%s.%s.sql" % (opts.object_name.lower(), settings.DATABASE_ENGINE)), |
|---|
| 181 |
os.path.join(app_dir, "%s.sql" % opts.object_name.lower())] |
|---|
| 182 |
for sql_file in sql_files: |
|---|
| 183 |
if os.path.exists(sql_file): |
|---|
| 184 |
fp = open(sql_file, 'U') |
|---|
| 185 |
for statement in statements.split(fp.read().decode(settings.FILE_CHARSET)): |
|---|
| 186 |
# Remove any comments from the file |
|---|
| 187 |
statement = re.sub(ur"--.*([\n\Z]|$)", "", statement) |
|---|
| 188 |
if statement.strip(): |
|---|
| 189 |
output.append(statement + u";") |
|---|
| 190 |
fp.close() |
|---|
| 191 |
|
|---|
| 192 |
return output |
|---|
| 193 |
|
|---|
| 194 |
|
|---|
| 195 |
def emit_post_sync_signal(created_models, verbosity, interactive): |
|---|
| 196 |
from django.db import models |
|---|
| 197 |
from django.dispatch import dispatcher |
|---|
| 198 |
# Emit the post_sync signal for every application. |
|---|
| 199 |
for app in models.get_apps(): |
|---|
| 200 |
app_name = app.__name__.split('.')[-2] |
|---|
| 201 |
if verbosity >= 2: |
|---|
| 202 |
print "Running post-sync handlers for application", app_name |
|---|
| 203 |
models.signals.post_syncdb.send(sender=app, app=app, |
|---|
| 204 |
created_models=created_models, verbosity=verbosity, |
|---|
| 205 |
interactive=interactive) |
|---|