diff --git a/AUTHORS b/AUTHORS index 1ad3b1ab33..3e5fc66665 100644 --- a/AUTHORS +++ b/AUTHORS @@ -220,6 +220,7 @@ answer newbie questions, and generally made Django that much better: Erik Karulf Ben Dean Kawamura Ian G. Kelly + Ryan Kelly Thomas Kerpe Ossama M. Khayat Ben Khoo diff --git a/django/db/models/base.py b/django/db/models/base.py index dec0316947..289294e97a 100644 --- a/django/db/models/base.py +++ b/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)) + self.save_base(cls=parent) + if field: + setattr(self, field.attname, self._get_pk_val(parent._meta)) + if meta.proxy: + return - non_pks = [f for f in meta.local_fields if not f.primary_key] + 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: + # 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 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 + 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, diff --git a/django/db/models/manager.py b/django/db/models/manager.py index 6869ad2eb5..c130a0c74a 100644 --- a/django/db/models/manager.py +++ b/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): """ diff --git a/django/db/models/options.py b/django/db/models/options.py index 2dd3d256a1..85cc511a4c 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', '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 '' % self.object_name diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index 775dfae772..cfb6501be5 100644 --- a/django/db/models/sql/query.py +++ b/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 diff --git a/docs/ref/models/options.txt b/docs/ref/models/options.txt index 9525e58df9..60a3064dff 100644 --- a/docs/ref/models/options.txt +++ b/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 `. + ``unique_together`` ------------------- diff --git a/docs/topics/db/managers.txt b/docs/topics/db/managers.txt index 2ace9c1408..371ded99cf 100644 --- a/docs/topics/db/managers.txt +++ b/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 ------------------------------------- diff --git a/docs/topics/db/models.txt b/docs/topics/db/models.txt index 12b54babf7..be500680ec 100644 --- a/docs/topics/db/models.txt +++ b/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 ` class. All the :ref:`Meta ` 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 ` 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 +` 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 ` 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 `, 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") + + +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 ` 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 -------------------- diff --git a/tests/modeltests/proxy_models/__init__.py b/tests/modeltests/proxy_models/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/modeltests/proxy_models/models.py b/tests/modeltests/proxy_models/models.py new file mode 100644 index 0000000000..275b5207cc --- /dev/null +++ b/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() +[] + +# 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() +[, ] +"""} + +