mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	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:
		| @@ -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): | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user