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
This commit is contained in:
Malcolm Tredinnick 2009-03-09 03:35:02 +00:00
parent 98710a5a28
commit b4dd4d4bb7
9 changed files with 166 additions and 2 deletions

View File

@ -297,6 +297,7 @@ answer newbie questions, and generally made Django that much better:
James Murty James Murty
msundstr msundstr
Robert Myers <myer0052@gmail.com> Robert Myers <myer0052@gmail.com>
Alexander Myodov <alex@myodov.com>
Nebojša Dorđević Nebojša Dorđević
Doug Napoleone <doug@dougma.com> Doug Napoleone <doug@dougma.com>
Gopal Narayanan <gopastro@gmail.com> Gopal Narayanan <gopastro@gmail.com>

View File

@ -71,7 +71,7 @@ class Command(NoArgsCommand):
if refto in seen_models: 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(refto, self.style, pending_references))
sql.extend(connection.creation.sql_for_pending_references(model, 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 print "Creating table %s" % model._meta.db_table
for statement in sql: for statement in sql:
cursor.execute(statement) cursor.execute(statement)

View File

@ -450,6 +450,8 @@ class BaseDatabaseIntrospection(object):
tables = set() tables = set()
for app in models.get_apps(): for app in models.get_apps():
for model in models.get_models(app): for model in models.get_models(app):
if not model._meta.managed:
continue
tables.add(model._meta.db_table) tables.add(model._meta.db_table)
tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many]) tables.update([f.m2m_db_table() for f in model._meta.local_many_to_many])
if only_existing: if only_existing:
@ -476,6 +478,8 @@ class BaseDatabaseIntrospection(object):
for app in apps: for app in apps:
for model in models.get_models(app): for model in models.get_models(app):
if not model._meta.managed:
continue
for f in model._meta.local_fields: for f in model._meta.local_fields:
if isinstance(f, models.AutoField): if isinstance(f, models.AutoField):
sequence_list.append({'table': model._meta.db_table, 'column': f.column}) sequence_list.append({'table': model._meta.db_table, 'column': f.column})

View File

@ -33,6 +33,8 @@ class BaseDatabaseCreation(object):
from django.db import models from django.db import models
opts = model._meta opts = model._meta
if not opts.managed:
return [], {}
final_output = [] final_output = []
table_output = [] table_output = []
pending_references = {} pending_references = {}
@ -112,6 +114,8 @@ class BaseDatabaseCreation(object):
"Returns any ALTER TABLE statements to add constraints after the fact." "Returns any ALTER TABLE statements to add constraints after the fact."
from django.db.backends.util import truncate_name from django.db.backends.util import truncate_name
if not model._meta.managed:
return []
qn = self.connection.ops.quote_name qn = self.connection.ops.quote_name
final_output = [] final_output = []
opts = model._meta opts = model._meta
@ -225,6 +229,8 @@ class BaseDatabaseCreation(object):
def sql_indexes_for_model(self, model, style): def sql_indexes_for_model(self, model, style):
"Returns the CREATE INDEX SQL statements for a single model" "Returns the CREATE INDEX SQL statements for a single model"
if not model._meta.managed:
return []
output = [] output = []
for f in model._meta.local_fields: for f in model._meta.local_fields:
output.extend(self.sql_indexes_for_field(model, f, style)) 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): def sql_destroy_model(self, model, references_to_delete, style):
"Return the DROP TABLE and restraint dropping statements for a single model" "Return the DROP TABLE and restraint dropping statements for a single model"
if not model._meta.managed:
return []
# Drop the table now # Drop the table now
qn = self.connection.ops.quote_name qn = self.connection.ops.quote_name
output = ['%s %s;' % (style.SQL_KEYWORD('DROP TABLE'), 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): def sql_remove_table_constraints(self, model, references_to_delete, style):
from django.db.backends.util import truncate_name from django.db.backends.util import truncate_name
if not model._meta.managed:
return []
output = [] output = []
qn = self.connection.ops.quote_name qn = self.connection.ops.quote_name
for rel_class, f in references_to_delete[model]: for rel_class, f in references_to_delete[model]:

View File

@ -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', DEFAULT_NAMES = ('verbose_name', 'db_table', 'ordering',
'unique_together', 'permissions', 'get_latest_by', 'unique_together', 'permissions', 'get_latest_by',
'order_with_respect_to', 'app_label', 'db_tablespace', 'order_with_respect_to', 'app_label', 'db_tablespace',
'abstract') 'abstract', 'managed')
class Options(object): class Options(object):
def __init__(self, meta, app_label=None): def __init__(self, meta, app_label=None):
@ -42,6 +42,7 @@ class Options(object):
self.pk = None self.pk = None
self.has_auto_field, self.auto_field = False, None self.has_auto_field, self.auto_field = False, None
self.abstract = False self.abstract = False
self.managed = True
self.parents = SortedDict() self.parents = SortedDict()
self.duplicate_targets = {} self.duplicate_targets = {}
# Managers that have been inherited from abstract base classes. These # Managers that have been inherited from abstract base classes. These

View File

@ -431,6 +431,8 @@ Currently supported:
* ``django`` for all ``*.py`` and ``*.html`` files (default) * ``django`` for all ``*.py`` and ``*.html`` files (default)
* ``djangojs`` for ``*.js`` files * ``djangojs`` for ``*.js`` files
.. _django-admin-reset:
reset <appname appname ...> reset <appname appname ...>
--------------------------- ---------------------------
@ -634,6 +636,8 @@ This command is disabled when the ``--settings`` option to
situations, either omit the ``--settings`` option or unset situations, either omit the ``--settings`` option or unset
``DJANGO_SETTINGS_MODULE``. ``DJANGO_SETTINGS_MODULE``.
.. _django-admin-syncdb:
syncdb syncdb
------ ------

View File

@ -75,6 +75,30 @@ Example::
See the docs for :meth:`~django.db.models.QuerySet.latest` for more. 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`` ``order_with_respect_to``
------------------------- -------------------------
@ -181,3 +205,4 @@ The plural name for the object::
verbose_name_plural = "stories" verbose_name_plural = "stories"
If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``. If this isn't given, Django will use :attr:`~Options.verbose_name` + ``"s"``.

View File

@ -0,0 +1,2 @@

View File

@ -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>]
"""}