summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--AUTHORS1
-rw-r--r--django/core/management/commands/syncdb.py2
-rw-r--r--django/db/backends/__init__.py4
-rw-r--r--django/db/backends/creation.py10
-rw-r--r--django/db/models/options.py3
-rw-r--r--docs/ref/django-admin.txt4
-rw-r--r--docs/ref/models/options.txt25
-rw-r--r--tests/modeltests/unmanaged_models/__init__.py2
-rw-r--r--tests/modeltests/unmanaged_models/models.py117
9 files changed, 166 insertions, 2 deletions
diff --git a/AUTHORS b/AUTHORS
index e49bb392aa..1b2a0cfadb 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -297,6 +297,7 @@ answer newbie questions, and generally made Django that much better:
James Murty
msundstr
Robert Myers <myer0052@gmail.com>
+ Alexander Myodov <alex@myodov.com>
Nebojša Dorđević
Doug Napoleone <doug@dougma.com>
Gopal Narayanan <gopastro@gmail.com>
diff --git a/django/core/management/commands/syncdb.py b/django/core/management/commands/syncdb.py
index a41fd5725b..dbef7a6a95 100644
--- a/django/core/management/commands/syncdb.py
+++ b/django/core/management/commands/syncdb.py
@@ -71,7 +71,7 @@ class Command(NoArgsCommand):
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)
diff --git a/django/db/backends/__init__.py b/django/db/backends/__init__.py
index 6b027de193..5a3cb53842 100644
--- a/django/db/backends/__init__.py
+++ b/django/db/backends/__init__.py
@@ -450,6 +450,8 @@ class BaseDatabaseIntrospection(object):
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 @@ class BaseDatabaseIntrospection(object):
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})
diff --git a/django/db/backends/creation.py b/django/db/backends/creation.py
index e6399874ef..9bebce9065 100644
--- a/django/db/backends/creation.py
+++ b/django/db/backends/creation.py
@@ -33,6 +33,8 @@ class BaseDatabaseCreation(object):
from django.db import models
opts = model._meta
+ if not opts.managed:
+ return [], {}
final_output = []
table_output = []
pending_references = {}
@@ -112,6 +114,8 @@ class BaseDatabaseCreation(object):
"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 @@ class BaseDatabaseCreation(object):
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 @@ class BaseDatabaseCreation(object):
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 @@ class BaseDatabaseCreation(object):
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]:
diff --git a/django/db/models/options.py b/django/db/models/options.py
index 06e17c61ad..2dd3d256a1 100644
--- a/django/db/models/options.py
+++ b/django/db/models/options.py
@@ -21,7 +21,7 @@ get_verbose_name = lambda class_name: re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|
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 @@ class Options(object):
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
diff --git a/docs/ref/django-admin.txt b/docs/ref/django-admin.txt
index dff5e681f8..84ae4d34b5 100644
--- a/docs/ref/django-admin.txt
+++ b/docs/ref/django-admin.txt
@@ -431,6 +431,8 @@ Currently supported:
* ``django`` for all ``*.py`` and ``*.html`` files (default)
* ``djangojs`` for ``*.js`` files
+.. _django-admin-reset:
+
reset <appname appname ...>
---------------------------
@@ -634,6 +636,8 @@ This command is disabled when the ``--settings`` option to
situations, either omit the ``--settings`` option or unset
``DJANGO_SETTINGS_MODULE``.
+.. _django-admin-syncdb:
+
syncdb
------
diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt
index e4e2d38543..9525e58df9 100644
--- a/docs/ref/models/options.txt
+++ b/docs/ref/models/options.txt
@@ -75,6 +75,30 @@ Example::
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 @@ The plural name for the object::
verbose_name_plural = "stories"
If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``.
+
diff --git a/tests/modeltests/unmanaged_models/__init__.py b/tests/modeltests/unmanaged_models/__init__.py
new file mode 100644
index 0000000000..139597f9cb
--- /dev/null
+++ b/tests/modeltests/unmanaged_models/__init__.py
@@ -0,0 +1,2 @@
+
+
diff --git a/tests/modeltests/unmanaged_models/models.py b/tests/modeltests/unmanaged_models/models.py
new file mode 100644
index 0000000000..717c8d5b3a
--- /dev/null
+++ b/tests/modeltests/unmanaged_models/models.py
@@ -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>]
+
+"""}