Renamed descriptor classes for related objects.

The old names were downright confusing. Some seemed to mean the opposite
of what the class actually did.

The new names follow a consistent nomenclature:

    (Forward|Reverse)(ManyToOne|OneToOne|ManyToMany)Descriptor.

I mentioned combinations that do not exist in the docstring in order to
help people who would search for them in the code base.
This commit is contained in:
Aymeric Augustin 2015-09-20 17:51:25 +02:00
parent 2409a4241a
commit e542e81b39
10 changed files with 61 additions and 58 deletions

View File

@ -9,7 +9,7 @@ from django.db import DEFAULT_DB_ALIAS, connection, models, router, transaction
from django.db.models import DO_NOTHING, signals
from django.db.models.base import ModelBase, make_foreign_order_accessors
from django.db.models.fields.related import (
ForeignObject, ForeignObjectRel, ForeignRelatedObjectsDescriptor,
ForeignObject, ForeignObjectRel, ReverseManyToOneDescriptor,
lazy_related_operation,
)
from django.db.models.query_utils import PathInfo
@ -24,8 +24,7 @@ class GenericForeignKey(object):
``object_id`` fields.
This class also doubles as an accessor to the related object (similar to
ReverseSingleRelatedObjectDescriptor) by adding itself as a model
attribute.
ForwardManyToOneDescriptor) by adding itself as a model attribute.
"""
# Field flags
@ -381,7 +380,7 @@ class GenericRelation(ForeignObject):
kwargs['virtual_only'] = True
super(GenericRelation, self).contribute_to_class(cls, name, **kwargs)
self.model = cls
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self.remote_field))
setattr(cls, self.name, ReverseGenericManyToOneDescriptor(self.remote_field))
# Add get_RELATED_order() and set_RELATED_order() methods if the model
# on the other end of this relation is ordered with respect to this.
@ -430,7 +429,7 @@ class GenericRelation(ForeignObject):
})
class ReverseGenericRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
class ReverseGenericManyToOneDescriptor(ReverseManyToOneDescriptor):
"""
Accessor to the related objects manager on the one-to-many relation created
by GenericRelation.
@ -440,7 +439,7 @@ class ReverseGenericRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
class Post(Model):
comments = GenericRelation(Comment)
``post.comments`` is a ReverseGenericRelatedObjectsDescriptor instance.
``post.comments`` is a ReverseGenericManyToOneDescriptor instance.
"""
@cached_property

View File

@ -25,8 +25,8 @@ from . import (
PositiveSmallIntegerField,
)
from .related_descriptors import (
ForeignRelatedObjectsDescriptor, ManyRelatedObjectsDescriptor,
ReverseSingleRelatedObjectDescriptor, SingleRelatedObjectDescriptor,
ForwardManyToOneDescriptor, ManyToManyDescriptor,
ReverseManyToOneDescriptor, ReverseOneToOneDescriptor,
)
from .related_lookups import (
RelatedExact, RelatedGreaterThan, RelatedGreaterThanOrEqual, RelatedIn,
@ -432,7 +432,7 @@ class ForeignObject(RelatedField):
one_to_one = False
requires_unique_target = True
related_accessor_class = ForeignRelatedObjectsDescriptor
related_accessor_class = ReverseManyToOneDescriptor
rel_class = ForeignObjectRel
def __init__(self, to, on_delete, from_fields, to_fields, rel=None, related_name=None,
@ -684,7 +684,7 @@ class ForeignObject(RelatedField):
def contribute_to_class(self, cls, name, virtual_only=False):
super(ForeignObject, self).contribute_to_class(cls, name, virtual_only=virtual_only)
setattr(cls, self.name, ReverseSingleRelatedObjectDescriptor(self))
setattr(cls, self.name, ForwardManyToOneDescriptor(self))
def contribute_to_related_class(self, cls, related):
# Internal FK's - i.e., those with a related name ending with '+' -
@ -974,7 +974,7 @@ class OneToOneField(ForeignKey):
one_to_many = False
one_to_one = True
related_accessor_class = SingleRelatedObjectDescriptor
related_accessor_class = ReverseOneToOneDescriptor
rel_class = OneToOneRel
description = _("One-to-one relationship")
@ -1560,7 +1560,7 @@ class ManyToManyField(RelatedField):
self.remote_field.through = create_many_to_many_intermediary_model(self, cls)
# Add the descriptor for the m2m relation.
setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.remote_field, reverse=False))
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False))
# Set up the accessor for the m2m table name for the relation.
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
@ -1569,7 +1569,7 @@ class ManyToManyField(RelatedField):
# Internal M2Ms (i.e., those with a related name ending with '+')
# and swapped models don't get a related descriptor.
if not self.remote_field.is_hidden() and not related.related_model._meta.swapped:
setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(self.remote_field, reverse=True))
setattr(cls, related.get_accessor_name(), ManyToManyDescriptor(self.remote_field, reverse=True))
# Set up the accessors for the column names on the m2m table.
self.m2m_column_name = curry(self._get_m2m_attr, related, 'column')

View File

@ -22,37 +22,42 @@ 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``.
relation: ``ForwardManyToOneDescriptor``.
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).
If you're looking for ``ForwardOneToOneDescriptor``, use
``ForwardManyToOneDescriptor`` instead.
2. Related instance on the reverse side of a one-to-one relation:
``SingleRelatedObjectDescriptor``.
``ReverseOneToOneDescriptor``.
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``.
one table to another. As a consequence ``ReverseOneToOneDescriptor`` is
slightly different from ``ForwardManyToOneDescriptor``.
3. Related objects manager for related instances on the reverse side of a
many-to-one relation: ``ForeignRelatedObjectsDescriptor``.
many-to-one relation: ``ReverseManyToOneDescriptor``.
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``.
sides of a many-to-many relation: ``ManyToManyDescriptor``.
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.
If you're looking for ``ForwardManyToManyDescriptor`` or
``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
"""
from __future__ import unicode_literals
@ -65,7 +70,7 @@ from django.db.models.query import QuerySet
from django.utils.functional import cached_property
class ReverseSingleRelatedObjectDescriptor(object):
class ForwardManyToOneDescriptor(object):
"""
Accessor to the related object on the forward side of a many-to-one or
one-to-one relation.
@ -75,7 +80,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
``child.parent`` is a ``ReverseSingleRelatedObjectDescriptor`` instance.
``child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
"""
def __init__(self, field_with_rel):
@ -150,7 +155,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
# The related instance is loaded from the database and then cached in
# the attribute defined in self.cache_name. It can also be pre-cached
# by the reverse accessor (SingleRelatedObjectDescriptor).
# by the reverse accessor (ReverseOneToOneDescriptor).
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
@ -249,7 +254,7 @@ class ReverseSingleRelatedObjectDescriptor(object):
setattr(value, self.field.remote_field.get_cache_name(), instance)
class SingleRelatedObjectDescriptor(object):
class ReverseOneToOneDescriptor(object):
"""
Accessor to the related object on the reverse side of a one-to-one
relation.
@ -259,7 +264,7 @@ class SingleRelatedObjectDescriptor(object):
class Restaurant(Model):
place = OneToOneField(Place, related_name='restaurant')
``place.restaurant`` is a ``SingleRelatedObjectDescriptor`` instance.
``place.restaurant`` is a ``ReverseOneToOneDescriptor`` instance.
"""
def __init__(self, related):
@ -269,7 +274,7 @@ class SingleRelatedObjectDescriptor(object):
@cached_property
def RelatedObjectDoesNotExist(self):
# The exception isn't created at initialization time for the sake of
# consistency with `ReverseSingleRelatedObjectDescriptor`.
# consistency with `ForwardManyToOneDescriptor`.
return type(
str('RelatedObjectDoesNotExist'),
(self.related.related_model.DoesNotExist, AttributeError),
@ -323,7 +328,7 @@ class SingleRelatedObjectDescriptor(object):
# The related instance is loaded from the database and then cached in
# the attribute defined in self.cache_name. It can also be pre-cached
# by the forward accessor (ReverseSingleRelatedObjectDescriptor).
# by the forward accessor (ForwardManyToOneDescriptor).
try:
rel_obj = getattr(instance, self.cache_name)
except AttributeError:
@ -366,7 +371,7 @@ class SingleRelatedObjectDescriptor(object):
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
# ForwardManyToOneDescriptor is annoying, but there's a bunch
# of small differences that would make a common base class convoluted.
# If null=True, we can assign null here, but otherwise the value needs
@ -410,7 +415,7 @@ class SingleRelatedObjectDescriptor(object):
setattr(value, self.related.field.get_cache_name(), instance)
class ForeignRelatedObjectsDescriptor(object):
class ReverseManyToOneDescriptor(object):
"""
Accessor to the related objects manager on the reverse side of a
many-to-one relation.
@ -420,10 +425,10 @@ class ForeignRelatedObjectsDescriptor(object):
class Child(Model):
parent = ForeignKey(Parent, related_name='children')
``parent.children`` is a ``ForeignRelatedObjectsDescriptor`` instance.
``parent.children`` is a ``ReverseManyToOneDescriptor`` instance.
Most of the implementation is delegated to a dynamically defined manager
class built by ``create_many_related_manager()`` which is defined below.
class built by ``create_forward_many_to_many_manager()`` defined below.
"""
def __init__(self, rel):
@ -432,7 +437,7 @@ class ForeignRelatedObjectsDescriptor(object):
@cached_property
def related_manager_cls(self):
return create_foreign_related_manager(
return create_reverse_many_to_one_manager(
self.rel.related_model._default_manager.__class__,
self.rel,
)
@ -466,7 +471,7 @@ class ForeignRelatedObjectsDescriptor(object):
manager.set(value)
def create_foreign_related_manager(superclass, rel):
def create_reverse_many_to_one_manager(superclass, rel):
"""
Create a manager for the reverse side of a many-to-one relation.
@ -488,7 +493,7 @@ def create_foreign_related_manager(superclass, rel):
# We use **kwargs rather than a kwarg argument to enforce the
# `manager='manager_name'` syntax.
manager = getattr(self.model, kwargs.pop('manager'))
manager_class = create_foreign_related_manager(manager.__class__, rel)
manager_class = create_reverse_many_to_one_manager(manager.__class__, rel)
return manager_class(self.instance)
do_not_call_in_templates = True
@ -650,7 +655,7 @@ def create_foreign_related_manager(superclass, rel):
return RelatedManager
class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
class ManyToManyDescriptor(ReverseManyToOneDescriptor):
"""
Accessor to the related objects manager on the forward and reverse sides of
a many-to-many relation.
@ -660,15 +665,15 @@ 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 ``ManyToManyDescriptor``
instances.
Most of the implementation is delegated to a dynamically defined manager
class built by ``create_many_related_manager()`` which is defined below.
class built by ``create_forward_many_to_many_manager()`` defined below.
"""
def __init__(self, rel, reverse=False):
super(ManyRelatedObjectsDescriptor, self).__init__(rel)
super(ManyToManyDescriptor, self).__init__(rel)
self.reverse = reverse
@ -682,14 +687,14 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
@cached_property
def related_manager_cls(self):
model = self.rel.related_model if self.reverse else self.rel.model
return create_many_related_manager(
return create_forward_many_to_many_manager(
model._default_manager.__class__,
self.rel,
reverse=self.reverse,
)
def create_many_related_manager(superclass, rel, reverse):
def create_forward_many_to_many_manager(superclass, rel, reverse):
"""
Create a manager for the either side of a many-to-many relation.
@ -746,7 +751,7 @@ def create_many_related_manager(superclass, rel, reverse):
# We use **kwargs rather than a kwarg argument to enforce the
# `manager='manager_name'` syntax.
manager = getattr(self.model, kwargs.pop('manager'))
manager_class = create_many_related_manager(manager.__class__, rel, reverse)
manager_class = create_forward_many_to_many_manager(manager.__class__, rel, reverse)
return manager_class(instance=self.instance)
do_not_call_in_templates = True

View File

@ -1,11 +1,10 @@
from django.db import models
from django.db.models.fields.related import \
ReverseSingleRelatedObjectDescriptor
from django.db.models.fields.related import ForwardManyToOneDescriptor
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import get_language
class ArticleTranslationDescriptor(ReverseSingleRelatedObjectDescriptor):
class ArticleTranslationDescriptor(ForwardManyToOneDescriptor):
"""
The set of articletranslation should not set any local fields.
"""

View File

@ -1,6 +1,6 @@
from django.db import models
from django.db.models.fields.related import (
ForeignObjectRel, ForeignRelatedObjectsDescriptor,
ForeignObjectRel, ReverseManyToOneDescriptor,
)
from django.db.models.lookups import StartsWith
from django.db.models.query_utils import PathInfo
@ -10,7 +10,7 @@ from django.utils.encoding import python_2_unicode_compatible
class CustomForeignObjectRel(ForeignObjectRel):
"""
Define some extra Field methods so this Rel acts more like a Field, which
lets us use ForeignRelatedObjectsDescriptor in both directions.
lets us use ReverseManyToOneDescriptor in both directions.
"""
@property
def foreign_related_fields(self):
@ -24,7 +24,7 @@ class StartsWithRelation(models.ForeignObject):
"""
A ForeignObject that uses StartsWith operator in its joins instead of
the default equality operator. This is logically a many-to-many relation
and creates a ForeignRelatedObjectsDescriptor in both directions.
and creates a ReverseManyToOneDescriptor in both directions.
"""
auto_created = False
@ -42,7 +42,7 @@ class StartsWithRelation(models.ForeignObject):
@property
def field(self):
"""
Makes ForeignRelatedObjectsDescriptor work in both directions.
Makes ReverseManyToOneDescriptor work in both directions.
"""
return self.remote_field
@ -66,7 +66,7 @@ class StartsWithRelation(models.ForeignObject):
def contribute_to_class(self, cls, name, virtual_only=False):
super(StartsWithRelation, self).contribute_to_class(cls, name, virtual_only)
setattr(cls, self.name, ForeignRelatedObjectsDescriptor(self))
setattr(cls, self.name, ReverseManyToOneDescriptor(self))
class BrokenContainsRelation(StartsWithRelation):

View File

@ -329,7 +329,7 @@ class GenericRelationsTests(TestCase):
def test_assign_with_queryset(self):
# Ensure that querysets used in reverse GFK assignments are pre-evaluated
# so their value isn't affected by the clearing operation in
# ManyRelatedObjectsDescriptor.__set__. Refs #19816.
# ManyToManyDescriptor.__set__. Refs #19816.
bacon = Vegetable.objects.create(name="Bacon", is_yucky=False)
bacon.tags.create(tag="fatty")
bacon.tags.create(tag="salty")

View File

@ -412,7 +412,7 @@ class ManyToManyTests(TestCase):
def test_forward_assign_with_queryset(self):
# Ensure that querysets used in m2m assignments are pre-evaluated
# so their value isn't affected by the clearing operation in
# ManyRelatedObjectsDescriptor.__set__. Refs #19816.
# ManyToManyDescriptor.__set__. Refs #19816.
self.a1.publications = [self.p1, self.p2]
qs = self.a1.publications.filter(title='The Python Journal')
@ -424,7 +424,7 @@ class ManyToManyTests(TestCase):
def test_reverse_assign_with_queryset(self):
# Ensure that querysets used in M2M assignments are pre-evaluated
# so their value isn't affected by the clearing operation in
# ManyRelatedObjectsDescriptor.__set__. Refs #19816.
# ManyToManyDescriptor.__set__. Refs #19816.
self.p1.article_set = [self.a1, self.a2]
qs = self.p1.article_set.filter(headline='Django lets you build Web apps easily')

View File

@ -106,7 +106,7 @@ class ManyToOneNullTests(TestCase):
def test_assign_with_queryset(self):
# Ensure that querysets used in reverse FK assignments are pre-evaluated
# so their value isn't affected by the clearing operation in
# ForeignRelatedObjectsDescriptor.__set__. Refs #19816.
# ReverseManyToOneDescriptor.__set__. Refs #19816.
self.r2.article_set = [self.a2, self.a3]
qs = self.r2.article_set.filter(headline="Second")

View File

@ -595,7 +595,7 @@ class CustomPrefetchTests(TestCase):
self.assertEqual(lst2[0].houses_lst[0].rooms_lst[0].main_room_of, self.house1)
self.assertEqual(len(lst2[1].houses_lst), 0)
# Test ReverseSingleRelatedObjectDescriptor.
# Test ForwardManyToOneDescriptor.
houses = House.objects.select_related('owner')
with self.assertNumQueries(6):
rooms = Room.objects.all().prefetch_related('house')
@ -624,7 +624,7 @@ class CustomPrefetchTests(TestCase):
with self.assertNumQueries(3):
getattr(rooms.first().house, 'address')
# Test SingleRelatedObjectDescriptor.
# Test ReverseOneToOneDescriptor.
houses = House.objects.select_related('owner')
with self.assertNumQueries(6):
rooms = Room.objects.all().prefetch_related('main_room_of')

View File

@ -1,6 +1,6 @@
from django.db import models
from django.db.models.fields.related import (
RECURSIVE_RELATIONSHIP_CONSTANT, ManyRelatedObjectsDescriptor,
RECURSIVE_RELATIONSHIP_CONSTANT, ManyToManyDescriptor,
ManyToManyField, ManyToManyRel, RelatedField,
create_many_to_many_intermediary_model,
)
@ -42,7 +42,7 @@ class CustomManyToManyField(RelatedField):
super(CustomManyToManyField, self).contribute_to_class(cls, name, **kwargs)
if not self.remote_field.through and not cls._meta.abstract and not cls._meta.swapped:
self.remote_field.through = create_many_to_many_intermediary_model(self, cls)
setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.remote_field))
setattr(cls, self.name, ManyToManyDescriptor(self.remote_field))
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
def get_internal_type(self):