Ticket #3163: unmanaged_models.diff

File unmanaged_models.diff, 12.0 KB (added by rfk, 6 years ago)

patch for unmanaged models, including tests and doc update

  • 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', 'managed')
    2525
    2626class Options(object):
    2727    def __init__(self, meta, app_label=None):
     
    4242        self.pk = None
    4343        self.has_auto_field, self.auto_field = False, None
    4444        self.abstract = False
     45        self.managed = True
    4546        self.parents = SortedDict()
    4647        self.duplicate_targets = {}
    4748        # Managers that have been inherited from abstract base classes. These
  • django/db/backends/__init__.py

     
    450450        tables = set()
    451451        for app in models.get_apps():
    452452            for model in models.get_models(app):
    453                 tables.add(model._meta.db_table)
    454                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
     453                if model._meta.managed:
     454                    tables.add(model._meta.db_table)
     455                    tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
    455456        if only_existing:
    456457            tables = [t for t in tables if self.table_name_converter(t) in self.table_names()]
    457458        return tables
     
    476477
    477478        for app in apps:
    478479            for model in models.get_models(app):
     480                if not model._meta.managed:
     481                    continue
    479482                for f in model._meta.local_fields:
    480483                    if isinstance(f, models.AutoField):
    481484                        sequence_list.append({'table': model._meta.db_table, 'column': f.column})
  • django/db/backends/creation.py

     
    3333        from django.db import models
    3434
    3535        opts = model._meta
     36        if not opts.managed:
     37            return [], {}         
    3638        final_output = []
    3739        table_output = []
    3840        pending_references = {}
     
    112114        "Returns any ALTER TABLE statements to add constraints after the fact."
    113115        from django.db.backends.util import truncate_name
    114116
     117        if not model._meta.managed:
     118            return []
    115119        qn = self.connection.ops.quote_name
    116120        final_output = []
    117121        opts = model._meta
     
    225229
    226230    def sql_indexes_for_model(self, model, style):
    227231        "Returns the CREATE INDEX SQL statements for a single model"
     232        if not model._meta.managed:
     233            return []
    228234        output = []
    229235        for f in model._meta.local_fields:
    230236            output.extend(self.sql_indexes_for_field(model, f, style))
     
    255261
    256262    def sql_destroy_model(self, model, references_to_delete, style):
    257263        "Return the DROP TABLE and restraint dropping statements for a single model"
     264        if not model._meta.managed:
     265            return []
    258266        # Drop the table now
    259267        qn = self.connection.ops.quote_name
    260268        output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
     
    271279    def sql_remove_table_constraints(self, model, references_to_delete, style):
    272280        from django.db.backends.util import truncate_name
    273281
     282        if not model._meta.managed:
     283            return []
    274284        output = []
    275285        qn = self.connection.ops.quote_name
    276286        for rel_class, f in references_to_delete[model]:
  • django/core/management/commands/syncdb.py

     
    7171                    if refto in seen_models:
    7272                        sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references))
    7373                sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references))
    74                 if verbosity >= 1:
     74                if verbosity >= 1 and sql:
    7575                    print "Creating table %s" % model._meta.db_table
    7676                for statement in sql:
    7777                    cursor.execute(statement)
  • tests/modeltests/unmanaged_models/__init__.py

     
     1
     2
  • tests/modeltests/unmanaged_models/models.py

     
     1"""
     2xx. unmanaged_models
     3 
     4Models can have a ``managed`` attribute, which specifies whether the
     5SQL code is generated for the table on various manage.py operations.
     6The default is True.
     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), usual
     16  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; managed = 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 managed = False disables the SQL
     22  generation properly.
     23* The intermediate table for M2M relations is not ever verified explicitly,
     24  because it is not ever marked with managed 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: managed 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"""
     6202: managed is set to True.
     63    SQL is generated for all three models.
     64"""
     65checks['A02'] = True
     66checks['B02'] = True
     67checks['C02'] = True
     68 
     69class A02(models.Model):
     70    class Meta:
     71        db_table = 'A02'
     72        managed = True
     73 
     74    f_a = models.TextField(db_index = True)
     75    f_b = models.IntegerField()
     76 
     77class B02(models.Model):
     78    class Meta:
     79        db_table = 'B02'
     80        managed = True
     81 
     82    fk_a = models.ForeignKey(A02)
     83    f_a = models.TextField(db_index = True)
     84    f_b = models.IntegerField()
     85 
     86class C02(models.Model):
     87    class Meta:
     88        db_table = 'C02'
     89        managed = True
     90 
     91    mm_a = models.ManyToManyField(A02, db_table = 'D02')
     92    f_a = models.TextField(db_index = True)
     93    f_b = models.IntegerField()
     94 
     95 
     96"""
     9703: managed is set to False.
     98    SQL is NOT generated for any of the three models.
     99"""
     100checks['A03'] = False
     101checks['B03'] = False
     102checks['C03'] = False
     103 
     104class A03(models.Model):
     105    class Meta:
     106        db_table = 'A03'
     107        managed = False
     108 
     109    f_a = models.TextField(db_index = True)
     110    f_b = models.IntegerField()
     111 
     112class B03(models.Model):
     113    class Meta:
     114        db_table = 'B03'
     115        managed = False
     116 
     117    fk_a = models.ForeignKey(A03)
     118    f_a = models.TextField(db_index = True)
     119    f_b = models.IntegerField()
     120 
     121class C03(models.Model):
     122    class Meta:
     123        db_table = 'C03'
     124        managed = False
     125 
     126    mm_a = models.ManyToManyField(A03, db_table = 'D03')
     127    f_a = models.TextField(db_index = True)
     128    f_b = models.IntegerField()
     129 
     130 
     131# We will use short names for these templates
     132sql_templates = {
     133        'create table': 'CREATE TABLE "%s"',
     134        'create index': 'CREATE INDEX "%s_f_a"',
     135        'drop table': 'DROP TABLE "%s"',
     136        'delete from': 'DELETE FROM "%s"'
     137}
     138 
     139def get_failed_models(arr_sql, sql_template_names):
     140    """
     141    Find the models which should not be in the SQL but they are present,
     142    or they should be in the SQL but they are missing.
     143    """
     144    txt_sql = ' '.join(arr_sql)
     145    for (model, should_be_present) in checks.iteritems():
     146        # Do we expect to see the model name in the SQL text?
     147        for sql_template_name in sql_template_names:
     148            # We are interested not in just the model name like "A01",
     149            # but in the whole string like 'CREATE TABLE "A01"'
     150            # so we apply the model name to the template
     151            # to find out the expected string
     152            expected = (sql_templates[sql_template_name])%model
     153            if ((expected in txt_sql) != should_be_present):
     154                # Our expectations failed!
     155                yield 'The string %s %s present in SQL but it %s.'%(
     156                    expected,
     157                    {False: 'is not', True: 'is'}[expected in txt_sql],
     158                    {False: 'should not be', True: 'should be'}[should_be_present]
     159                    )
     160 
     161 
     162__test__ = {'API_TESTS':"""
     163>>> from django.db.models import get_app
     164>>> from django.core.management.sql import *
     165>>> from django.core.management.color import no_style
     166>>> import sys
     167 
     168>>> myapp = get_app('unmanaged_models')
     169>>> mystyle = no_style()
     170 
     171# a. Verify sql_create
     172>>> list(get_failed_models( sql_create(myapp, mystyle), ['create table'] ))
     173[]
     174 
     175# b. Verify sql_delete
     176>>> list(get_failed_models( sql_delete(myapp, mystyle), ['drop table'] ))
     177[]
     178 
     179# c. Verify sql_reset
     180>>> list(get_failed_models( sql_reset(myapp, mystyle), ['drop table', 'create table', 'create index'] ))
     181[]
     182 
     183# d. Verify sql_flush
     184>>> # sql_flush(mystyle)
     185>>> list(get_failed_models( sql_flush(mystyle), ['delete from'] ))
     186[]
     187 
     188# e. Verify sql_custom
     189# No custom data provided, should not be no output.
     190>>> sql_custom(myapp,mystyle)
     191[]
     192 
     193# f. Verify sql_indexes
     194>>> list(get_failed_models( sql_indexes(myapp, mystyle), ['create index'] ))
     195[]
     196 
     197# g. Verify sql_all
     198>>> list(get_failed_models( sql_all(myapp, mystyle), ['create table', 'create index'] ))
     199[]
     200"""}
  • AUTHORS

     
    443443    Mykola Zamkovoi <nickzam@gmail.com>
    444444    Jarek Zgoda <jarek.zgoda@gmail.com>
    445445    Cheng Zhang
     446    Alexander Myodov <alex@myodov.com>
    446447
    447448A big THANK YOU goes to:
    448449
  • docs/ref/models/options.txt

     
    181181    verbose_name_plural = "stories"
    182182
    183183If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``.
     184
     185``managed``
     186-----------------------
     187
     188.. attribute:: Options.managed
     189
     190.. versionadded:: 1.1
     191
     192If ``False``, Django's database-management facilities (such as syncdb) will
     193avoid processing this model.  Set this option on models that use
     194manually-created database tables or views.
     195
Back to Top