mirror of
https://github.com/django/django.git
synced 2024-12-26 11:06:07 +00:00
92824e7102
Models with deferred fields, or which are proxying for an existing model, now return the same ContentType object as the real model they reflect. Thanks to tomasz.elendt for help with fixing this. git-svn-id: http://code.djangoproject.com/svn/django/trunk@10523 bcc190cf-cafb-0310-a4f2-bffc1f526a37
184 lines
5.1 KiB
Python
184 lines
5.1 KiB
Python
"""
|
|
By specifying the 'proxy' Meta attribute, model subclasses can specify that
|
|
they will take data directly from the table of their base class table rather
|
|
than using a new table of their own. This allows them to act as simple proxies,
|
|
providing a modified interface to the data from the base class.
|
|
"""
|
|
|
|
from django.contrib.contenttypes.models import ContentType
|
|
from django.db import models
|
|
|
|
|
|
# A couple of managers for testing managing overriding in proxy model cases.
|
|
|
|
class PersonManager(models.Manager):
|
|
def get_query_set(self):
|
|
return super(PersonManager, self).get_query_set().exclude(name="fred")
|
|
|
|
class SubManager(models.Manager):
|
|
def get_query_set(self):
|
|
return super(SubManager, self).get_query_set().exclude(name="wilma")
|
|
|
|
class Person(models.Model):
|
|
"""
|
|
A simple concrete base class.
|
|
"""
|
|
name = models.CharField(max_length=50)
|
|
|
|
objects = PersonManager()
|
|
|
|
def __unicode__(self):
|
|
return self.name
|
|
|
|
class Abstract(models.Model):
|
|
"""
|
|
A simple abstract base class, to be used for error checking.
|
|
"""
|
|
data = models.CharField(max_length=10)
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
class MyPerson(Person):
|
|
"""
|
|
A proxy subclass, this should not get a new table. Overrides the default
|
|
manager.
|
|
"""
|
|
class Meta:
|
|
proxy = True
|
|
ordering = ["name"]
|
|
|
|
objects = SubManager()
|
|
other = PersonManager()
|
|
|
|
def has_special_name(self):
|
|
return self.name.lower() == "special"
|
|
|
|
class ManagerMixin(models.Model):
|
|
excluder = SubManager()
|
|
|
|
class Meta:
|
|
abstract = True
|
|
|
|
class OtherPerson(Person, ManagerMixin):
|
|
"""
|
|
A class with the default manager from Person, plus an secondary manager.
|
|
"""
|
|
class Meta:
|
|
proxy = True
|
|
ordering = ["name"]
|
|
|
|
class StatusPerson(MyPerson):
|
|
"""
|
|
A non-proxy subclass of a proxy, it should get a new table.
|
|
"""
|
|
status = models.CharField(max_length=80)
|
|
|
|
# We can even have proxies of proxies (and subclass of those).
|
|
class MyPersonProxy(MyPerson):
|
|
class Meta:
|
|
proxy = True
|
|
|
|
class LowerStatusPerson(MyPersonProxy):
|
|
status = models.CharField(max_length=80)
|
|
|
|
__test__ = {'API_TESTS' : """
|
|
# The MyPerson model should be generating the same database queries as the
|
|
# Person model (when the same manager is used in each case).
|
|
>>> MyPerson.other.all().query.as_sql() == Person.objects.order_by("name").query.as_sql()
|
|
True
|
|
|
|
# The StatusPerson models should have its own table (it's using ORM-level
|
|
# inheritance).
|
|
>>> StatusPerson.objects.all().query.as_sql() == Person.objects.all().query.as_sql()
|
|
False
|
|
|
|
# Creating a Person makes them accessible through the MyPerson proxy.
|
|
>>> _ = Person.objects.create(name="Foo McBar")
|
|
>>> len(Person.objects.all())
|
|
1
|
|
>>> len(MyPerson.objects.all())
|
|
1
|
|
>>> MyPerson.objects.get(name="Foo McBar").id
|
|
1
|
|
>>> MyPerson.objects.get(id=1).has_special_name()
|
|
False
|
|
|
|
# Person is not proxied by StatusPerson subclass, however.
|
|
>>> StatusPerson.objects.all()
|
|
[]
|
|
|
|
# A new MyPerson also shows up as a standard Person
|
|
>>> _ = MyPerson.objects.create(name="Bazza del Frob")
|
|
>>> len(MyPerson.objects.all())
|
|
2
|
|
>>> len(Person.objects.all())
|
|
2
|
|
|
|
>>> _ = LowerStatusPerson.objects.create(status="low", name="homer")
|
|
>>> LowerStatusPerson.objects.all()
|
|
[<LowerStatusPerson: homer>]
|
|
|
|
# And now for some things that shouldn't work...
|
|
#
|
|
# All base classes must be non-abstract
|
|
>>> class NoAbstract(Abstract):
|
|
... class Meta:
|
|
... proxy = True
|
|
Traceback (most recent call last):
|
|
....
|
|
TypeError: Abstract base class containing model fields not permitted for proxy model 'NoAbstract'.
|
|
|
|
# The proxy must actually have one concrete base class
|
|
>>> class TooManyBases(Person, Abstract):
|
|
... class Meta:
|
|
... proxy = True
|
|
Traceback (most recent call last):
|
|
....
|
|
TypeError: Abstract base class containing model fields not permitted for proxy model 'TooManyBases'.
|
|
|
|
>>> class NoBaseClasses(models.Model):
|
|
... class Meta:
|
|
... proxy = True
|
|
Traceback (most recent call last):
|
|
....
|
|
TypeError: Proxy model 'NoBaseClasses' has no non-abstract model base class.
|
|
|
|
|
|
# A proxy cannot introduce any new fields
|
|
>>> class NoNewFields(Person):
|
|
... newfield = models.BooleanField()
|
|
... class Meta:
|
|
... proxy = True
|
|
Traceback (most recent call last):
|
|
....
|
|
FieldError: Proxy model 'NoNewFields' contains model fields.
|
|
|
|
# Manager tests.
|
|
|
|
>>> Person.objects.all().delete()
|
|
>>> _ = Person.objects.create(name="fred")
|
|
>>> _ = Person.objects.create(name="wilma")
|
|
>>> _ = Person.objects.create(name="barney")
|
|
|
|
>>> MyPerson.objects.all()
|
|
[<MyPerson: barney>, <MyPerson: fred>]
|
|
>>> MyPerson._default_manager.all()
|
|
[<MyPerson: barney>, <MyPerson: fred>]
|
|
|
|
>>> OtherPerson.objects.all()
|
|
[<OtherPerson: barney>, <OtherPerson: wilma>]
|
|
>>> OtherPerson.excluder.all()
|
|
[<OtherPerson: barney>, <OtherPerson: fred>]
|
|
>>> OtherPerson._default_manager.all()
|
|
[<OtherPerson: barney>, <OtherPerson: wilma>]
|
|
|
|
# A proxy has the same content type as the model it is proxying for (at the
|
|
# storage level, it is meant to be essentially indistinguishable).
|
|
>>> ctype = ContentType.objects.get_for_model
|
|
>>> ctype(Person) is ctype(OtherPerson)
|
|
True
|
|
"""}
|
|
|
|
|