Ticket #3163: create_db_schema-trunk7442-withtests.diff
File create_db_schema-trunk7442-withtests.diff, 24.9 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 if refto in seen_models: 71 sql.extend(sql_for_pending_references(refto, self.style, pending_references)) 72 sql.extend(sql_for_pending_references(model, self.style, pending_references)) 73 if verbosity >= 1: 74 print "Creating table %s" % model._meta.db_table 75 for statement in sql: 76 cursor.execute(statement) 77 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 if refto in seen_models: 72 sql.extend(sql_for_pending_references(refto, self.style, pending_references)) 73 sql.extend(sql_for_pending_references(model, self.style, pending_references)) 74 if verbosity >= 1: 75 print "Creating table %s" % model._meta.db_table 76 for statement in sql: 77 cursor.execute(statement) 78 tables.append(table_name_converter(model._meta.db_table)) 78 79 79 80 # Create the m2m tables. This must be done after all tables have been created 80 81 # to ensure that all referred tables will exist. … … 121 122 app_name = app.__name__.split('.')[-2] 122 123 for model in models.get_models(app): 123 124 if model in created_models: 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() 125 if model._meta.create_db_schema: 126 index_sql = sql_indexes_for_model(model, self.style) 127 if index_sql: 128 if verbosity >= 1: 129 print "Installing index for %s.%s model" % (app_name, model._meta.object_name) 130 try: 131 for sql in index_sql: 132 cursor.execute(sql) 133 except Exception, e: 134 sys.stderr.write("Failed to install index for %s.%s model: %s" % \ 135 (app_name, model._meta.object_name, e)) 136 transaction.rollback_unless_managed() 137 else: 138 transaction.commit_unless_managed() 137 139 138 140 # Install the 'initial_data' fixture, using format discovery 139 141 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}) … … 158 168 for model in app_models: 159 169 if cursor and table_name_converter(model._meta.db_table) in table_names: 160 170 # Drop the table now 161 output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 162 style.SQL_TABLE(qn(model._meta.db_table)))) 171 if model._meta.create_db_schema: 172 output.append('%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 173 style.SQL_TABLE(qn(model._meta.db_table)))) 163 174 if connection.features.supports_constraints and model in references_to_delete: 164 175 for rel_class, f in references_to_delete[model]: 165 176 table = rel_class._meta.db_table … … 167 178 r_table = model._meta.db_table 168 179 r_col = model._meta.get_field(f.rel.field_name).column 169 180 r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table)))) 170 output.append('%s %s %s %s;' % \ 171 (style.SQL_KEYWORD('ALTER TABLE'), 172 style.SQL_TABLE(qn(table)), 173 style.SQL_KEYWORD(connection.ops.drop_foreignkey_sql()), 174 style.SQL_FIELD(truncate_name(r_name, connection.ops.max_name_length())))) 181 if rel_class._meta.create_db_schema: 182 output.append('%s %s %s %s;' % \ 183 (style.SQL_KEYWORD('ALTER TABLE'), 184 style.SQL_TABLE(qn(table)), 185 style.SQL_KEYWORD(connection.ops.drop_foreignkey_sql()), 186 style.SQL_FIELD(truncate_name(r_name, connection.ops.max_name_length())))) 175 187 del references_to_delete[model] 176 188 if model._meta.has_auto_field: 177 189 ds = connection.ops.drop_sequence_sql(model._meta.db_table) … … 208 220 def sql_flush(style, only_django=False): 209 221 """ 210 222 Returns a list of the SQL statements used to flush the database. 211 223 212 224 If only_django is True, then only table names that have associated Django 213 225 models and are in INSTALLED_APPS will be included. 214 226 """ 215 227 from django.db import connection 216 228 if only_django: 217 tables = django_table_list( )229 tables = django_table_list(filter_not_generated_tables=True) 218 230 else: 219 231 tables = table_list() 220 statements = connection.ops.sql_flush(style, tables, sequence_list( ))232 statements = connection.ops.sql_flush(style, tables, sequence_list(filter_not_generated_tables=True)) 221 233 return statements 222 234 223 235 def sql_custom(app): … … 303 315 opts.db_tablespace, inline=True)) 304 316 table_output.append(' '.join(constraint_output)) 305 317 306 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] 307 for i, line in enumerate(table_output): # Combine and add commas. 308 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 309 full_statement.append(')') 310 if opts.db_tablespace and connection.features.supports_tablespaces: 311 full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace)) 312 full_statement.append(';') 313 final_output.append('\n'.join(full_statement)) 318 # Now build up the CREATE TABLE section but only if the model requires it 319 if opts.create_db_schema: 320 full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' ('] 321 for i, line in enumerate(table_output): # Combine and add commas. 322 full_statement.append(' %s%s' % (line, i < len(table_output)-1 and ',' or '')) 323 full_statement.append(')') 324 if opts.db_tablespace and connection.features.supports_tablespaces: 325 full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace)) 326 full_statement.append(';') 327 final_output.append('\n'.join(full_statement)) 314 328 315 if opts.has_auto_field:316 # Add any extra SQL needed to support auto-incrementing primary keys.317 auto_column = opts.auto_field.db_column or opts.auto_field.name318 autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column)319 if autoinc_sql:320 for stmt in autoinc_sql:321 final_output.append(stmt)329 if opts.has_auto_field: 330 # Add any extra SQL needed to support auto-incrementing primary keys. 331 auto_column = opts.auto_field.db_column or opts.auto_field.name 332 autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column) 333 if autoinc_sql: 334 for stmt in autoinc_sql: 335 final_output.append(stmt) 322 336 323 337 return final_output, pending_references 324 338 … … 343 357 # For MySQL, r_name must be unique in the first 64 characters. 344 358 # So we are careful with character usage here. 345 359 r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table)))) 346 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ 347 (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), 348 qn(r_col), qn(table), qn(col), 349 connection.ops.deferrable_sql())) 360 if rel_opts.create_db_schema: 361 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \ 362 (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()), 363 qn(r_col), qn(table), qn(col), 364 connection.ops.deferrable_sql())) 350 365 del pending_references[model] 351 366 return final_output 352 367 … … 419 434 for r_table, r_col, table, col in deferred: 420 435 r_name = '%s_refs_%s_%x' % (r_col, col, 421 436 abs(hash((r_table, table)))) 422 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % 437 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % 423 438 (qn(r_table), 424 439 truncate_name(r_name, connection.ops.max_name_length()), 425 440 qn(r_col), qn(table), qn(col), … … 466 481 output = [] 467 482 468 483 qn = connection.ops.quote_name 469 for f in model._meta.fields: 470 if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): 471 unique = f.unique and 'UNIQUE ' or '' 472 tablespace = f.db_tablespace or model._meta.db_tablespace 473 if tablespace and connection.features.supports_tablespaces: 474 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace) 475 else: 476 tablespace_sql = '' 477 output.append( 478 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ 479 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ 480 style.SQL_KEYWORD('ON') + ' ' + \ 481 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \ 482 "(%s)" % style.SQL_FIELD(qn(f.column)) + \ 483 "%s;" % tablespace_sql 484 ) 484 if model._meta.create_db_schema: 485 for f in model._meta.fields: 486 if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys): 487 unique = f.unique and 'UNIQUE ' or '' 488 tablespace = f.db_tablespace or model._meta.db_tablespace 489 if tablespace and connection.features.supports_tablespaces: 490 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace) 491 else: 492 tablespace_sql = '' 493 output.append( 494 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \ 495 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \ 496 style.SQL_KEYWORD('ON') + ' ' + \ 497 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \ 498 "(%s)" % style.SQL_FIELD(qn(f.column)) + \ 499 "%s;" % tablespace_sql 500 ) 485 501 return output 486 502 487 503 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
381 381 ymasuda@ethercube.com 382 382 Jarek Zgoda <jarek.zgoda@gmail.com> 383 383 Cheng Zhang 384 Alexander Myodov <amyodov@gmail.com> 384 385 385 386 A big THANK YOU goes to: 386 387 -
docs/model-api.txt
1062 1062 that aren't allowed in Python variable names -- notably, the hyphen -- 1063 1063 that's OK. Django quotes column and table names behind the scenes. 1064 1064 1065 ``create_db_schema`` 1066 -------------------- 1067 1068 **New in Django development version** 1069 1070 Marks this model as requiring SQL operations when calling ``manage.py``:: 1071 1072 create_db_schema = False 1073 1074 If this isn't given, Django will use ``create_db_schema = True`` 1075 what means that the operations like ``manage.py sqlreset``, ``manage.py syncdb`` 1076 and others will regenerate the appropriate table when needed. 1077 If the option is set to False, the appropriate table will not be affected with 1078 any SQL operations. 1079 1080 This is useful for databases where some DB tables are controlled by Django models, 1081 but some other DB tables and views are created via raw SQL and should not be affected 1082 by any ``manage.py`` actions. 1083 Note that if the initial SQL data is provided (see `Providing initial SQL 1084 data`_ below), it still will be present in the output of 1085 ``sqlall``/``sqlcustom`` commands. 1086 1065 1087 ``db_tablespace`` 1066 1088 ----------------- 1067 1089