Browse Source

Fixed #10356 -- Added pure-Python inheritance for models (a.k.a proxy models).

Large portions of this are needed for #5420, so I implemented it fully.
Thanks to Ryan Kelly for an initial patch to get this started.

Refs #5420.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10083 bcc190cf-cafb-0310-a4f2-bffc1f526a37
Malcolm Tredinnick 16 years ago
parent
commit
61a2708c41

+ 1 - 0
AUTHORS

@@ -220,6 +220,7 @@ answer newbie questions, and generally made Django that much better:
     Erik Karulf <erik@karulf.com>
     Ben Dean Kawamura <ben.dean.kawamura@gmail.com>
     Ian G. Kelly <ian.g.kelly@gmail.com>
+    Ryan Kelly <ryan@rfk.id.au>
     Thomas Kerpe <thomas@kerpe.net>
     Ossama M. Khayat <okhayat@yahoo.com>
     Ben Khoo <khoobks@westnet.com.au>

+ 109 - 61
django/db/models/base.py

@@ -67,9 +67,19 @@ class ModelBase(type):
                 if not hasattr(meta, 'get_latest_by'):
                     new_class._meta.get_latest_by = base_meta.get_latest_by
 
+        is_proxy = new_class._meta.proxy
+
         if getattr(new_class, '_default_manager', None):
-            new_class._default_manager = None
-            new_class._base_manager = None
+            if not is_proxy:
+                # Multi-table inheritance doesn't inherit default manager from
+                # parents.
+                new_class._default_manager = None
+                new_class._base_manager = None
+            else:
+                # Proxy classes do inherit parent's default manager, if none is
+                # set explicitly.
+                new_class._default_manager = new_class._default_manager._copy_to_model(new_class)
+                new_class._base_manager = new_class._base_manager._copy_to_model(new_class)
 
         # Bail out early if we have already created this class.
         m = get_model(new_class._meta.app_label, name, False)
@@ -80,21 +90,43 @@ class ModelBase(type):
         for obj_name, obj in attrs.items():
             new_class.add_to_class(obj_name, obj)
 
+        # All the fields of any type declared on this model
+        new_fields = new_class._meta.local_fields + \
+                     new_class._meta.local_many_to_many + \
+                     new_class._meta.virtual_fields
+        field_names = set([f.name for f in new_fields])
+
+        # Basic setup for proxy models.
+        if is_proxy:
+            base = None
+            for parent in [cls for cls in parents if hasattr(cls, '_meta')]:
+                if parent._meta.abstract:
+                    if parent._meta.fields:
+                        raise TypeError("Abstract base class containing model fields not permitted for proxy model '%s'." % name)
+                    else:
+                        continue
+                if base is not None:
+                    raise TypeError("Proxy model '%s' has more than one non-abstract model base class." % name)
+                else:
+                    base = parent
+            if base is None:
+                    raise TypeError("Proxy model '%s' has no non-abstract model base class." % name)
+            if (new_class._meta.local_fields or
+                    new_class._meta.local_many_to_many):
+                raise FieldError("Proxy model '%s' contains model fields."
+                        % name)
+            new_class._meta.setup_proxy(base)
+
         # Do the appropriate setup for any model parents.
         o2o_map = dict([(f.rel.to, f) for f in new_class._meta.local_fields
                 if isinstance(f, OneToOneField)])
+
         for base in parents:
             if not hasattr(base, '_meta'):
                 # Things without _meta aren't functional models, so they're
                 # uninteresting parents.
                 continue
 
-            # All the fields of any type declared on this model
-            new_fields = new_class._meta.local_fields + \
-                         new_class._meta.local_many_to_many + \
-                         new_class._meta.virtual_fields
-            field_names = set([f.name for f in new_fields])
-
             parent_fields = base._meta.local_fields + base._meta.local_many_to_many
             # Check for clashes between locally declared fields and those
             # on the base classes (we cannot handle shadowed fields at the
@@ -107,15 +139,19 @@ class ModelBase(type):
                                         (field.name, name, base.__name__))
             if not base._meta.abstract:
                 # Concrete classes...
+                while base._meta.proxy:
+                    # Skip over a proxy class to the "real" base it proxies.
+                    base = base._meta.proxy_for_model
                 if base in o2o_map:
                     field = o2o_map[base]
-                else:
+                elif not is_proxy:
                     attr_name = '%s_ptr' % base._meta.module_name
                     field = OneToOneField(base, name=attr_name,
                             auto_created=True, parent_link=True)
                     new_class.add_to_class(attr_name, field)
+                else:
+                    field = None
                 new_class._meta.parents[base] = field
-
             else:
                 # .. and abstract ones.
                 for field in parent_fields:
@@ -125,13 +161,12 @@ class ModelBase(type):
                 new_class._meta.parents.update(base._meta.parents)
 
             # Inherit managers from the abstract base classes.
-            base_managers = base._meta.abstract_managers
-            base_managers.sort()
-            for _, mgr_name, manager in base_managers:
-                val = getattr(new_class, mgr_name, None)
-                if not val or val is manager:
-                    new_manager = manager._copy_to_model(new_class)
-                    new_class.add_to_class(mgr_name, new_manager)
+            new_class.copy_managers(base._meta.abstract_managers)
+
+            # Proxy models inherit the non-abstract managers from their base,
+            # unless they have redefined any of them.
+            if is_proxy:
+                new_class.copy_managers(base._meta.concrete_managers)
 
             # Inherit virtual fields (like GenericForeignKey) from the parent
             # class
@@ -160,6 +195,15 @@ class ModelBase(type):
         # registered version.
         return get_model(new_class._meta.app_label, name, False)
 
+    def copy_managers(cls, base_managers):
+        # This is in-place sorting of an Options attribute, but that's fine.
+        base_managers.sort()
+        for _, mgr_name, manager in base_managers:
+            val = getattr(cls, mgr_name, None)
+            if not val or val is manager:
+                new_manager = manager._copy_to_model(cls)
+                cls.add_to_class(mgr_name, new_manager)
+
     def add_to_class(cls, name, value):
         if hasattr(value, 'contribute_to_class'):
             value.contribute_to_class(cls, name)
@@ -358,55 +402,59 @@ class Model(object):
                 # At this point, parent's primary key field may be unknown
                 # (for example, from administration form which doesn't fill
                 # this field). If so, fill it.
-                if getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
+                if field and getattr(self, parent._meta.pk.attname) is None and getattr(self, field.attname) is not None:
                     setattr(self, parent._meta.pk.attname, getattr(self, field.attname))
 
-                self.save_base(raw, parent)
-                setattr(self, field.attname, self._get_pk_val(parent._meta))
-
-        non_pks = [f for f in meta.local_fields if not f.primary_key]
-
-        # First, try an UPDATE. If that doesn't update anything, do an INSERT.
-        pk_val = self._get_pk_val(meta)
-        pk_set = pk_val is not None
-        record_exists = True
-        manager = cls._base_manager
-        if pk_set:
-            # Determine whether a record with the primary key already exists.
-            if (force_update or (not force_insert and
-                    manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
-                # It does already exist, so do an UPDATE.
-                if force_update or non_pks:
-                    values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
-                    rows = manager.filter(pk=pk_val)._update(values)
-                    if force_update and not rows:
-                        raise DatabaseError("Forced update did not affect any rows.")
-            else:
-                record_exists = False
-        if not pk_set or not record_exists:
-            if not pk_set:
-                if force_update:
-                    raise ValueError("Cannot force an update in save() with no primary key.")
-                values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)]
-            else:
-                values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
+                self.save_base(cls=parent)
+                if field:
+                    setattr(self, field.attname, self._get_pk_val(parent._meta))
+            if meta.proxy:
+                return
+
+        if not meta.proxy:
+            non_pks = [f for f in meta.local_fields if not f.primary_key]
+
+            # First, try an UPDATE. If that doesn't update anything, do an INSERT.
+            pk_val = self._get_pk_val(meta)
+            pk_set = pk_val is not None
+            record_exists = True
+            manager = cls._base_manager
+            if pk_set:
+                # Determine whether a record with the primary key already exists.
+                if (force_update or (not force_insert and
+                        manager.filter(pk=pk_val).extra(select={'a': 1}).values('a').order_by())):
+                    # It does already exist, so do an UPDATE.
+                    if force_update or non_pks:
+                        values = [(f, None, (raw and getattr(self, f.attname) or f.pre_save(self, False))) for f in non_pks]
+                        rows = manager.filter(pk=pk_val)._update(values)
+                        if force_update and not rows:
+                            raise DatabaseError("Forced update did not affect any rows.")
+                else:
+                    record_exists = False
+            if not pk_set or not record_exists:
+                if not pk_set:
+                    if force_update:
+                        raise ValueError("Cannot force an update in save() with no primary key.")
+                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields if not isinstance(f, AutoField)]
+                else:
+                    values = [(f, f.get_db_prep_save(raw and getattr(self, f.attname) or f.pre_save(self, True))) for f in meta.local_fields]
 
-            if meta.order_with_respect_to:
-                field = meta.order_with_respect_to
-                values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count()))
-            record_exists = False
+                if meta.order_with_respect_to:
+                    field = meta.order_with_respect_to
+                    values.append((meta.get_field_by_name('_order')[0], manager.filter(**{field.name: getattr(self, field.attname)}).count()))
+                record_exists = False
 
-            update_pk = bool(meta.has_auto_field and not pk_set)
-            if values:
-                # Create a new record.
-                result = manager._insert(values, return_id=update_pk)
-            else:
-                # Create a new record with defaults for everything.
-                result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
+                update_pk = bool(meta.has_auto_field and not pk_set)
+                if values:
+                    # Create a new record.
+                    result = manager._insert(values, return_id=update_pk)
+                else:
+                    # Create a new record with defaults for everything.
+                    result = manager._insert([(meta.pk, connection.ops.pk_default_value())], return_id=update_pk, raw_values=True)
 
-            if update_pk:
-                setattr(self, meta.pk.attname, result)
-        transaction.commit_unless_managed()
+                if update_pk:
+                    setattr(self, meta.pk.attname, result)
+            transaction.commit_unless_managed()
 
         if signal:
             signals.post_save.send(sender=self.__class__, instance=self,

+ 3 - 0
django/db/models/manager.py

@@ -60,6 +60,9 @@ class Manager(object):
         if model._meta.abstract or self._inherited:
             model._meta.abstract_managers.append((self.creation_counter, name,
                     self))
+        else:
+            model._meta.concrete_managers.append((self.creation_counter, name,
+                self))
 
     def _set_creation_counter(self):
         """

+ 16 - 3
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', 'managed')
+                 'abstract', 'managed', 'proxy')
 
 class Options(object):
     def __init__(self, meta, app_label=None):
@@ -43,11 +43,15 @@ class Options(object):
         self.has_auto_field, self.auto_field = False, None
         self.abstract = False
         self.managed = True
+        self.proxy = False
+        self.proxy_for_model = None
         self.parents = SortedDict()
         self.duplicate_targets = {}
-        # Managers that have been inherited from abstract base classes. These
-        # are passed onto any children.
+
+        # To handle various inheritance situations, we need to track where
+        # managers came from (concrete or abstract base classes).
         self.abstract_managers = []
+        self.concrete_managers = []
 
     def contribute_to_class(self, cls, name):
         from django.db import connection
@@ -164,6 +168,15 @@ class Options(object):
             self.pk = field
             field.serialize = False
 
+    def setup_proxy(self, target):
+        """
+        Does the internal setup so that the current model is a proxy for
+        "target".
+        """
+        self.pk = target._meta.pk
+        self.proxy_for_model = target
+        self.db_table = target._meta.db_table
+
     def __repr__(self):
         return '<Options for %s>' % self.object_name
 

+ 32 - 19
django/db/models/sql/query.py

@@ -641,6 +641,7 @@ class BaseQuery(object):
         qn = self.quote_name_unless_alias
         qn2 = self.connection.ops.quote_name
         aliases = set()
+        proxied_model = opts.proxy and opts.proxy_for_model or 0
         if start_alias:
             seen = {None: start_alias}
         for field, model in opts.get_fields_with_model():
@@ -648,9 +649,12 @@ class BaseQuery(object):
                 try:
                     alias = seen[model]
                 except KeyError:
-                    link_field = opts.get_ancestor_link(model)
-                    alias = self.join((start_alias, model._meta.db_table,
-                            link_field.column, model._meta.pk.column))
+                    if model is proxied_model:
+                        alias = start_alias
+                    else:
+                        link_field = opts.get_ancestor_link(model)
+                        alias = self.join((start_alias, model._meta.db_table,
+                                link_field.column, model._meta.pk.column))
                     seen[model] = alias
             else:
                 # If we're starting from the base model of the queryset, the
@@ -1158,11 +1162,15 @@ class BaseQuery(object):
         opts = self.model._meta
         root_alias = self.tables[0]
         seen = {None: root_alias}
+        proxied_model = opts.proxy and opts.proxy_for_model or 0
         for field, model in opts.get_fields_with_model():
             if model not in seen:
-                link_field = opts.get_ancestor_link(model)
-                seen[model] = self.join((root_alias, model._meta.db_table,
-                        link_field.column, model._meta.pk.column))
+                if model is proxied_model:
+                    seen[model] = root_alias
+                else:
+                    link_field = opts.get_ancestor_link(model)
+                    seen[model] = self.join((root_alias, model._meta.db_table,
+                            link_field.column, model._meta.pk.column))
         self.included_inherited_models = seen
 
     def remove_inherited_models(self):
@@ -1559,20 +1567,25 @@ class BaseQuery(object):
                 raise MultiJoin(pos + 1)
             if model:
                 # The field lives on a base class of the current model.
+                proxied_model = opts.proxy and opts.proxy_for_model or 0
                 for int_model in opts.get_base_chain(model):
-                    lhs_col = opts.parents[int_model].column
-                    dedupe = lhs_col in opts.duplicate_targets
-                    if dedupe:
-                        exclusions.update(self.dupe_avoidance.get(
-                                (id(opts), lhs_col), ()))
-                        dupe_set.add((opts, lhs_col))
-                    opts = int_model._meta
-                    alias = self.join((alias, opts.db_table, lhs_col,
-                            opts.pk.column), exclusions=exclusions)
-                    joins.append(alias)
-                    exclusions.add(alias)
-                    for (dupe_opts, dupe_col) in dupe_set:
-                        self.update_dupe_avoidance(dupe_opts, dupe_col, alias)
+                    if int_model is proxied_model:
+                        opts = int_model._meta
+                    else:
+                        lhs_col = opts.parents[int_model].column
+                        dedupe = lhs_col in opts.duplicate_targets
+                        if dedupe:
+                            exclusions.update(self.dupe_avoidance.get(
+                                    (id(opts), lhs_col), ()))
+                            dupe_set.add((opts, lhs_col))
+                        opts = int_model._meta
+                        alias = self.join((alias, opts.db_table, lhs_col,
+                                opts.pk.column), exclusions=exclusions)
+                        joins.append(alias)
+                        exclusions.add(alias)
+                        for (dupe_opts, dupe_col) in dupe_set:
+                            self.update_dupe_avoidance(dupe_opts, dupe_col,
+                                    alias)
             cached_data = opts._join_cache.get(name)
             orig_opts = opts
             dupe_col = direct and field.column or field.field.column

+ 10 - 0
docs/ref/models/options.txt

@@ -162,6 +162,16 @@ that has ``admin`` set. This example specifies an extra permission,
 This is a list or tuple of 2-tuples in the format ``(permission_code,
 human_readable_permission_name)``.
 
+``proxy``
+---------
+
+.. attribute:: Options.proxy
+
+.. versionadded: 1.1
+
+If set to ``True``, a model which subclasses another model will be treated as
+a :ref:`proxy model <proxy-models>`.
+
 ``unique_together``
 -------------------
 

+ 2 - 0
docs/topics/db/managers.txt

@@ -195,6 +195,8 @@ attribute on the manager class. This is documented fully below_.
 
 .. _below: manager-types_
 
+.. _custom-managers-and-inheritance:
+
 Custom managers and model inheritance
 -------------------------------------
 

+ 139 - 12
docs/topics/db/models.txt

@@ -773,13 +773,18 @@ is whether you want the parent models to be models in their own right
 of common information that will only be visible through the child
 models.
 
-Often, you will just want to use the parent class to hold information
-that you don't want to have to type out for each child model. This
-class isn't going to ever be used in isolation, so
-:ref:`abstract-base-classes` are what you're after. However, if you're
-subclassing an existing model (perhaps something from another
-application entirely), or want each model to have its own database
-table, :ref:`multi-table-inheritance` is the way to go.
+There are three styles of inheritance that are possible in Django.
+
+ 1. Often, you will just want to use the parent class to hold information that
+    you don't want to have to type out for each child model. This class isn't
+    going to ever be used in isolation, so :ref:`abstract-base-classes` are
+    what you're after.
+ 2. If you're subclassing an existing model (perhaps something from another
+    application entirely) and want each model to have its own database table,
+    :ref:`multi-table-inheritance` is the way to go.
+ 3. Finally, if you only want to modify the Python-level behaviour of a model,
+    without changing the models fields in any way, you can use
+    :ref:`proxy-models`.
 
 .. _abstract-base-classes:
 
@@ -937,14 +942,16 @@ referring to ``p.restaurant`` would raise a Restaurant.DoesNotExist exception.
 In the multi-table inheritance situation, it doesn't make sense for a child
 class to inherit from its parent's :ref:`Meta <meta-options>` class. All the :ref:`Meta <meta-options>` options
 have already been applied to the parent class and applying them again would
-normally only lead to contradictory behaviour (this is in contrast with the
+normally only lead to contradictory behavior (this is in contrast with the
 abstract base class case, where the base class doesn't exist in its own
 right).
 
-So a child model does not have access to its parent's :ref:`Meta <meta-options>` class. However,
-there are a few limited cases where the child inherits behaviour from the
-parent: if the child does not specify an :attr:`django.db.models.Options.ordering` attribute or a
-:attr:`django.db.models.Options.get_latest_by` attribute, it will inherit these from its parent.
+So a child model does not have access to its parent's :ref:`Meta
+<meta-options>` class. However, there are a few limited cases where the child
+inherits behavior from the parent: if the child does not specify an
+:attr:`django.db.models.Options.ordering` attribute or a
+:attr:`django.db.models.Options.get_latest_by` attribute, it will inherit
+these from its parent.
 
 If the parent has an ordering and you don't want the child to have any natural
 ordering, you can explicitly disable it::
@@ -990,6 +997,126 @@ own :class:`~django.db.models.fields.OneToOneField` and set
 :attr:`parent_link=True <django.db.models.fields.OneToOneField.parent_link>`
 to indicate that your field is the link back to the parent class.
 
+.. _proxy-models:
+
+Proxy models
+------------
+
+.. versionadded:: 1.1
+
+When using :ref:`multi-table inheritance <multi-table-inheritance>`, a new
+database table is created for each subclass of a model. This is usually the
+desired behavior, since the subclass needs a place to store any additional
+data fields that are not present on the base class. Sometimes, however, you
+only want to change the Python behavior of a model -- perhaps to change the
+default manager, or add a new method.
+
+This is what proxy model inheritance is for: creating a *proxy* for the
+original model. You can create, delete and update instances of the proxy model
+and all the data will be saved as if you were using the original (non-proxied)
+model. The difference is that you can change things like the default model
+ordering or the default manager in the proxy, without having to alter the
+original.
+
+Proxy models are declared like normal models. You tell Django that it's a
+proxy model by setting the :attr:`~django.db.models.Options.proxy` attribute to of the ``Meta`` class to ``True``.
+
+For example, suppose you want to add a method to the standard ``User`` model
+that will make be used in your templates. You can do it like this::
+
+    from django.contrib.auth.models import User
+
+    class MyUser(User):
+        class Meta:
+            proxy = True
+
+        def do_something(self):
+            ...
+
+The ``MyUser`` class operates on the same database table as its parent
+``User`` class. In particular, any new instances of ``User`` will also be
+accessible through ``MyUser``, and vice-versa::
+
+    >>> u = User.objects.create(username="foobar")
+    >>> MyUser.objects.get(username="foobar")
+    <MyUser: foobar>
+
+You could also use a proxy model to define a different default ordering on a
+model. The standard ``User`` model has no ordering defined on it
+(intentionally; sorting is expensive and we don't want to do it all the time
+when we fetch users). You might want to regularly order by the ``username``
+attribute when you use the proxy. This is easy::
+
+    class OrderedUser(User):
+        class Meta:
+            ordering = ["username"]
+            proxy = True
+
+Now normal ``User`` queries will be unorderd and ``OrderedUser`` queries will
+be ordered by ``username``.
+
+Querysets still return the model that was requested
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+There is no way to have Django return, say, a ``MyUser`` object whenever you
+query for ``User`` objects. A queryset for ``User`` objects will return those
+types of objects. The whole point of proxy objects is that code relying on the
+original ``User`` will use those and your own code can use the extensions you
+included (that no other code is relying on anyway). It is not a way to replace
+the ``User`` (or any other) model everywhere with something of your own
+creation.
+
+Base class restrictions
+~~~~~~~~~~~~~~~~~~~~~~~
+
+A proxy model must inherit from exactly one non-abstract model class. You
+can't inherit from multiple non-abstract models as the proxy model doesn't
+provide any connection between the rows in the different database tables. A
+proxy model can inherit from any number of abstract model classes, providing
+they do *not* define any model fields.
+
+Proxy models inherit any ``Meta`` options that they don't define from their
+non-abstract model parent (the model they are proxying for).
+
+Proxy model managers
+~~~~~~~~~~~~~~~~~~~~
+
+If you don't specify any model managers on a proxy model, it inherits the
+managers from its model parents. If you define a manager on the proxy model,
+it will become the default, although any managers defined on the parent
+classes will still be available.
+
+Continuing our example from above, you could change the default manager used
+when you query the ``User`` model like this::
+
+    class NewManager(models.Manager):
+        ...
+
+    class MyUser(User):
+        objects = NewManager()
+
+        class Meta:
+            proxy = True
+
+If you wanted to add a new manager to the Proxy, without replacing the
+existing default, you can use the techniques described in the :ref:`custom
+manager <custom-managers-and-inheritance>` documentation: create a base class
+containing the new managers and inherit that after the primary base class::
+
+    # Create an abstract class for the new manager.
+    class ExtraManagers:
+        secondary = NewManager()
+
+        class Meta:
+            abstract = True
+
+    class MyUser(User, ExtraManagers):
+        class Meta:
+            proxy = True
+
+You probably won't need to do this very often, but, when you do, it's
+possible.
+
 Multiple inheritance
 --------------------
 

+ 0 - 0
tests/modeltests/proxy_models/__init__.py


+ 176 - 0
tests/modeltests/proxy_models/models.py

@@ -0,0 +1,176 @@
+"""
+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.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>]
+"""}
+
+