""" 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) class User(models.Model): name = models.CharField(max_length=100) def __unicode__(self): return self.name class UserProxy(User): class Meta: proxy = True class UserProxyProxy(UserProxy): class Meta: proxy = True # We can still use `select_related()` to include related models in our querysets. class Country(models.Model): name = models.CharField(max_length=50) class State(models.Model): name = models.CharField(max_length=50) country = models.ForeignKey(Country) def __unicode__(self): return self.name class StateProxy(State): class Meta: proxy = True # Proxy models still works with filters (on related fields) # and select_related, even when mixed with model inheritance class BaseUser(models.Model): name = models.CharField(max_length=255) class TrackerUser(BaseUser): status = models.CharField(max_length=50) class ProxyTrackerUser(TrackerUser): class Meta: proxy = True class Issue(models.Model): summary = models.CharField(max_length=255) assignee = models.ForeignKey(TrackerUser) def __unicode__(self): return ':'.join((self.__class__.__name__,self.summary,)) class Bug(Issue): version = models.CharField(max_length=50) reporter = models.ForeignKey(BaseUser) class ProxyBug(Bug): """ Proxy of an inherited class """ class Meta: proxy = True class ProxyProxyBug(ProxyBug): """ A proxy of proxy model with related field """ class Meta: proxy = True class Improvement(Issue): """ A model that has relation to a proxy model or to a proxy of proxy model """ version = models.CharField(max_length=50) reporter = models.ForeignKey(ProxyTrackerUser) associated_bug = models.ForeignKey(ProxyProxyBug) class ProxyImprovement(Improvement): class Meta: proxy = True __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() [] # Correct type when querying a proxy of proxy >>> MyPersonProxy.objects.all() [, , ] # 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._default_manager.all() [, ] >>> OtherPerson.objects.all() [, ] >>> OtherPerson.excluder.all() [, ] >>> OtherPerson._default_manager.all() [, ] # 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 >>> MyPersonProxy.objects.all() [, ] >>> u = User.objects.create(name='Bruce') >>> User.objects.all() [] >>> UserProxy.objects.all() [] >>> UserProxyProxy.objects.all() [] # We can still use `select_related()` to include related models in our querysets. >>> country = Country.objects.create(name='Australia') >>> state = State.objects.create(name='New South Wales', country=country) >>> State.objects.select_related() [] >>> StateProxy.objects.select_related() [] >>> StateProxy.objects.get(name='New South Wales') >>> StateProxy.objects.select_related().get(name='New South Wales') >>> contributor = TrackerUser.objects.create(name='Contributor',status='contrib') >>> someone = BaseUser.objects.create(name='Someone') >>> _ = Bug.objects.create(summary='fix this', version='1.1beta', ... assignee=contributor, reporter=someone) >>> pcontributor = ProxyTrackerUser.objects.create(name='OtherContributor', ... status='proxy') >>> _ = Improvement.objects.create(summary='improve that', version='1.1beta', ... assignee=contributor, reporter=pcontributor, ... associated_bug=ProxyProxyBug.objects.all()[0]) # Related field filter on proxy >>> ProxyBug.objects.get(version__icontains='beta') # Select related + filter on proxy >>> ProxyBug.objects.select_related().get(version__icontains='beta') # Proxy of proxy, select_related + filter >>> ProxyProxyBug.objects.select_related().get(version__icontains='beta') # Select related + filter on a related proxy field >>> ProxyImprovement.objects.select_related().get(reporter__name__icontains='butor') # Select related + filter on a related proxy of proxy field >>> ProxyImprovement.objects.select_related().get(associated_bug__summary__icontains='fix') """}