Ticket #3163: create_db_schema-trunk7027-withtests.2.diff
File create_db_schema-trunk7027-withtests.2.diff, 24.7 KB (added by , 17 years ago) |
---|
-
django/db/models/options.py
15 15 16 16 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering', 17 17 'unique_together', 'permissions', 'get_latest_by', 18 'order_with_respect_to', 'app_label', 'db_tablespace' )18 'order_with_respect_to', 'app_label', 'db_tablespace', 'create_db_schema') 19 19 20 20 class Options(object): 21 21 def __init__(self, meta): … … 30 30 self.get_latest_by = None 31 31 self.order_with_respect_to = None 32 32 self.db_tablespace = settings.DEFAULT_TABLESPACE 33 self.create_db_schema = True 33 34 self.admin = None 34 35 self.meta = meta 35 36 self.pk = None -
django/core/management/commands/syncdb.py
57 57 app_name = app.__name__.split('.')[-2] 58 58 model_list = models.get_models(app) 59 59 for model in model_list: 60 # Create the model's database table, if it doesn't already exist. 61 if verbosity >= 2: 62 print "Processing %s.%s model" % (app_name, model._meta.object_name) 63 if table_name_converter(model._meta.db_table) in tables: 64 continue 65 sql, references = sql_model_create(model, self.style, seen_models) 66 seen_models.add(model) 67 created_models.add(model) 68 for refto, refs in references.items(): 69 pending_references.setdefault(refto, []).extend(refs) 70 sql.extend(sql_for_pending_references(model, self.style, pending_references)) 71 if verbosity >= 1: 72 print "Creating table %s" % model._meta.db_table 73 for statement in sql: 74 cursor.execute(statement) 75 tables.append(table_name_converter(model._meta.db_table)) 60 if model._meta.create_db_schema: 61 # Create the model's database table, if it doesn't already exist. 62 if verbosity >= 2: 63 print "Processing %s.%s model" % (app_name, model._meta.object_name) 64 if table_name_converter(model._meta.db_table) in tables: 65 continue 66 sql, references = sql_model_create(model, self.style, seen_models) 67 seen_models.add(model) 68 created_models.add(model) 69 for refto, refs in references.items(): 70 pending_references.setdefault(refto, []).extend(refs) 71 sql.extend(sql_for_pending_references(model, self.style, pending_references)) 72 if verbosity >= 1: 73 print "Creating table %s" % model._meta.db_table 74 for statement in sql: 75 cursor.execute(statement) 76 tables.append(table_name_converter(model._meta.db_table)) 76 77 77 78 # Create the m2m tables. This must be done after all tables have been created 78 79 # to ensure that all referred tables will exist. … … 119 120 app_name = app.__name__.split('.')[-2] 120 121 for model in models.get_models(app): 121 122 if model in created_models: 122 index_sql = sql_indexes_for_model(model, self.style) 123 if index_sql: 124 if verbosity >= 1: 125 print "Installing index for %s.%s model" % (app_name, model._meta.object_name) 126 try: 127 for sql in index_sql: 128 cursor.execute(sql) 129 except Exception, e: 130 sys.stderr.write("Failed to install index for %s.%s model: %s" % \ 131 (app_name, model._meta.object_name, e)) 132 transaction.rollback_unless_managed() 133 else: 134 transaction.commit_unless_managed() 123 if model._meta.create_db_schema: 124 index_sql = sql_indexes_for_model(model, self.style) 125 if index_sql: 126 if verbosity >= 1: 127 print "Installing index for %s.%s model" % (app_name, model._meta.object_name) 128 try: 129 for sql in index_sql: 130 cursor.execute(sql) 131 except Exception, e: 132 sys.stderr.write("Failed to install index for %s.%s model: %s" % \ 133 (app_name, model._meta.object_name, e)) 134 transaction.rollback_unless_managed() 135 else: 136 transaction.commit_unless_managed() 135 137 136 138 # Install the 'initial_data' fixture, using format discovery 137 139 from django.core.management import call_command -
django/core/management/sql.py
13 13 cursor = connection.cursor() 14 14 return get_introspection_module().get_table_list(cursor) 15 15 16 def django_table_list(only_existing=False ):16 def django_table_list(only_existing=False, filter_not_generated_tables=False): 17 17 """ 18 18 Returns a list of all table names that have associated Django models and 19 19 are in INSTALLED_APPS. 20 20 21 21 If only_existing is True, the resulting list will only include the tables 22 22 that actually exist in the database. 23 24 If filter_not_generated_tables is True, then all tables with associated Django models 25 which have Meta option create_db_schema=False will not be added to the list. 23 26 """ 24 27 from django.db import models 25 28 tables = [] 26 29 for app in models.get_apps(): 27 30 for model in models.get_models(app): 28 tables.append(model._meta.db_table) 29 tables.extend([f.m2m_db_table() for f in model._meta.many_to_many]) 31 if (not filter_not_generated_tables) or (model._meta.create_db_schema): 32 tables.append(model._meta.db_table) 33 tables.extend([f.m2m_db_table() for f in model._meta.many_to_many]) 30 34 if only_existing: 31 35 existing = table_list() 32 36 tables = [t for t in tables if t in existing] … … 45 49 converter = lambda x: x 46 50 return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)]) 47 51 48 def sequence_list(): 49 "Returns a list of information about all DB sequences for all models in all apps." 52 def sequence_list(filter_not_generated_tables=False): 53 """ 54 Returns a list of information about all DB sequences for all models in all apps. 55 56 If filter_not_generated_tables is True, then only the sequences for the Django models 57 which have Meta option create_db_schema=False will be added to the list. 58 """ 50 59 from django.db import models 51 60 52 61 apps = models.get_apps() … … 54 63 55 64 for app in apps: 56 65 for model in models.get_models(app): 57 for f in model._meta.fields: 58 if isinstance(f, models.AutoField): 59 sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 60 break # Only one AutoField is allowed per model, so don't bother continuing. 66 if (not filter_not_generated_tables) or (model._meta.create_db_schema): 67 for f in model._meta.fields: 68 if isinstance(f, models.AutoField): 69 sequence_list.append({'table': model._meta.db_table, 'column': f.column}) 70 break # Only one AutoField is allowed per model, so don't bother continuing. 61 71 62 72 for f in model._meta.many_to_many: 63 73 sequence_list.append({'table': f.m2m_db_table(), 'column': None}) … … 156 166 for model in app_models: 157 167 if cursor and table_name_converter(model._meta.db_table) in table_names: 158 168 # Drop the table now 159 output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 160 style.SQL_TABLE(qn(model._meta.db_table)))) 169 if model._meta.create_db_schema: 170 output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 171 style.SQL_TABLE(qn(model._meta.db_table)))) 161 172 if connection.features.supports_constraints and model in references_to_delete: 162 173 for rel_class, f in references_to_delete[model]: 163 174 table = rel_class._meta.db_table … … 165 176 r_table = model._meta.db_table 166 177 r_col = model._meta.get_field(f.rel.field_name).column 167 178 r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))) 168 output.append('%s %s %s %s;' % \ 169 (style.SQL_KEYWORD('ALTER TABLE'), 170 style.SQL_TABLE(qn(table)), 171 style.SQL_KEYWORD(connection.ops.drop_foreignkey_sql()), 172 style.SQL_FIELD(truncate_name(r_name, connection.ops.max_name_length())))) 179 if rel_class._meta.create_db_schema: 180 output.append('%s %s %s %s;' % \ 181 (style.SQL_KEYWORD('ALTER TABLE'), 182 style.SQL_TABLE(qn(table)), 183 style.SQL_KEYWORD(connection.ops.drop_foreignkey_sql()), 184 style.SQL_FIELD(truncate_name(r_name, connection.ops.max_name_length())))) 173 185 del references_to_delete[model] 174 186 if model._meta.has_auto_field: 175 187 ds = connection.ops.drop_sequence_sql(model._meta.db_table) … … 206 218 def sql_flush(style, only_django=False): 207 219 """ 208 220 Returns a list of the SQL statements used to flush the database. 209 221 210 222 If only_django is True, then only table names that have associated Django 211 223 models and are in INSTALLED_APPS will be included. 212 224 """ 213 225 from django.db import connection 214 226 if only_django: 215 tables = django_table_list( )227 tables = django_table_list(filter_not_generated_tables=True) 216 228 else: 217 229 tables = table_list() 218 statements = connection.ops.sql_flush(style, tables, sequence_list( ))230 statements = connection.ops.sql_flush(style, tables, sequence_list(filter_not_generated_tables=True)) 219 231 return statements 220 232 221 233 def sql_custom(app): … … 295 307 table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \ 296 308 ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints])) 297 309 298 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] 299 for i, line in enumerate(table_output): # Combine and add commas. 300 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 301 full_statement.append(')') 302 if opts.db_tablespace and connection.features.supports_tablespaces: 303 full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace)) 304 full_statement.append(';') 305 final_output.append('\n'.join(full_statement)) 310 # Now build up the CREATE TABLE section but only if the model requires it 311 if opts.create_db_schema: 312 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] 313 for i, line in enumerate(table_output): # Combine and add commas. 314 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 315 full_statement.append(')') 316 if opts.db_tablespace and connection.features.supports_tablespaces: 317 full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace)) 318 full_statement.append(';') 319 final_output.append('\n'.join(full_statement)) 306 320 307 if opts.has_auto_field:308 # Add any extra SQL needed to support auto-incrementing primary keys.309 auto_column = opts.auto_field.db_column or opts.auto_field.name310 autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column)311 if autoinc_sql:312 for stmt in autoinc_sql:313 final_output.append(stmt)321 if opts.has_auto_field: 322 # Add any extra SQL needed to support auto-incrementing primary keys. 323 auto_column = opts.auto_field.db_column or opts.auto_field.name 324 autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column) 325 if autoinc_sql: 326 for stmt in autoinc_sql: 327 final_output.append(stmt) 314 328 315 329 return final_output, pending_references 316 330 … … 335 349 # For MySQL, r_name must be unique in the first 64 characters. 336 350 # So we are careful with character usage here. 337 351 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) 338 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ 339 (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), 340 qn(r_col), qn(table), qn(col), 341 connection.ops.deferrable_sql())) 352 if rel_opts.create_db_schema: 353 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ 354 (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), 355 qn(r_col), qn(table), qn(col), 356 connection.ops.deferrable_sql())) 342 357 del pending_references[model] 343 358 return final_output 344 359 … … 411 426 for r_table, r_col, table, col in deferred: 412 427 r_name = '%s_refs_%s_%x' % (r_col, col, 413 428 abs(hash((r_table, table)))) 414 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % 429 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % 415 430 (qn(r_table), 416 431 truncate_name(r_name, connection.ops.max_name_length()), 417 432 qn(r_col), qn(table), qn(col), … … 458 473 output = [] 459 474 460 475 qn = connection.ops.quote_name 461 for f in model._meta.fields: 462 if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): 463 unique = f.unique and 'UNIQUE ' or '' 464 tablespace = f.db_tablespace or model._meta.db_tablespace 465 if tablespace and connection.features.supports_tablespaces: 466 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace) 467 else: 468 tablespace_sql = '' 469 output.append( 470 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ 471 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ 472 style.SQL_KEYWORD('ON') + ' ' + \ 473 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \ 474 "(%s)" % style.SQL_FIELD(qn(f.column)) + \ 475 "%s;" % tablespace_sql 476 ) 476 if model._meta.create_db_schema: 477 for f in model._meta.fields: 478 if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): 479 unique = f.unique and 'UNIQUE ' or '' 480 tablespace = f.db_tablespace or model._meta.db_tablespace 481 if tablespace and connection.features.supports_tablespaces: 482 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace) 483 else: 484 tablespace_sql = '' 485 output.append( 486 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ 487 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ 488 style.SQL_KEYWORD('ON') + ' ' + \ 489 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \ 490 "(%s)" % style.SQL_FIELD(qn(f.column)) + \ 491 "%s;" % tablespace_sql 492 ) 477 493 return output 478 494 479 495 def emit_post_sync_signal(created_models, verbosity, interactive): -
tests/modeltests/create_db_schema/models.py
1 """ 2 xx. create_db_schema 3 4 Models can have a ``create_db_schema`` attribute, which specifies 5 whether the SQL code is generated for the table on various manage.py operations 6 or not. 7 """ 8 9 from django.db import models 10 11 """ 12 General test strategy: 13 * All tests are numbered (01, 02, 03... etc). 14 * Each test contains three models (A, B, C, followed with the number of test), 15 containing both indexed and non-indexed fields (to verify sql_index), 16 usual fields (model A), foreign keys (model B) and many-to-many fields (model C). 17 D table is generated automatically as intermediate M2M one. 18 * The normal (default; create_db_schema = True) behaviour during the manage.py 19 operations is not thoroughly checked; it is the duty of the appropriate tests 20 for the primary functionality of these operations. 21 The most attention is paid to whether the create_db_schema = False 22 disables the SQL generation properly. 23 * The intermediate table for M2M relations is not ever verified explicitly, 24 because it is not ever marked with create_db_schema explicitly. 25 """ 26 27 # This dictionary maps the name of the model/SQL table (like 'A01') 28 # to the boolean specifying whether this name should appear in the final SQL 29 checks = {} 30 31 32 """ 33 01: create_db_schema is not set. 34 In such case, it should be equal (by default) to True, 35 and SQL is generated for all three models. 36 """ 37 checks['A01'] = True 38 checks['B01'] = True 39 checks['C01'] = True 40 41 class A01(models.Model): 42 class Meta: db_table = 'A01' 43 44 f_a = models.TextField(db_index = True) 45 f_b = models.IntegerField() 46 47 class B01(models.Model): 48 class Meta: db_table = 'B01' 49 50 fk_a = models.ForeignKey(A01) 51 f_a = models.TextField(db_index = True) 52 f_b = models.IntegerField() 53 54 class C01(models.Model): 55 class Meta: db_table = 'C01' 56 57 mm_a = models.ManyToManyField(A01, db_table = 'D01') 58 f_a = models.TextField(db_index = True) 59 f_b = models.IntegerField() 60 61 62 """ 63 02: create_db_schema is set to True. 64 SQL is generated for all three models. 65 """ 66 checks['A02'] = True 67 checks['B02'] = True 68 checks['C02'] = True 69 70 class A02(models.Model): 71 class Meta: 72 db_table = 'A02' 73 create_db_schema = True 74 75 f_a = models.TextField(db_index = True) 76 f_b = models.IntegerField() 77 78 class B02(models.Model): 79 class Meta: 80 db_table = 'B02' 81 create_db_schema = True 82 83 fk_a = models.ForeignKey(A02) 84 f_a = models.TextField(db_index = True) 85 f_b = models.IntegerField() 86 87 class C02(models.Model): 88 class Meta: 89 db_table = 'C02' 90 create_db_schema = True 91 92 mm_a = models.ManyToManyField(A02, db_table = 'D02') 93 f_a = models.TextField(db_index = True) 94 f_b = models.IntegerField() 95 96 97 """ 98 03: create_db_schema is set to False. 99 SQL is NOT generated for any of the three models. 100 """ 101 checks['A03'] = False 102 checks['B03'] = False 103 checks['C03'] = False 104 105 class A03(models.Model): 106 class Meta: 107 db_table = 'A03' 108 create_db_schema = False 109 110 f_a = models.TextField(db_index = True) 111 f_b = models.IntegerField() 112 113 class B03(models.Model): 114 class Meta: 115 db_table = 'B03' 116 create_db_schema = False 117 118 fk_a = models.ForeignKey(A03) 119 f_a = models.TextField(db_index = True) 120 f_b = models.IntegerField() 121 122 class C03(models.Model): 123 class Meta: 124 db_table = 'C03' 125 create_db_schema = False 126 127 mm_a = models.ManyToManyField(A03, db_table = 'D03') 128 f_a = models.TextField(db_index = True) 129 f_b = models.IntegerField() 130 131 132 # We will use short names for these templates 133 sql_templates = { 134 'create table': 'CREATE TABLE "%s"', 135 'create index': 'CREATE INDEX "%s_f_a"', 136 'drop table': 'DROP TABLE "%s"', 137 'delete from': 'DELETE FROM "%s"' 138 } 139 140 def get_failed_models(arr_sql, sql_template_names): 141 """ 142 Find the models which should not be in the SQL but they are present, 143 or they should be in the SQL but they are missing. 144 """ 145 txt_sql = ' '.join(arr_sql) 146 for (model, should_be_present) in checks.iteritems(): 147 # Do we expect to see the model name in the SQL text? 148 for sql_template_name in sql_template_names: 149 # We are interested not in just the model name like "A01", 150 # but in the whole string like 'CREATE TABLE "A01"' 151 # so we apply the model name to the template 152 # to find out the expected string 153 expected = (sql_templates[sql_template_name])%model 154 if ((expected in txt_sql) != should_be_present): 155 # Our expectations failed! 156 yield 'The string %s %s present in SQL but it %s.'%( 157 expected, 158 {False: 'is not', True: 'is'}[expected in txt_sql], 159 {False: 'should not be', True: 'should be'}[should_be_present] 160 ) 161 162 163 __test__ = {'API_TESTS':""" 164 >>> from django.db.models import get_app 165 >>> from django.core.management.sql import * 166 >>> from django.core.management.color import no_style 167 >>> import sys 168 169 >>> myapp = get_app('create_db_schema') 170 >>> mystyle = no_style() 171 172 # a. Verify sql_create 173 >>> list(get_failed_models( sql_create(myapp, mystyle), ['create table'] )) 174 [] 175 176 # b. Verify sql_delete 177 >>> list(get_failed_models( sql_delete(myapp, mystyle), ['drop table'] )) 178 [] 179 180 # c. Verify sql_reset 181 >>> list(get_failed_models( sql_reset(myapp, mystyle), ['drop table', 'create table', 'create index'] )) 182 [] 183 184 # d. Verify sql_flush 185 >>> # sql_flush(mystyle) 186 >>> list(get_failed_models( sql_flush(mystyle), ['delete from'] )) 187 [] 188 189 # e. Verify sql_custom 190 # No custom data provided, should not be no output. 191 >>> sql_custom(myapp) 192 [] 193 194 # f. Verify sql_indexes 195 >>> list(get_failed_models( sql_indexes(myapp, mystyle), ['create index'] )) 196 [] 197 198 # g. Verify sql_all 199 >>> list(get_failed_models( sql_all(myapp, mystyle), ['create table', 'create index'] )) 200 [] 201 """} -
AUTHORS
353 353 ymasuda@ethercube.com 354 354 Jarek Zgoda <jarek.zgoda@gmail.com> 355 355 Cheng Zhang 356 Alexander Myodov <amyodov@gmail.com> 356 357 357 358 A big THANK YOU goes to: 358 359 -
docs/model-api.txt
1051 1051 that aren't allowed in Python variable names -- notably, the hyphen -- 1052 1052 that's OK. Django quotes column and table names behind the scenes. 1053 1053 1054 ``create_db_schema`` 1055 -------------------- 1056 1057 **New in Django development version** 1058 1059 Marks this model as requiring SQL operations when calling ``manage.py``:: 1060 1061 create_db_schema = False 1062 1063 If this isn't given, Django will use ``create_db_schema = True`` 1064 what means that the operations like ``manage.py sqlreset``, ``manage.py syncdb`` 1065 and others will regenerate the appropriate table when needed. 1066 If the option is set to False, the appropriate table will not be affected with 1067 any SQL operations. 1068 1069 This is useful for databases where some DB tables are controlled by Django models, 1070 but some other DB tables and views are created via raw SQL and should not be affected 1071 by any ``manage.py`` actions. 1072 Note that if the initial SQL data is provided (see `Providing initial SQL 1073 data`_ below), it still will be present in the output of 1074 ``sqlall``/``sqlcustom`` commands. 1075 1054 1076 ``db_tablespace`` 1055 1077 ----------------- 1056 1078