Author: mtredinnick
Date: 2009-03-08 22:35:02 -0500 (Sun, 08 Mar 2009)
New Revision: 10008

Added:
   django/trunk/tests/modeltests/unmanaged_models/
   django/trunk/tests/modeltests/unmanaged_models/__init__.py
   django/trunk/tests/modeltests/unmanaged_models/models.py
Modified:
   django/trunk/AUTHORS
   django/trunk/django/core/management/commands/syncdb.py
   django/trunk/django/db/backends/__init__.py
   django/trunk/django/db/backends/creation.py
   django/trunk/django/db/models/options.py
   django/trunk/docs/ref/django-admin.txt
   django/trunk/docs/ref/models/options.txt
Log:
Fixed #3163 -- Add a "Meta.managed" option to models.

This allows a model to be defined which is not subject to database table
creation and removal. Useful for models that sit over existing tables or
database views.

Thanks to Alexander Myodov, Wolfgang Kriesing and Ryan Kelly for the bulk of
this patch.

Modified: django/trunk/AUTHORS
===================================================================
--- django/trunk/AUTHORS        2009-03-09 03:33:54 UTC (rev 10007)
+++ django/trunk/AUTHORS        2009-03-09 03:35:02 UTC (rev 10008)
@@ -297,6 +297,7 @@
     James Murty
     msundstr
     Robert Myers <[email protected]>
+    Alexander Myodov <[email protected]>
     Nebojša Dorđević
     Doug Napoleone <[email protected]>
     Gopal Narayanan <[email protected]>

Modified: django/trunk/django/core/management/commands/syncdb.py
===================================================================
--- django/trunk/django/core/management/commands/syncdb.py      2009-03-09 
03:33:54 UTC (rev 10007)
+++ django/trunk/django/core/management/commands/syncdb.py      2009-03-09 
03:35:02 UTC (rev 10008)
@@ -71,7 +71,7 @@
                     if refto in seen_models:
                         
sql.extend(connection.creation.sql_for_pending_references(refto, self.style, 
pending_references))
                 
sql.extend(connection.creation.sql_for_pending_references(model, self.style, 
pending_references))
-                if verbosity >= 1:
+                if verbosity >= 1 and sql:
                     print "Creating table %s" % model._meta.db_table
                 for statement in sql:
                     cursor.execute(statement)

Modified: django/trunk/django/db/backends/__init__.py
===================================================================
--- django/trunk/django/db/backends/__init__.py 2009-03-09 03:33:54 UTC (rev 
10007)
+++ django/trunk/django/db/backends/__init__.py 2009-03-09 03:35:02 UTC (rev 
10008)
@@ -450,6 +450,8 @@
         tables = set()
         for app in models.get_apps():
             for model in models.get_models(app):
+                if not model._meta.managed:
+                    continue
                 tables.add(model._meta.db_table)
                 tables.update([f.m2m_db_table() for f in 
model._meta.local_many_to_many])
         if only_existing:
@@ -476,6 +478,8 @@
 
         for app in apps:
             for model in models.get_models(app):
+                if not model._meta.managed:
+                    continue
                 for f in model._meta.local_fields:
                     if isinstance(f, models.AutoField):
                         sequence_list.append({'table': model._meta.db_table, 
'column': f.column})

Modified: django/trunk/django/db/backends/creation.py
===================================================================
--- django/trunk/django/db/backends/creation.py 2009-03-09 03:33:54 UTC (rev 
10007)
+++ django/trunk/django/db/backends/creation.py 2009-03-09 03:35:02 UTC (rev 
10008)
@@ -33,6 +33,8 @@
         from django.db import models
 
         opts = model._meta
+        if not opts.managed:
+            return [], {}
         final_output = []
         table_output = []
         pending_references = {}
@@ -112,6 +114,8 @@
         "Returns any ALTER TABLE statements to add constraints after the fact."
         from django.db.backends.util import truncate_name
 
+        if not model._meta.managed:
+            return []
         qn = self.connection.ops.quote_name
         final_output = []
         opts = model._meta
@@ -225,6 +229,8 @@
 
     def sql_indexes_for_model(self, model, style):
         "Returns the CREATE INDEX SQL statements for a single model"
+        if not model._meta.managed:
+            return []
         output = []
         for f in model._meta.local_fields:
             output.extend(self.sql_indexes_for_field(model, f, style))
@@ -255,6 +261,8 @@
 
     def sql_destroy_model(self, model, references_to_delete, style):
         "Return the DROP TABLE and restraint dropping statements for a single 
model"
+        if not model._meta.managed:
+            return []
         # Drop the table now
         qn = self.connection.ops.quote_name
         output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
@@ -271,6 +279,8 @@
     def sql_remove_table_constraints(self, model, references_to_delete, style):
         from django.db.backends.util import truncate_name
 
+        if not model._meta.managed:
+            return []
         output = []
         qn = self.connection.ops.quote_name
         for rel_class, f in references_to_delete[model]:

Modified: django/trunk/django/db/models/options.py
===================================================================
--- django/trunk/django/db/models/options.py    2009-03-09 03:33:54 UTC (rev 
10007)
+++ django/trunk/django/db/models/options.py    2009-03-09 03:35:02 UTC (rev 
10008)
@@ -21,7 +21,7 @@
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
                  'unique_together', 'permissions', 'get_latest_by',
                  'order_with_respect_to', 'app_label', 'db_tablespace',
-                 'abstract')
+                 'abstract', 'managed')
 
 class Options(object):
     def __init__(self, meta, app_label=None):
@@ -42,6 +42,7 @@
         self.pk = None
         self.has_auto_field, self.auto_field = False, None
         self.abstract = False
+        self.managed = True
         self.parents = SortedDict()
         self.duplicate_targets = {}
         # Managers that have been inherited from abstract base classes. These

Modified: django/trunk/docs/ref/django-admin.txt
===================================================================
--- django/trunk/docs/ref/django-admin.txt      2009-03-09 03:33:54 UTC (rev 
10007)
+++ django/trunk/docs/ref/django-admin.txt      2009-03-09 03:35:02 UTC (rev 
10008)
@@ -431,6 +431,8 @@
        * ``django`` for all ``*.py`` and ``*.html`` files (default)
        * ``djangojs`` for ``*.js`` files
 
+.. _django-admin-reset:
+
 reset <appname appname ...>
 ---------------------------
 
@@ -634,6 +636,8 @@
 situations, either omit the ``--settings`` option or unset
 ``DJANGO_SETTINGS_MODULE``.
 
+.. _django-admin-syncdb:
+
 syncdb
 ------
 

Modified: django/trunk/docs/ref/models/options.txt
===================================================================
--- django/trunk/docs/ref/models/options.txt    2009-03-09 03:33:54 UTC (rev 
10007)
+++ django/trunk/docs/ref/models/options.txt    2009-03-09 03:35:02 UTC (rev 
10008)
@@ -75,6 +75,30 @@
 
 See the docs for :meth:`~django.db.models.QuerySet.latest` for more.
 
+``managed``
+-----------------------
+
+.. attribute:: Options.managed
+
+.. versionadded:: 1.1
+
+If ``False``, no database table creation or deletion operations will be
+performed for this model. This is useful if the model represents an existing
+table or a database view that has been created by some other means.
+
+The default value is ``True``, meaning Django will create the appropriate
+database tables in :ref:`django-admin-syncdb` and remove them as part of a
+:ref:`reset <django-admin-reset>` management command.
+
+If a model contains a :class:`~django.db.models.ManyToManyField` and has
+``managed=False``, the intermediate table for the many-to-many join will also
+not be created. Should you require the intermediate table to be created, set
+it up as an explicit model and use the :attr:`ManyToManyField.through`
+attribute.
+
+For tests involving models with ``managed=False``, it's up to you to ensure
+the correct tables are created as part of the test setup.
+
 ``order_with_respect_to``
 -------------------------
 
@@ -181,3 +205,4 @@
     verbose_name_plural = "stories"
 
 If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``.
+

Added: django/trunk/tests/modeltests/unmanaged_models/__init__.py
===================================================================
--- django/trunk/tests/modeltests/unmanaged_models/__init__.py                  
        (rev 0)
+++ django/trunk/tests/modeltests/unmanaged_models/__init__.py  2009-03-09 
03:35:02 UTC (rev 10008)
@@ -0,0 +1,2 @@
+
+

Added: django/trunk/tests/modeltests/unmanaged_models/models.py
===================================================================
--- django/trunk/tests/modeltests/unmanaged_models/models.py                    
        (rev 0)
+++ django/trunk/tests/modeltests/unmanaged_models/models.py    2009-03-09 
03:35:02 UTC (rev 10008)
@@ -0,0 +1,117 @@
+"""
+Models can have a ``managed`` attribute, which specifies whether the SQL code
+is generated for the table on various manage.py operations.
+"""
+
+from django.db import models
+
+#  All of these models are creatd in the database by Django.
+
+class A01(models.Model):
+    f_a = models.CharField(max_length=10, db_index=True)
+    f_b = models.IntegerField()
+
+    class Meta:
+        db_table = 'A01'
+
+    def __unicode__(self):
+        return self.f_a
+
+class B01(models.Model):
+    fk_a = models.ForeignKey(A01)
+    f_a = models.CharField(max_length=10, db_index=True)
+    f_b = models.IntegerField()
+
+    class Meta:
+        db_table = 'B01'
+        # 'managed' is True by default. This tests we can set it explicitly.
+        managed = True
+
+    def __unicode__(self):
+        return self.f_a
+
+class C01(models.Model):
+    mm_a = models.ManyToManyField(A01, db_table='D01')
+    f_a = models.CharField(max_length=10, db_index=True)
+    f_b = models.IntegerField()
+
+    class Meta:
+        db_table = 'C01'
+
+    def __unicode__(self):
+        return self.f_a
+
+# All of these models use the same tables as the previous set (they are shadows
+# of possibly a subset of the columns). There should be no creation errors,
+# since we have told Django they aren't managed by Django.
+
+class A02(models.Model):
+    f_a = models.CharField(max_length=10, db_index=True)
+
+    class Meta:
+        db_table = 'A01'
+        managed = False
+
+    def __unicode__(self):
+        return self.f_a
+
+class B02(models.Model):
+    class Meta:
+        db_table = 'B01'
+        managed = False
+
+    fk_a = models.ForeignKey(A02)
+    f_a = models.CharField(max_length=10, db_index=True)
+    f_b = models.IntegerField()
+
+    def __unicode__(self):
+        return self.f_a
+
+# To re-use the many-to-many intermediate table, we need to manually set up
+# things up.
+class C02(models.Model):
+    mm_a = models.ManyToManyField(A02, through="Intermediate")
+    f_a = models.CharField(max_length=10, db_index=True)
+    f_b = models.IntegerField()
+
+    class Meta:
+        db_table = 'C01'
+        managed = False
+
+    def __unicode__(self):
+        return self.f_a
+
+class Intermediate(models.Model):
+    a02 = models.ForeignKey(A02, db_column="a01_id")
+    c02 = models.ForeignKey(C02, db_column="c01_id")
+
+    class Meta:
+        db_table = 'D01'
+        managed = False
+
+__test__ = {'API_TESTS':"""
+The main test here is that the all the models can be created without any
+database errors. We can also do some more simple insertion and lookup tests
+whilst we're here to show that the second of models do refer to the tables from
+the first set.
+
+# Insert some data into one set of models.
+>>> a = A01.objects.create(f_a="foo", f_b=42)
+>>> _ = B01.objects.create(fk_a=a, f_a="fred", f_b=1729)
+>>> c = C01.objects.create(f_a="barney", f_b=1)
+>>> c.mm_a = [a]
+
+# ... and pull it out via the other set.
+>>> A02.objects.all()
+[<A02: foo>]
+>>> b = B02.objects.all()[0]
+>>> b
+<B02: fred>
+>>> b.fk_a
+<A02: foo>
+>>> C02.objects.filter(f_a=None)
+[]
+>>> C02.objects.filter(mm_a=a.id)
+[<C02: barney>]
+
+"""}


--~--~---------~--~----~------------~-------~--~----~
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To post to this group, send email to [email protected]
To unsubscribe from this group, send email to 
[email protected]
For more options, visit this group at 
http://groups.google.com/group/django-updates?hl=en
-~----------~----~----~----~------~----~------~--~---

Reply via email to