Ticket #3163: create_db_schema-trunk.diff

File create_db_schema-trunk.diff, 20.4 KB (added by honeyman, 7 years ago)

create_db_schema Meta option implementation (Django trunk 8599)

  • django/db/models/options.py

     
    2121DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
    2222                 'unique_together', 'permissions', 'get_latest_by',
    2323                 'order_with_respect_to', 'app_label', 'db_tablespace',
    24                  'abstract')
     24                 'abstract', 'create_db_schema')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    3636        self.get_latest_by = None
    3737        self.order_with_respect_to = None
    3838        self.db_tablespace = settings.DEFAULT_TABLESPACE
     39        self.create_db_schema = True
    3940        self.admin = None
    4041        self.meta = meta
    4142        self.pk = None
  • django/db/backends/__init__.py

     
    382382        cursor = self.connection.cursor()
    383383        return self.get_table_list(cursor)
    384384
    385     def django_table_names(self, only_existing=False):
     385    def django_table_names(self, only_existing=False, filter_not_generated_tables=False):
    386386        """
    387387        Returns a list of all table names that have associated Django models and
    388388        are in INSTALLED_APPS.
    389389
    390390        If only_existing is True, the resulting list will only include the tables
    391391        that actually exist in the database.
     392
     393        If filter_not_generated_tables is True, then all tables with associated Django models
     394        which have Meta option create_db_schema=False will not be added to the list.
    392395        """
    393396        from django.db import models
    394397        tables = set()
    395398        for app in models.get_apps():
    396399            for model in models.get_models(app):
    397                 tables.add(model._meta.db_table)
    398                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
     400                if (not filter_not_generated_tables) or (model._meta.create_db_schema):
     401                    tables.add(model._meta.db_table)
     402                    tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
    399403        if only_existing:
    400404            tables = [t for t in tables if t in self.table_names()]
    401405        return tables
     
    411415            if self.table_name_converter(m._meta.db_table) in map(self.table_name_converter, tables)
    412416        ])
    413417
    414     def sequence_list(self):
    415         "Returns a list of information about all DB sequences for all models in all apps."
     418    def sequence_list(self, filter_not_generated_tables=False):
     419        """
     420        Returns a list of information about all DB sequences for all models in all apps.
     421
     422        If filter_not_generated_tables is True, then only the sequences for the Django models
     423        which have Meta option create_db_schema=False will be added to the list.
     424        """
    416425        from django.db import models
    417426
    418427        apps = models.get_apps()
     
    420429
    421430        for app in apps:
    422431            for model in models.get_models(app):
    423                 for f in model._meta.local_fields:
    424                     if isinstance(f, models.AutoField):
    425                         sequence_list.append({'table': model._meta.db_table, 'column': f.column})
    426                         break # Only one AutoField is allowed per model, so don't bother continuing.
     432                if (not filter_not_generated_tables) or (model._meta.create_db_schema):
     433                    for f in model._meta.local_fields:
     434                        if isinstance(f, models.AutoField):
     435                            sequence_list.append({'table': model._meta.db_table, 'column': f.column})
     436                            break # Only one AutoField is allowed per model, so don't bother continuing.
    427437
    428                 for f in model._meta.local_many_to_many:
    429                     sequence_list.append({'table': f.m2m_db_table(), 'column': None})
     438                    for f in model._meta.local_many_to_many:
     439                        sequence_list.append({'table': f.m2m_db_table(), 'column': None})
    430440
    431441        return sequence_list
    432442
     
    445455    def validate_field(self, errors, opts, f):
    446456        "By default, there is no backend-specific validation"
    447457        pass
    448 
  • django/db/backends/creation.py

     
    7171            table_output.append(style.SQL_KEYWORD('UNIQUE') + ' (%s)' % \
    7272                ", ".join([style.SQL_FIELD(qn(opts.get_field(f).column)) for f in field_constraints]))
    7373
    74         full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
    75         for i, line in enumerate(table_output): # Combine and add commas.
    76             full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
    77         full_statement.append(')')
    78         if opts.db_tablespace:
    79             full_statement.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
    80         full_statement.append(';')
    81         final_output.append('\n'.join(full_statement))
     74        # Now build up the CREATE TABLE section but only if the model requires it
     75        if opts.create_db_schema:
     76            full_statement = [style.SQL_KEYWORD('CREATE TABLE') + ' ' + style.SQL_TABLE(qn(opts.db_table)) + ' (']
     77            for i, line in enumerate(table_output): # Combine and add commas.
     78                full_statement.append('    %s%s' % (line, i < len(table_output)-1 and ',' or ''))
     79            full_statement.append(')')
     80            if opts.db_tablespace:
     81                full_statement.append(self.connection.ops.tablespace_sql(opts.db_tablespace))
     82            full_statement.append(';')
     83            final_output.append('\n'.join(full_statement))
    8284
    83         if opts.has_auto_field:
    84             # Add any extra SQL needed to support auto-incrementing primary keys.
    85             auto_column = opts.auto_field.db_column or opts.auto_field.name
    86             autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
    87             if autoinc_sql:
    88                 for stmt in autoinc_sql:
    89                     final_output.append(stmt)
     85            if opts.has_auto_field:
     86                # Add any extra SQL needed to support auto-incrementing primary keys.
     87                auto_column = opts.auto_field.db_column or opts.auto_field.name
     88                autoinc_sql = self.connection.ops.autoinc_sql(opts.db_table, auto_column)
     89                if autoinc_sql:
     90                    for stmt in autoinc_sql:
     91                        final_output.append(stmt)
    9092
    9193        return final_output, pending_references
    9294
     
    125127                # For MySQL, r_name must be unique in the first 64 characters.
    126128                # So we are careful with character usage here.
    127129                r_name = '%s_refs_%s_%x' % (r_col, col, abs(hash((r_table, table))))
    128                 final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
    129                     (qn(r_table), truncate_name(r_name, self.connection.ops.max_name_length()),
    130                     qn(r_col), qn(table), qn(col),
    131                     self.connection.ops.deferrable_sql()))
     130                if rel_opts.create_db_schema:
     131                    final_output.append(style.SQL_KEYWORD('ALTER TABLE') + ' %s ADD CONSTRAINT %s FOREIGN KEY (%s) REFERENCES %s (%s)%s;' % \
     132                        (qn(r_table), truncate_name(r_name, self.connection.ops.max_name_length()),
     133                        qn(r_col), qn(table), qn(col),
     134                        self.connection.ops.deferrable_sql()))
    132135            del pending_references[model]
    133136        return final_output
    134137
     
    259262        qn = self.connection.ops.quote_name
    260263        output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
    261264                              style.SQL_TABLE(qn(model._meta.db_table)))]
    262         if model in references_to_delete:
     265        if model in references_to_delete and model._meta.create_db_schema:
    263266            output.extend(self.sql_remove_table_constraints(model, references_to_delete, style))
    264267
    265268        if model._meta.has_auto_field:
     
    279282            r_table = model._meta.db_table
    280283            r_col = model._meta.get_field(f.rel.field_name).column
    281284            r_name = '%s_refs_%s_%x' % (col, r_col, abs(hash((table, r_table))))
    282             output.append('%s %s %s %s;' % \
    283                 (style.SQL_KEYWORD('ALTER TABLE'),
    284                 style.SQL_TABLE(qn(table)),
    285                 style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
    286                 style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length()))))
     285            if rel_class._meta.create_db_schema:
     286                output.append('%s %s %s %s;' % \
     287                    (style.SQL_KEYWORD('ALTER TABLE'),
     288                    style.SQL_TABLE(qn(table)),
     289                    style.SQL_KEYWORD(self.connection.ops.drop_foreignkey_sql()),
     290                    style.SQL_FIELD(truncate_name(r_name, self.connection.ops.max_name_length()))))
    287291        del references_to_delete[model]
    288292        return output
    289293
     
    401405    def sql_table_creation_suffix(self):
    402406        "SQL to append to the end of the test table creation statements"
    403407        return ''
    404 
  • django/core/management/commands/reset.py

     
    4545  * The database isn't running or isn't configured correctly.
    4646  * At least one of the database tables doesn't exist.
    4747  * The SQL was invalid.
    48 Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run.
    49 The full error: %s""" % (app_name, app_name, e))
     48Hint: Look at the output of 'django-admin.py sqlreset %s'. That's the SQL this command wasn't able to run:\n%s.
     49The full error: %s""" % (app_name, app_name, sql, e))
    5050            transaction.commit_unless_managed()
    5151        else:
    5252            print "Reset cancelled."
  • django/core/management/commands/syncdb.py

     
    4141                # but raises an ImportError for some reason. The only way we
    4242                # can do this is to check the text of the exception. Note that
    4343                # we're a bit broad in how we check the text, because different
    44                 # Python implementations may not use the same text. 
     44                # Python implementations may not use the same text.
    4545                # CPython uses the text "No module named management"
    4646                # PyPy uses "No module named myproject.myapp.management"
    4747                msg = exc.args[0]
     
    6060        for app in models.get_apps():
    6161            app_name = app.__name__.split('.')[-2]
    6262            model_list = models.get_models(app)
    63             for model in model_list:
     63            for model in (m for m in model_list if m._meta.create_db_schema):
    6464                # Create the model's database table, if it doesn't already exist.
    6565                if verbosity >= 2:
    6666                    print "Processing %s.%s model" % (app_name, model._meta.object_name)
     
    9999        # Send the post_syncdb signal, so individual apps can do whatever they need
    100100        # to do at this point.
    101101        emit_post_sync_signal(created_models, verbosity, interactive)
    102        
     102
    103103        # The connection may have been closed by a syncdb handler.
    104104        cursor = connection.cursor()
    105        
     105
    106106        # Install custom SQL for the app (but only if this
    107107        # is a model we've just created)
    108108        for app in models.get_apps():
     
    132132        for app in models.get_apps():
    133133            app_name = app.__name__.split('.')[-2]
    134134            for model in models.get_models(app):
    135                 if model in created_models:
     135                if model in created_models and model._meta.create_db_schema:
    136136                    index_sql = connection.creation.sql_indexes_for_model(model, self.style)
    137137                    if index_sql:
    138138                        if verbosity >= 1:
  • django/core/management/sql.py

     
    119119def sql_flush(style, only_django=False):
    120120    """
    121121    Returns a list of the SQL statements used to flush the database.
    122    
     122
    123123    If only_django is True, then only table names that have associated Django
    124124    models and are in INSTALLED_APPS will be included.
    125125    """
    126126    from django.db import connection
    127127    if only_django:
    128         tables = connection.introspection.django_table_names()
     128        tables = connection.introspection.django_table_names(filter_not_generated_tables=True)
    129129    else:
    130130        tables = connection.introspection.table_names()
    131     statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list())
     131    statements = connection.ops.sql_flush(style, tables, connection.introspection.sequence_list(filter_not_generated_tables=True))
    132132    return statements
    133133
    134134def sql_custom(app, style):
     
    148148    "Returns a list of the CREATE INDEX SQL statements for all models in the given app."
    149149    from django.db import connection, models
    150150    output = []
    151     for model in models.get_models(app):
     151    for model in (m for m in models.get_models(app) if m._meta.create_db_schema):
    152152        output.extend(connection.creation.sql_indexes_for_model(model, style))
    153153    return output
    154154
  • 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

     
    430430    ymasuda@ethercube.com
    431431    Jarek Zgoda <jarek.zgoda@gmail.com>
    432432    Cheng Zhang
     433    Alexander Myodov <alex@myodov.com>
    433434
    434435A big THANK YOU goes to:
    435436
Back to Top