summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAymeric Augustin <aymeric.augustin@m4x.org>2015-09-19 15:41:12 +0200
committerAymeric Augustin <aymeric.augustin@m4x.org>2015-09-21 22:06:03 +0200
commit497d9152997252f0afe172e3421cbfe4fbe55780 (patch)
tree74d261a9e1f31c127e619a758b13aec77e98b759
parent005c9fc45f99d03344fa9fae3dd984c2ab87f1ea (diff)
Documented related models descriptors.
Changed the poll / choices example to a more obvious parent / children. I think that reduces the cognitive load.
-rw-r--r--django/db/models/fields/related_descriptors.py155
1 files changed, 141 insertions, 14 deletions
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index cdbd159a44..91f17ef53e 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -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 operator import attrgetter
@@ -15,10 +72,10 @@ class ReverseSingleRelatedObjectDescriptor(object):
In the example::
- class Choice(Model):
- poll = ForeignKey(Place, related_name='choices')
+ class Child(Model):
+ parent = ForeignKey(Parent, related_name='children')
- `choice.poll` is a ReverseSingleRelatedObjectDescriptor instance.
+ ``child.parent`` is a ``ReverseSingleRelatedObjectDescriptor`` instance.
"""
def __init__(self, field_with_rel):
@@ -79,8 +136,18 @@ class ReverseSingleRelatedObjectDescriptor(object):
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
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:
return self
+
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
@@ -95,6 +162,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
if not self.field.remote_field.multiple:
setattr(rel_obj, self.field.remote_field.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
+
if rel_obj is None and not self.field.null:
raise self.RelatedObjectDoesNotExist(
"%s has no %s." % (self.field.model.__name__, self.field.name)
@@ -103,6 +171,15 @@ class ReverseSingleRelatedObjectDescriptor(object):
return rel_obj
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
# to be an instance of the related class.
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
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:
return self
+
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
@@ -238,6 +327,7 @@ class SingleRelatedObjectDescriptor(object):
else:
setattr(rel_obj, self.related.field.get_cache_name(), instance)
setattr(instance, self.cache_name, rel_obj)
+
if rel_obj is None:
raise self.RelatedObjectDoesNotExist(
"%s has no %s." % (
@@ -249,6 +339,17 @@ class SingleRelatedObjectDescriptor(object):
return rel_obj
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
# ReverseSingleRelatedObjectDescriptor is annoying, but there's a bunch
# of small differences that would make a common base class convoluted.
@@ -299,10 +400,13 @@ class ForeignRelatedObjectsDescriptor(object):
In the example::
- class Choice(Model):
- poll = ForeignKey(Place, related_name='choices')
+ class Child(Model):
+ parent = ForeignKey(Parent, related_name='children')
+
+ ``parent.children`` is a ``ForeignRelatedObjectsDescriptor`` instance.
- ``poll.choices`` 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):
@@ -317,21 +421,40 @@ class ForeignRelatedObjectsDescriptor(object):
)
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:
return self
return self.related_manager_cls(instance)
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.set(value)
def create_foreign_related_manager(superclass, rel):
"""
- Factory function to create a manager that subclasses another manager
- (generally the default manager of a given model) and adds behaviors
- specific to many-to-one relations.
+ Create a manager for the reverse side of a many-to-one relation.
+
+ 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):
@@ -520,8 +643,11 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
- ``pizza.toppings`` and ``topping.pizzas`` are ManyRelatedObjectsDescriptor
- instances.
+ ``pizza.toppings`` and ``topping.pizzas`` are
+ ``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):
@@ -548,9 +674,10 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
def create_many_related_manager(superclass, rel, reverse):
"""
- Factory function to create a manager that subclasses another manager
- (generally the default manager of a given model) and adds behaviors
- specific to many-to-many relations.
+ Create a manager for the either side of a many-to-many relation.
+
+ 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):