|
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) |