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