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
This commit is contained in:
parent
c0b6e23eb3
commit
61a2708c41
1
AUTHORS
1
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>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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):
|
||||
"""
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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``
|
||||
-------------------
|
||||
|
||||
|
|
|
@ -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
|
||||
-------------------------------------
|
||||
|
||||
|
|
|
@ -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 +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>]
|
||||
"""}
|
||||
|
||||
|
Loading…
Reference in New Issue