Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10008 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit b4dd4d4bb7db6533e13be1455ccdc52c3d50cac3 1 parent 98710a5
Malcolm Tredinnick authored March 09, 2009
1  AUTHORS
@@ -297,6 +297,7 @@ answer newbie questions, and generally made Django that much better:
297 297
     James Murty
298 298
     msundstr
299 299
     Robert Myers <myer0052@gmail.com>
  300
+    Alexander Myodov <alex@myodov.com>
300 301
     Nebojša Dorđević
301 302
     Doug Napoleone <doug@dougma.com>
302 303
     Gopal Narayanan <gopastro@gmail.com>
2  django/core/management/commands/syncdb.py
@@ -71,7 +71,7 @@ def handle_noargs(self, **options):
71 71
                     if refto in seen_models:
72 72
                         sql.extend(connection.creation.sql_for_pending_references(refto, self.style, pending_references))
73 73
                 sql.extend(connection.creation.sql_for_pending_references(model, self.style, pending_references))
74  
-                if verbosity >= 1:
  74
+                if verbosity >= 1 and sql:
75 75
                     print "Creating table %s" % model._meta.db_table
76 76
                 for statement in sql:
77 77
                     cursor.execute(statement)
4  django/db/backends/__init__.py
@@ -450,6 +450,8 @@ def django_table_names(self, only_existing=False):
450 450
         tables = set()
451 451
         for app in models.get_apps():
452 452
             for model in models.get_models(app):
  453
+                if not model._meta.managed:
  454
+                    continue
453 455
                 tables.add(model._meta.db_table)
454 456
                 tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
455 457
         if only_existing:
@@ -476,6 +478,8 @@ def sequence_list(self):
476 478
 
477 479
         for app in apps:
478 480
             for model in models.get_models(app):
  481
+                if not model._meta.managed:
  482
+                    continue
479 483
                 for f in model._meta.local_fields:
480 484
                     if isinstance(f, models.AutoField):
481 485
                         sequence_list.append({'table': model._meta.db_table, 'column': f.column})
10  django/db/backends/creation.py
@@ -33,6 +33,8 @@ def sql_create_model(self, model, style, known_models=set()):
33 33
         from django.db import models
34 34
 
35 35
         opts = model._meta
  36
+        if not opts.managed:
  37
+            return [], {}
36 38
         final_output = []
37 39
         table_output = []
38 40
         pending_references = {}
@@ -112,6 +114,8 @@ def sql_for_pending_references(self, model, style, pending_references):
112 114
         "Returns any ALTER TABLE statements to add constraints after the fact."
113 115
         from django.db.backends.util import truncate_name
114 116
 
  117
+        if not model._meta.managed:
  118
+            return []
115 119
         qn = self.connection.ops.quote_name
116 120
         final_output = []
117 121
         opts = model._meta
@@ -225,6 +229,8 @@ def sql_for_inline_many_to_many_references(self, model, field, style):
225 229
 
226 230
     def sql_indexes_for_model(self, model, style):
227 231
         "Returns the CREATE INDEX SQL statements for a single model"
  232
+        if not model._meta.managed:
  233
+            return []
228 234
         output = []
229 235
         for f in model._meta.local_fields:
230 236
             output.extend(self.sql_indexes_for_field(model, f, style))
@@ -255,6 +261,8 @@ def sql_indexes_for_field(self, model, f, style):
255 261
 
256 262
     def sql_destroy_model(self, model, references_to_delete, style):
257 263
         "Return the DROP TABLE and restraint dropping statements for a single model"
  264
+        if not model._meta.managed:
  265
+            return []
258 266
         # Drop the table now
259 267
         qn = self.connection.ops.quote_name
260 268
         output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'),
@@ -271,6 +279,8 @@ def sql_destroy_model(self, model, references_to_delete, style):
271 279
     def sql_remove_table_constraints(self, model, references_to_delete, style):
272 280
         from django.db.backends.util import truncate_name
273 281
 
  282
+        if not model._meta.managed:
  283
+            return []
274 284
         output = []
275 285
         qn = self.connection.ops.quote_name
276 286
         for rel_class, f in references_to_delete[model]:
3  django/db/models/options.py
@@ -21,7 +21,7 @@
21 21
 DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
22 22
                  'unique_together', 'permissions', 'get_latest_by',
23 23
                  'order_with_respect_to', 'app_label', 'db_tablespace',
24  
-                 'abstract')
  24
+                 'abstract', 'managed')
25 25
 
26 26
 class Options(object):
27 27
     def __init__(self, meta, app_label=None):
@@ -42,6 +42,7 @@ def __init__(self, meta, app_label=None):
42 42
         self.pk = None
43 43
         self.has_auto_field, self.auto_field = False, None
44 44
         self.abstract = False
  45
+        self.managed = True
45 46
         self.parents = SortedDict()
46 47
         self.duplicate_targets = {}
47 48
         # Managers that have been inherited from abstract base classes. These
4  docs/ref/django-admin.txt
@@ -431,6 +431,8 @@ Currently supported:
431 431
 	* ``django`` for all ``*.py`` and ``*.html`` files (default)
432 432
 	* ``djangojs`` for ``*.js`` files
433 433
 
  434
+.. _django-admin-reset:
  435
+
434 436
 reset <appname appname ...>
435 437
 ---------------------------
436 438
 
@@ -634,6 +636,8 @@ This command is disabled when the ``--settings`` option to
634 636
 situations, either omit the ``--settings`` option or unset
635 637
 ``DJANGO_SETTINGS_MODULE``.
636 638
 
  639
+.. _django-admin-syncdb:
  640
+
637 641
 syncdb
638 642
 ------
639 643
 
25  docs/ref/models/options.txt
@@ -75,6 +75,30 @@ Example::
75 75
 
76 76
 See the docs for :meth:`~django.db.models.QuerySet.latest` for more.
77 77
 
  78
+``managed``
  79
+-----------------------
  80
+
  81
+.. attribute:: Options.managed
  82
+
  83
+.. versionadded:: 1.1
  84
+
  85
+If ``False``, no database table creation or deletion operations will be
  86
+performed for this model. This is useful if the model represents an existing
  87
+table or a database view that has been created by some other means.
  88
+
  89
+The default value is ``True``, meaning Django will create the appropriate
  90
+database tables in :ref:`django-admin-syncdb` and remove them as part of a
  91
+:ref:`reset <django-admin-reset>` management command.
  92
+
  93
+If a model contains a :class:`~django.db.models.ManyToManyField` and has
  94
+``managed=False``, the intermediate table for the many-to-many join will also
  95
+not be created. Should you require the intermediate table to be created, set
  96
+it up as an explicit model and use the :attr:`ManyToManyField.through`
  97
+attribute.
  98
+
  99
+For tests involving models with ``managed=False``, it's up to you to ensure
  100
+the correct tables are created as part of the test setup.
  101
+
78 102
 ``order_with_respect_to``
79 103
 -------------------------
80 104
 
@@ -181,3 +205,4 @@ The plural name for the object::
181 205
     verbose_name_plural = "stories"
182 206
 
183 207
 If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``.
  208
+
2  tests/modeltests/unmanaged_models/__init__.py
... ...
@@ -0,0 +1,2 @@
  1
+
  2
+
117  tests/modeltests/unmanaged_models/models.py
... ...
@@ -0,0 +1,117 @@
  1
+"""
  2
+Models can have a ``managed`` attribute, which specifies whether the SQL code
  3
+is generated for the table on various manage.py operations.
  4
+"""
  5
+
  6
+from django.db import models
  7
+
  8
+#  All of these models are creatd in the database by Django.
  9
+
  10
+class A01(models.Model):
  11
+    f_a = models.CharField(max_length=10, db_index=True)
  12
+    f_b = models.IntegerField()
  13
+
  14
+    class Meta:
  15
+        db_table = 'A01'
  16
+
  17
+    def __unicode__(self):
  18
+        return self.f_a
  19
+
  20
+class B01(models.Model):
  21
+    fk_a = models.ForeignKey(A01)
  22
+    f_a = models.CharField(max_length=10, db_index=True)
  23
+    f_b = models.IntegerField()
  24
+
  25
+    class Meta:
  26
+        db_table = 'B01'
  27
+        # 'managed' is True by default. This tests we can set it explicitly.
  28
+        managed = True
  29
+
  30
+    def __unicode__(self):
  31
+        return self.f_a
  32
+
  33
+class C01(models.Model):
  34
+    mm_a = models.ManyToManyField(A01, db_table='D01')
  35
+    f_a = models.CharField(max_length=10, db_index=True)
  36
+    f_b = models.IntegerField()
  37
+
  38
+    class Meta:
  39
+        db_table = 'C01'
  40
+
  41
+    def __unicode__(self):
  42
+        return self.f_a
  43
+
  44
+# All of these models use the same tables as the previous set (they are shadows
  45
+# of possibly a subset of the columns). There should be no creation errors,
  46
+# since we have told Django they aren't managed by Django.
  47
+
  48
+class A02(models.Model):
  49
+    f_a = models.CharField(max_length=10, db_index=True)
  50
+
  51
+    class Meta:
  52
+        db_table = 'A01'
  53
+        managed = False
  54
+
  55
+    def __unicode__(self):
  56
+        return self.f_a
  57
+
  58
+class B02(models.Model):
  59
+    class Meta:
  60
+        db_table = 'B01'
  61
+        managed = False
  62
+
  63
+    fk_a = models.ForeignKey(A02)
  64
+    f_a = models.CharField(max_length=10, db_index=True)
  65
+    f_b = models.IntegerField()
  66
+
  67
+    def __unicode__(self):
  68
+        return self.f_a
  69
+
  70
+# To re-use the many-to-many intermediate table, we need to manually set up
  71
+# things up.
  72
+class C02(models.Model):
  73
+    mm_a = models.ManyToManyField(A02, through="Intermediate")
  74
+    f_a = models.CharField(max_length=10, db_index=True)
  75
+    f_b = models.IntegerField()
  76
+
  77
+    class Meta:
  78
+        db_table = 'C01'
  79
+        managed = False
  80
+
  81
+    def __unicode__(self):
  82
+        return self.f_a
  83
+
  84
+class Intermediate(models.Model):
  85
+    a02 = models.ForeignKey(A02, db_column="a01_id")
  86
+    c02 = models.ForeignKey(C02, db_column="c01_id")
  87
+
  88
+    class Meta:
  89
+        db_table = 'D01'
  90
+        managed = False
  91
+
  92
+__test__ = {'API_TESTS':"""
  93
+The main test here is that the all the models can be created without any
  94
+database errors. We can also do some more simple insertion and lookup tests
  95
+whilst we're here to show that the second of models do refer to the tables from
  96
+the first set.
  97
+
  98
+# Insert some data into one set of models.
  99
+>>> a = A01.objects.create(f_a="foo", f_b=42)
  100
+>>> _ = B01.objects.create(fk_a=a, f_a="fred", f_b=1729)
  101
+>>> c = C01.objects.create(f_a="barney", f_b=1)
  102
+>>> c.mm_a = [a]
  103
+
  104
+# ... and pull it out via the other set.
  105
+>>> A02.objects.all()
  106
+[<A02: foo>]
  107
+>>> b = B02.objects.all()[0]
  108
+>>> b
  109
+<B02: fred>
  110
+>>> b.fk_a
  111
+<A02: foo>
  112
+>>> C02.objects.filter(f_a=None)
  113
+[]
  114
+>>> C02.objects.filter(mm_a=a.id)
  115
+[<C02: barney>]
  116
+
  117
+"""}

0 notes on commit b4dd4d4

Please sign in to comment.
Something went wrong with that request. Please try again.