Documented related models descriptors.
Changed the poll / choices example to a more obvious parent / children. I think that reduces the cognitive load.
This commit is contained in:
parent
005c9fc45f
commit
497d915299
|
@ -1,3 +1,60 @@
|
||||||
|
"""
|
||||||
|
Accessors for related objects.
|
||||||
|
|
||||||
|
When a field defines a relation between two models, each model class provides
|
||||||
|
an attribute to access related instances of the other model class (unless the
|
||||||
|
reverse accessor has been disabled with related_name='+').
|
||||||
|
|
||||||
|
Accessors are implemented as descriptors in order to customize access and
|
||||||
|
assignment. This module defines the descriptor classes.
|
||||||
|
|
||||||
|
Forward accessors follow foreign keys. Reverse accessors trace them back. For
|
||||||
|
example, with the following models::
|
||||||
|
|
||||||
|
class Parent(Model):
|
||||||
|
pass
|
||||||
|
|
||||||
|
class Child(Model):
|
||||||
|
parent = ForeignKey(Parent, related_name='children')
|
||||||
|
|
||||||
|
``child.parent`` is a forward many-to-one relation. ``parent.children`` is a
|
||||||
|
reverse many-to-one relation.
|
||||||
|
|
||||||
|
There are three types of relations (many-to-one, one-to-one, and many-to-many)
|
||||||
|
and two directions (forward and reverse) for a total of six combinations.
|
||||||
|
However only four accessor classes are required.
|
||||||
|
|
||||||
|
1. Related instance on the forward side of a many-to-one or one-to-one
|
||||||
|
relation: ``ReverseSingleRelatedObjectDescriptor``.
|
||||||
|
|
||||||
|
Uniqueness of foreign key values is irrelevant to accessing the related
|
||||||
|
instance, making the many-to-one and one-to-one cases identical as far as
|
||||||
|
the descriptor is concerned. The constraint is checked upstream (unicity
|
||||||
|
validation in forms) or downstream (unique indexes in the database).
|
||||||
|
|
||||||
|
2. Related instance on the reverse side of a one-to-one relation:
|
||||||
|
``SingleRelatedObjectDescriptor``.
|
||||||
|
|
||||||
|
One-to-one relations are asymmetrical, despite the apparent symmetry of the
|
||||||
|
name, because they're implemented in the database with a foreign key from
|
||||||
|
one table to another. As a consequence ``SingleRelatedObjectDescriptor`` is
|
||||||
|
slightly different from ``ReverseSingleRelatedObjectDescriptor``.
|
||||||
|
|
||||||
|
3. Related objects manager for related instances on the reverse side of a
|
||||||
|
many-to-one relation: ``ForeignRelatedObjectsDescriptor``.
|
||||||
|
|
||||||
|
Unlike the previous two classes, this one provides access to a collection
|
||||||
|
of objects. It returns a manager rather than an instance.
|
||||||
|
|
||||||
|
4. Related objects manager for related instances on the forward or reverse
|
||||||
|
sides of a many-to-many relation: ``ManyRelatedObjectsDescriptor``.
|
||||||
|
|
||||||
|
Many-to-many relations are symmetrical. The syntax of Django models
|
||||||
|
requires declaring them on one side but that's an implementation detail.
|
||||||
|
They could be declared on the other side without any change in behavior.
|
||||||
|
Therefore the forward and reverse descriptors can be the same.
|
||||||
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
|
@ -15,10 +72,10 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
||||||
|
|
||||||
In the example::
|
In the example::
|
||||||
|
|
||||||
class Choice(Model):
|
class Child(Model):
|
||||||
poll = ForeignKey(Place, related_name='choices')
|
parent = ForeignKey(Parent, related_name='children')
|
||||||
|
|
||||||
`choice.poll` is a ReverseSingleRelatedObjectDescriptor instance.
|
``child.parent`` is a ``ReverseSingleRelatedObjectDescriptor`` instance.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, field_with_rel):
|
def __init__(self, field_with_rel):
|
||||||
|
@ -79,8 +136,18 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
||||||
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
|
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
|
"""
|
||||||
|
Get the related instance through the forward relation.
|
||||||
|
|
||||||
|
With the example above, when getting ``child.parent``:
|
||||||
|
|
||||||
|
- ``self`` is the descriptor managing the ``parent`` attribute
|
||||||
|
- ``instance`` is the ``child`` instance
|
||||||
|
- ``instance_type`` in the ``Child`` class (we don't need it)
|
||||||
|
"""
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rel_obj = getattr(instance, self.cache_name)
|
rel_obj = getattr(instance, self.cache_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -95,6 +162,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
||||||
if not self.field.remote_field.multiple:
|
if not self.field.remote_field.multiple:
|
||||||
setattr(rel_obj, self.field.remote_field.get_cache_name(), instance)
|
setattr(rel_obj, self.field.remote_field.get_cache_name(), instance)
|
||||||
setattr(instance, self.cache_name, rel_obj)
|
setattr(instance, self.cache_name, rel_obj)
|
||||||
|
|
||||||
if rel_obj is None and not self.field.null:
|
if rel_obj is None and not self.field.null:
|
||||||
raise self.RelatedObjectDoesNotExist(
|
raise self.RelatedObjectDoesNotExist(
|
||||||
"%s has no %s." % (self.field.model.__name__, self.field.name)
|
"%s has no %s." % (self.field.model.__name__, self.field.name)
|
||||||
|
@ -103,6 +171,15 @@ class ReverseSingleRelatedObjectDescriptor(object):
|
||||||
return rel_obj
|
return rel_obj
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
"""
|
||||||
|
Set the related instance through the forward relation.
|
||||||
|
|
||||||
|
With the example above, when setting ``child.parent = parent``:
|
||||||
|
|
||||||
|
- ``self`` is the descriptor managing the ``parent`` attribute
|
||||||
|
- ``instance`` is the ``child`` instance
|
||||||
|
- ``value`` in the ``parent`` instance on the right of the equal sign
|
||||||
|
"""
|
||||||
# If null=True, we can assign null here, but otherwise the value needs
|
# If null=True, we can assign null here, but otherwise the value needs
|
||||||
# to be an instance of the related class.
|
# to be an instance of the related class.
|
||||||
if value is None and self.field.null is False:
|
if value is None and self.field.null is False:
|
||||||
|
@ -221,8 +298,20 @@ class SingleRelatedObjectDescriptor(object):
|
||||||
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
|
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
|
"""
|
||||||
|
Get the related instance through the reverse relation.
|
||||||
|
|
||||||
|
With the example above, when getting ``place.restaurant``:
|
||||||
|
|
||||||
|
- ``self`` is the descriptor managing the ``restaurant`` attribute
|
||||||
|
- ``instance`` is the ``place`` instance
|
||||||
|
- ``instance_type`` in the ``Place`` class (we don't need it)
|
||||||
|
|
||||||
|
Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
|
||||||
|
"""
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
try:
|
try:
|
||||||
rel_obj = getattr(instance, self.cache_name)
|
rel_obj = getattr(instance, self.cache_name)
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
@ -238,6 +327,7 @@ class SingleRelatedObjectDescriptor(object):
|
||||||
else:
|
else:
|
||||||
setattr(rel_obj, self.related.field.get_cache_name(), instance)
|
setattr(rel_obj, self.related.field.get_cache_name(), instance)
|
||||||
setattr(instance, self.cache_name, rel_obj)
|
setattr(instance, self.cache_name, rel_obj)
|
||||||
|
|
||||||
if rel_obj is None:
|
if rel_obj is None:
|
||||||
raise self.RelatedObjectDoesNotExist(
|
raise self.RelatedObjectDoesNotExist(
|
||||||
"%s has no %s." % (
|
"%s has no %s." % (
|
||||||
|
@ -249,6 +339,17 @@ class SingleRelatedObjectDescriptor(object):
|
||||||
return rel_obj
|
return rel_obj
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
"""
|
||||||
|
Set the related instance through the reverse relation.
|
||||||
|
|
||||||
|
With the example above, when setting ``place.restaurant = restaurant``:
|
||||||
|
|
||||||
|
- ``self`` is the descriptor managing the ``restaurant`` attribute
|
||||||
|
- ``instance`` is the ``place`` instance
|
||||||
|
- ``value`` in the ``restaurant`` instance on the right of the equal sign
|
||||||
|
|
||||||
|
Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
|
||||||
|
"""
|
||||||
# The similarity of the code below to the code in
|
# The similarity of the code below to the code in
|
||||||
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
|
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
|
||||||
# of small differences that would make a common base class convoluted.
|
# of small differences that would make a common base class convoluted.
|
||||||
|
@ -299,10 +400,13 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
|
|
||||||
In the example::
|
In the example::
|
||||||
|
|
||||||
class Choice(Model):
|
class Child(Model):
|
||||||
poll = ForeignKey(Place, related_name='choices')
|
parent = ForeignKey(Parent, related_name='children')
|
||||||
|
|
||||||
``poll.choices`` is a ``ForeignRelatedObjectsDescriptor`` instance.
|
``parent.children`` is a ``ForeignRelatedObjectsDescriptor`` instance.
|
||||||
|
|
||||||
|
Most of the implementation is delegated to a dynamically defined manager
|
||||||
|
class built by ``create_many_related_manager()`` which is defined below.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, rel):
|
def __init__(self, rel):
|
||||||
|
@ -317,21 +421,40 @@ class ForeignRelatedObjectsDescriptor(object):
|
||||||
)
|
)
|
||||||
|
|
||||||
def __get__(self, instance, instance_type=None):
|
def __get__(self, instance, instance_type=None):
|
||||||
|
"""
|
||||||
|
Get the related objects through the reverse relation.
|
||||||
|
|
||||||
|
With the example above, when getting ``parent.children``:
|
||||||
|
|
||||||
|
- ``self`` is the descriptor managing the ``children`` attribute
|
||||||
|
- ``instance`` is the ``parent`` instance
|
||||||
|
- ``instance_type`` in the ``Parent`` class (we don't need it)
|
||||||
|
"""
|
||||||
if instance is None:
|
if instance is None:
|
||||||
return self
|
return self
|
||||||
|
|
||||||
return self.related_manager_cls(instance)
|
return self.related_manager_cls(instance)
|
||||||
|
|
||||||
def __set__(self, instance, value):
|
def __set__(self, instance, value):
|
||||||
|
"""
|
||||||
|
Set the related objects through the reverse relation.
|
||||||
|
|
||||||
|
With the example above, when setting ``parent.children = children``:
|
||||||
|
|
||||||
|
- ``self`` is the descriptor managing the ``children`` attribute
|
||||||
|
- ``instance`` is the ``parent`` instance
|
||||||
|
- ``value`` in the ``children`` sequence on the right of the equal sign
|
||||||
|
"""
|
||||||
manager = self.__get__(instance)
|
manager = self.__get__(instance)
|
||||||
manager.set(value)
|
manager.set(value)
|
||||||
|
|
||||||
|
|
||||||
def create_foreign_related_manager(superclass, rel):
|
def create_foreign_related_manager(superclass, rel):
|
||||||
"""
|
"""
|
||||||
Factory function to create a manager that subclasses another manager
|
Create a manager for the reverse side of a many-to-one relation.
|
||||||
(generally the default manager of a given model) and adds behaviors
|
|
||||||
specific to many-to-one relations.
|
This manager subclasses another manager, generally the default manager of
|
||||||
|
the related model, and adds behaviors specific to many-to-one relations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class RelatedManager(superclass):
|
class RelatedManager(superclass):
|
||||||
|
@ -520,8 +643,11 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
|
||||||
class Pizza(Model):
|
class Pizza(Model):
|
||||||
toppings = ManyToManyField(Topping, related_name='pizzas')
|
toppings = ManyToManyField(Topping, related_name='pizzas')
|
||||||
|
|
||||||
``pizza.toppings`` and ``topping.pizzas`` are ManyRelatedObjectsDescriptor
|
``pizza.toppings`` and ``topping.pizzas`` are
|
||||||
instances.
|
``ManyRelatedObjectsDescriptor`` instances.
|
||||||
|
|
||||||
|
Most of the implementation is delegated to a dynamically defined manager
|
||||||
|
class built by ``create_many_related_manager()`` which is defined below.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, rel, reverse=False):
|
def __init__(self, rel, reverse=False):
|
||||||
|
@ -548,9 +674,10 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
|
||||||
|
|
||||||
def create_many_related_manager(superclass, rel, reverse):
|
def create_many_related_manager(superclass, rel, reverse):
|
||||||
"""
|
"""
|
||||||
Factory function to create a manager that subclasses another manager
|
Create a manager for the either side of a many-to-many relation.
|
||||||
(generally the default manager of a given model) and adds behaviors
|
|
||||||
specific to many-to-many relations.
|
This manager subclasses another manager, generally the default manager of
|
||||||
|
the related model, and adds behaviors specific to many-to-many relations.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
class ManyRelatedManager(superclass):
|
class ManyRelatedManager(superclass):
|
||||||
|
|
Loading…
Reference in New Issue