Ticket #3163: create_db_schema-trunk7512-qsrf_ready.diff

File create_db_schema-trunk7512-qsrf_ready.diff, 25.0 KB (added by honeyman, 7 years ago)

create_db_schema Meta option implementation (Django trunk 7512, qsrf-ready)

  • django/db/models/options.py

     
    2222DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2323                 'unique_together', 'permissions', 'get_latest_by',
    2424                 'order_with_respect_to', 'app_label', 'db_tablespace',
    25                  'abstract')
     25                 'abstract', 'create_db_schema')
    2626
    2727class Options(object):
    2828    def __init__(self, meta):
     
    3737        self.get_latest_by = None
    3838        self.order_with_respect_to = None
    3939        self.db_tablespace = settings.DEFAULT_TABLESPACE
     40        self.create_db_schema = True
    4041        self.admin = None
    4142        self.meta = meta
    4243        self.pk = None
  • django/core/management/commands/syncdb.py

     
    5757            app_name = app.__name__.split('.')[-2]
    5858            model_list = models.get_models(app)
    5959            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))
    7879
    7980        # Create the m2m tables. This must be done after all tables have been created
    8081        # to ensure that all referred tables will exist.
     
    121122            app_name = app.__name__.split('.')[-2]
    122123            for model in models.get_models(app):
    123124                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()
    137139
    138140        # Install the 'initial_data' fixture, using format discovery
    139141        from django.core.management import call_command
  • django/core/management/sql.py

     
    1313    cursor = connection.cursor()
    1414    return get_introspection_module().get_table_list(cursor)
    1515
    16 def django_table_list(only_existing=False):
     16def django_table_list(only_existing=False, filter_not_generated_tables=False):
    1717    """
    1818    Returns a list of all table names that have associated Django models and
    1919    are in INSTALLED_APPS.
    2020
    2121    If only_existing is True, the resulting list will only include the tables
    2222    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.
    2326    """
    2427    from django.db import models
    2528    tables = []
    2629    for app in models.get_apps():
    2730        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.local_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.local_many_to_many])
    3034    if only_existing:
    3135        existing = table_list()
    3236        tables = [t for t in tables if t in existing]
     
    4549        converter = lambda x: x
    4650    return set([m for m in all_models if converter(m._meta.db_table) in map(converter, table_list)])
    4751
    48 def sequence_list():
    49     "Returns a list of information about all DB sequences for all models in all apps."
     52def 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    """
    5059    from django.db import models
    5160
    5261    apps = models.get_apps()
     
    5463
    5564    for app in apps:
    5665        for model in models.get_models(app):
    57             for f in model._meta.local_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.local_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.
    6171
    6272            for f in model._meta.local_many_to_many:
    6373                sequence_list.append({'table': f.m2m_db_table(), 'column': None})
     
    158168    for model in app_models:
    159169        if cursor and table_name_converter(model._meta.db_table) in table_names:
    160170            # 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))))
    163174            if connection.features.supports_constraints and model in references_to_delete:
    164175                for rel_class, f in references_to_delete[model]:
    165176                    table = rel_class._meta.db_table
     
    167178                    r_table = model._meta.db_table
    168179                    r_col = model._meta.get_field(f.rel.field_name).column
    169180                    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()))))
    175187                del references_to_delete[model]
    176188            if model._meta.has_auto_field:
    177189                ds = connection.ops.drop_sequence_sql(model._meta.db_table)
     
    208220def sql_flush(style, only_django=False):
    209221    """
    210222    Returns a list of the SQL statements used to flush the database.
    211    
     223
    212224    If only_django is True, then only table names that have associated Django
    213225    models and are in INSTALLED_APPS will be included.
    214226    """
    215227    from django.db import connection
    216228    if only_django:
    217         tables = django_table_list()
     229        tables = django_table_list(filter_not_generated_tables=True)
    218230    else:
    219231        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))
    221233    return statements
    222234
    223235def sql_custom(app):
     
    297309        table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
    298310            ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
    299311
    300     full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
    301     for i, line in enumerate(table_output): # Combine and add commas.
    302         full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
    303     full_statement.append(')')
    304     if opts.db_tablespace and connection.features.supports_tablespaces:
    305         full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace))
    306     full_statement.append(';')
    307     final_output.append('\n'.join(full_statement))
     312    # Now build up the CREATE TABLE section but only if the model requires it
     313    if opts.create_db_schema:
     314        full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     315        for i, line in enumerate(table_output): # Combine and add commas.
     316            full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
     317        full_statement.append(')')
     318        if opts.db_tablespace and connection.features.supports_tablespaces:
     319            full_statement.append(connection.ops.tablespace_sql(opts.db_tablespace))
     320        full_statement.append(';')
     321        final_output.append('\n'.join(full_statement))
    308322
    309     if opts.has_auto_field:
    310         # Add any extra SQL needed to support auto-incrementing primary keys.
    311         auto_column = opts.auto_field.db_column or opts.auto_field.name
    312         autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column)
    313         if autoinc_sql:
    314             for stmt in autoinc_sql:
    315                 final_output.append(stmt)
     323        if opts.has_auto_field:
     324            # Add any extra SQL needed to support auto-incrementing primary keys.
     325            auto_column = opts.auto_field.db_column or opts.auto_field.name
     326            autoinc_sql = connection.ops.autoinc_sql(opts.db_table, auto_column)
     327            if autoinc_sql:
     328                for stmt in autoinc_sql:
     329                    final_output.append(stmt)
    316330
    317331    return final_output, pending_references
    318332
     
    337351                # For MySQL, r_name must be unique in the first 64 characters.
    338352                # So we are careful with character usage here.
    339353                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
    340                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    341                     (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
    342                     qn(r_col), qn(table), qn(col),
    343                     connection.ops.deferrable_sql()))
     354                if rel_opts.create_db_schema:
     355                    final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
     356                        (qn(r_table), truncate_name(r_name, connection.ops.max_name_length()),
     357                        qn(r_col), qn(table), qn(col),
     358                        connection.ops.deferrable_sql()))
    344359            del pending_references[model]
    345360    return final_output
    346361
     
    413428            for r_table, r_col, table, col in deferred:
    414429                r_name = '%s_refs_%s_%x' % (r_col, col,
    415430                        abs(hash((r_table, table))))
    416                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % 
     431                final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' %
    417432                (qn(r_table),
    418433                truncate_name(r_name, connection.ops.max_name_length()),
    419434                qn(r_col), qn(table), qn(col),
     
    460475    output = []
    461476
    462477    qn = connection.ops.quote_name
    463     for f in model._meta.local_fields:
    464         if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
    465             unique = f.unique and 'UNIQUE ' or ''
    466             tablespace = f.db_tablespace or model._meta.db_tablespace
    467             if tablespace and connection.features.supports_tablespaces:
    468                 tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace)
    469             else:
    470                 tablespace_sql = ''
    471             output.append(
    472                 style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
    473                 style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
    474                 style.SQL_KEYWORD('ON') + ' ' + \
    475                 style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \
    476                 "(%s)" % style.SQL_FIELD(qn(f.column)) + \
    477                 "%s;" % tablespace_sql
    478             )
     478    if model._meta.create_db_schema:
     479        for f in model._meta.local_fields:
     480            if f.db_index and not ((f.primary_key or f.unique) and connection.features.autoindexes_primary_keys):
     481                unique = f.unique and 'UNIQUE ' or ''
     482                tablespace = f.db_tablespace or model._meta.db_tablespace
     483                if tablespace and connection.features.supports_tablespaces:
     484                    tablespace_sql = ' ' + connection.ops.tablespace_sql(tablespace)
     485                else:
     486                    tablespace_sql = ''
     487                output.append(
     488                    style.SQL_KEYWORD('CREATE %sINDEX' % unique) + ' ' + \
     489                    style.SQL_TABLE(qn('%s_%s' % (model._meta.db_table, f.column))) + ' ' + \
     490                    style.SQL_KEYWORD('ON') + ' ' + \
     491                    style.SQL_TABLE(qn(model._meta.db_table)) + ' ' + \
     492                    "(%s)" % style.SQL_FIELD(qn(f.column)) + \
     493                    "%s;" % tablespace_sql
     494                )
    479495    return output
    480496
    481497def emit_post_sync_signal(created_models, verbosity, interactive):
  • tests/modeltests/create_db_schema/models.py

     
     1"""
     2xx. create_db_schema
     3
     4Models can have a ``create_db_schema`` attribute, which specifies
     5whether the SQL code is generated for the table on various manage.py operations
     6or not.
     7"""
     8
     9from django.db import models
     10
     11"""
     12General 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
     29checks = {}
     30
     31
     32"""
     3301: 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"""
     37checks['A01'] = True
     38checks['B01'] = True
     39checks['C01'] = True
     40
     41class A01(models.Model):
     42    class Meta: db_table = 'A01'
     43
     44    f_a = models.TextField(db_index = True)
     45    f_b = models.IntegerField()
     46
     47class 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
     54class 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"""
     6302: create_db_schema is set to True.
     64    SQL is generated for all three models.
     65"""
     66checks['A02'] = True
     67checks['B02'] = True
     68checks['C02'] = True
     69
     70class 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
     78class 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
     87class 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"""
     9803: create_db_schema is set to False.
     99    SQL is NOT generated for any of the three models.
     100"""
     101checks['A03'] = False
     102checks['B03'] = False
     103checks['C03'] = False
     104
     105class 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
     113class 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
     122class 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
     133sql_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
     140def 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

     
    382382    ymasuda@ethercube.com
    383383    Jarek Zgoda <jarek.zgoda@gmail.com>
    384384    Cheng Zhang
     385    Alexander Myodov <amyodov@gmail.com>
    385386
    386387A big THANK YOU goes to:
    387388
  • docs/model-api.txt

     
    10901090that aren't allowed in Python variable names -- notably, the hyphen --
    10911091that's OK. Django quotes column and table names behind the scenes.
    10921092
     1093``create_db_schema``
     1094--------------------
     1095
     1096**New in Django development version**
     1097
     1098Marks this model as requiring SQL operations when calling ``manage.py``::
     1099
     1100    create_db_schema = False
     1101
     1102If this isn't given, Django will use ``create_db_schema = True``
     1103what means that the operations like ``manage.py sqlreset``, ``manage.py syncdb``
     1104and others will regenerate the appropriate table when needed.
     1105If the option is set to False, the appropriate table will not be affected with
     1106any SQL operations.
     1107
     1108This is useful for databases where some DB tables are controlled by Django models,
     1109but some other DB tables and views are created via raw SQL and should not be affected
     1110by any ``manage.py`` actions.
     1111Note that if the initial SQL data is provided (see `Providing initial SQL
     1112data`_ below), it still will be present in the output of
     1113``sqlall``/``sqlcustom`` commands.
     1114
    10931115``db_tablespace``
    10941116-----------------
    10951117
Back to Top