Reworked docstrings and comments in related.py.

Thanks Tim Graham for the review.
This commit is contained in:
Loic Bistuer 2015-02-16 16:06:42 +07:00
parent c5a77721e2
commit 5efd472178
2 changed files with 201 additions and 87 deletions

View File

@ -19,9 +19,14 @@ from django.utils.functional import cached_property
@python_2_unicode_compatible
class GenericForeignKey(object):
"""
Provides a generic relation to any object through content-type/object-id
fields.
Provide a generic many-to-one relation through the ``content_type`` and
``object_id`` fields.
This class also doubles as an accessor to the related object (similar to
ReverseSingleRelatedObjectDescriptor) by adding itself as a model
attribute.
"""
# Field flags
auto_created = False
concrete = False
@ -96,9 +101,10 @@ class GenericForeignKey(object):
return []
def _check_content_type_field(self):
""" Check if field named `field_name` in model `model` exists and is
valid content_type field (is a ForeignKey to ContentType). """
"""
Check if field named `field_name` in model `model` exists and is a
valid content_type field (is a ForeignKey to ContentType).
"""
try:
field = self.model._meta.get_field(self.ct_field)
except FieldDoesNotExist:
@ -146,8 +152,8 @@ class GenericForeignKey(object):
def instance_pre_init(self, signal, sender, args, kwargs, **_kwargs):
"""
Handles initializing an object with the generic FK instead of
content-type/object-id fields.
Handle initializing an object with the generic FK instead of
content_type and object_id fields.
"""
if self.name in kwargs:
value = kwargs.pop(self.name)
@ -256,6 +262,10 @@ class GenericForeignKey(object):
class GenericRel(ForeignObjectRel):
"""
Used by GenericRelation to store information about the relation.
"""
def __init__(self, field, to, related_name=None, related_query_name=None, limit_choices_to=None):
super(GenericRel, self).__init__(
field, to,
@ -267,7 +277,10 @@ class GenericRel(ForeignObjectRel):
class GenericRelation(ForeignObject):
"""Provides an accessor to generic related objects (e.g. comments)"""
"""
Provide a reverse to a relation created by a GenericForeignKey.
"""
# Field flags
auto_created = False
@ -310,9 +323,6 @@ class GenericRelation(ForeignObject):
def _check_generic_foreign_key_existence(self):
target = self.rel.to
if isinstance(target, ModelBase):
# Using `vars` is very ugly approach, but there is no better one,
# because GenericForeignKeys are not considered as fields and,
# therefore, are not included in `target._meta.local_fields`.
fields = target._meta.virtual_fields
if any(isinstance(field, GenericForeignKey) and
field.ct_field == self.content_type_field_name and
@ -358,9 +368,7 @@ class GenericRelation(ForeignObject):
def contribute_to_class(self, cls, name, **kwargs):
kwargs['virtual_only'] = True
super(GenericRelation, self).contribute_to_class(cls, name, **kwargs)
# Save a reference to which model this class is on for future use
self.model = cls
# Add the descriptor for the relation
setattr(cls, self.name, ReverseGenericRelatedObjectsDescriptor(self.rel))
def set_attributes_from_rel(self):
@ -371,7 +379,7 @@ class GenericRelation(ForeignObject):
def get_content_type(self):
"""
Returns the content type associated with this field's model.
Return the content type associated with this field's model.
"""
return ContentType.objects.get_for_model(self.model,
for_concrete_model=self.for_concrete_model)
@ -387,7 +395,6 @@ class GenericRelation(ForeignObject):
def bulk_related_objects(self, objs, using=DEFAULT_DB_ALIAS):
"""
Return all objects related to ``objs`` via this ``GenericRelation``.
"""
return self.rel.to._base_manager.db_manager(using).filter(**{
"%s__pk" % self.content_type_field_name: ContentType.objects.db_manager(using).get_for_model(
@ -398,17 +405,17 @@ class GenericRelation(ForeignObject):
class ReverseGenericRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
"""
This class provides the functionality that makes the related-object
managers available as attributes on a model class, for fields that have
multiple "remote" values and have a GenericRelation defined in their model
(rather than having another model pointed *at* them). In the example
"article.publications", the publications attribute is a
ReverseGenericRelatedObjectsDescriptor instance.
Accessor to the related objects manager on the one-to-many relation created
by GenericRelation.
In the example::
class Post(Model):
comments = GenericRelation(Comment)
``post.comments`` is a ReverseGenericRelatedObjectsDescriptor instance.
"""
@cached_property
def related_manager_cls(self):
return create_generic_related_manager(
@ -419,8 +426,9 @@ class ReverseGenericRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
def create_generic_related_manager(superclass, rel):
"""
Factory function for a manager that subclasses 'superclass' (which is a
Manager) and adds behavior for generic related objects.
Factory function to create a manager that subclasses another manager
(generally the default manager of a given model) and adds behaviors
specific to generic relations.
"""
class GenericRelatedObjectManager(superclass):

View File

@ -50,7 +50,8 @@ def add_lazy_relation(cls, field, relation, operation):
lazy relationships -- then the relation won't be set up until the
class_prepared signal fires at the end of model initialization.
operation is the work that must be performed once the relation can be resolved.
``operation`` is the work that must be performed once the relation can be
resolved.
"""
# Check for recursive relations
if relation == RECURSIVE_RELATIONSHIP_CONSTANT:
@ -58,17 +59,17 @@ def add_lazy_relation(cls, field, relation, operation):
model_name = cls.__name__
else:
# Look for an "app.Model" relation
# Look for an "app.Model" relation.
if isinstance(relation, six.string_types):
try:
app_label, model_name = relation.split(".")
except ValueError:
# If we can't split, assume a model in current app
# If we can't split, assume a model in current app.
app_label = cls._meta.app_label
model_name = relation
else:
# it's actually a model class
# It's actually a model class.
app_label = relation._meta.app_label
model_name = relation._meta.object_name
@ -88,7 +89,7 @@ def add_lazy_relation(cls, field, relation, operation):
def do_pending_lookups(sender, **kwargs):
"""
Handle any pending relations to the sending model. Sent from class_prepared.
Sent from class_prepared to handle pending relations to the sending model.
"""
key = (sender._meta.app_label, sender.__name__)
for cls, field, operation in sender._meta.apps._pending_lookups.pop(key, []):
@ -98,6 +99,10 @@ signals.class_prepared.connect(do_pending_lookups)
class RelatedField(Field):
"""
Base class that all relational fields inherit from.
"""
# Field flags
one_to_many = False
one_to_one = False
@ -174,8 +179,9 @@ class RelatedField(Field):
return []
def _check_clashes(self):
""" Check accessor and reverse query name clashes. """
"""
Check accessor and reverse query name clashes.
"""
from django.db.models.base import ModelBase
errors = []
@ -278,14 +284,13 @@ class RelatedField(Field):
return errors
def db_type(self, connection):
'''By default related field will not have a column
as it relates columns to another table'''
# By default related field will not have a column as it relates to
# columns from another table.
return None
def contribute_to_class(self, cls, name, virtual_only=False):
sup = super(RelatedField, self)
# Store the opts for related_query_name()
self.opts = cls._meta
if hasattr(sup, 'contribute_to_class'):
@ -310,7 +315,7 @@ class RelatedField(Field):
@property
def swappable_setting(self):
"""
Gets the setting that this is powered from for swapping, or None
Get the setting that this is powered from for swapping, or None
if it's not swapped in / marked with swappable=False.
"""
if self.swappable:
@ -350,7 +355,8 @@ class RelatedField(Field):
self.contribute_to_related_class(other, self.rel)
def get_limit_choices_to(self):
"""Returns 'limit_choices_to' for this model field.
"""
Return ``limit_choices_to`` for this model field.
If it is a callable, it will be invoked and the result will be
returned.
@ -360,7 +366,8 @@ class RelatedField(Field):
return self.rel.limit_choices_to
def formfield(self, **kwargs):
"""Passes ``limit_choices_to`` to field being constructed.
"""
Pass ``limit_choices_to`` to the field being constructed.
Only passes it if there is a type that supports related fields.
This is a similar strategy used to pass the ``queryset`` to the field
@ -379,19 +386,26 @@ class RelatedField(Field):
return super(RelatedField, self).formfield(**defaults)
def related_query_name(self):
# This method defines the name that can be used to identify this
# related object in a table-spanning query. It uses the lower-cased
# object_name by default, but this can be overridden with the
# "related_name" option.
"""
Define the name that can be used to identify this related object in a
table-spanning query.
"""
return self.rel.related_query_name or self.rel.related_name or self.opts.model_name
class SingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# a single "remote" value, on the class pointed to by a related field.
# In the example "place.restaurant", the restaurant attribute is a
# SingleRelatedObjectDescriptor instance.
"""
Accessor to the related object on the reverse side of a one-to-one
relation.
In the example::
class Restaurant(Model):
place = OneToOneField(Place, related_name='restaurant')
``place.restaurant`` is a ``SingleRelatedObjectDescriptor`` instance.
"""
def __init__(self, related):
self.related = related
self.cache_name = related.get_cache_name()
@ -517,11 +531,18 @@ class SingleRelatedObjectDescriptor(object):
class ReverseSingleRelatedObjectDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# a single "remote" value, on the class that defines the related field.
# In the example "choice.poll", the poll attribute is a
# ReverseSingleRelatedObjectDescriptor instance.
"""
Accessor to the related object on the forward side of a many-to-one or
one-to-one relation.
In the example::
class Choice(Model):
poll = ForeignKey(Place, related_name='choices')
`choice.poll` is a ReverseSingleRelatedObjectDescriptor instance.
"""
def __init__(self, field_with_rel):
self.field = field_with_rel
self.cache_name = self.field.get_cache_name()
@ -679,6 +700,12 @@ class ReverseSingleRelatedObjectDescriptor(object):
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.
"""
class RelatedManager(superclass):
def __init__(self, instance):
super(RelatedManager, self).__init__()
@ -833,11 +860,18 @@ def create_foreign_related_manager(superclass, rel):
class ForeignRelatedObjectsDescriptor(object):
# This class provides the functionality that makes the related-object
# managers available as attributes on a model class, for fields that have
# multiple "remote" values and have a ForeignKey pointed at them by
# some other model. In the example "poll.choice_set", the choice_set
# attribute is a ForeignRelatedObjectsDescriptor instance.
"""
Accessor to the related objects manager on the reverse side of a
many-to-one relation.
In the example::
class Choice(Model):
poll = ForeignKey(Place, related_name='choices')
``poll.choices`` is a ``ForeignRelatedObjectsDescriptor`` instance.
"""
def __init__(self, rel):
self.rel = rel
self.field = rel.field
@ -861,9 +895,12 @@ class ForeignRelatedObjectsDescriptor(object):
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.
"""
"""Creates a manager that subclasses 'superclass' (which is a Manager)
and adds behavior for many-to-many related objects."""
class ManyRelatedManager(superclass):
def __init__(self, instance=None):
super(ManyRelatedManager, self).__init__()
@ -1198,9 +1235,18 @@ def create_many_related_manager(superclass, rel, reverse):
class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
"""
Accessor to the related objects manager on the forward and reverse sides of
a many-to-many relation.
In the example::
class Pizza(Model):
toppings = ManyToManyField(Topping, related_name='pizzas')
``pizza.toppings`` and ``topping.pizzas`` are ManyRelatedObjectsDescriptor
instances.
"""
def __init__(self, rel, reverse=False):
super(ManyRelatedObjectsDescriptor, self).__init__(rel)
@ -1217,8 +1263,6 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
@cached_property
def related_manager_cls(self):
model = self.rel.related_model if self.reverse else self.rel.to
# Dynamically create a class that subclasses the related model's
# default manager.
return create_many_related_manager(
model._default_manager.__class__,
self.rel,
@ -1227,7 +1271,12 @@ class ManyRelatedObjectsDescriptor(ForeignRelatedObjectsDescriptor):
class ForeignObjectRel(object):
"""
Used by ForeignObject to store information about the relation.
``_meta.get_fields()`` returns this class to provide access to the field
flags for the reverse relation.
"""
# Field flags
auto_created = True
@ -1301,7 +1350,7 @@ class ForeignObjectRel(object):
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
limit_to_currently_related=False):
"""
Returns choices with a default blank choices included, for use as
Return choices with a default blank choices included, for use as
SelectField choices for this field.
Analog of django.db.models.fields.Field.get_choices(), provided
@ -1370,6 +1419,20 @@ class ForeignObjectRel(object):
class ManyToOneRel(ForeignObjectRel):
"""
Used by the ForeignKey field to store information about the relation.
``_meta.get_fields()`` returns this class to provide access to the field
flags for the reverse relation.
Note: Because we somewhat abuse the Rel objects by using them as reverse
fields we get the funny situation where
``ManyToOneRel.many_to_one == False`` and
``ManyToOneRel.one_to_many == True``. This is unfortunate but the actual
ManyToOneRel class is a private API and there is work underway to turn
reverse relations into actual fields.
"""
def __init__(self, field, to, field_name, related_name=None, related_query_name=None,
limit_choices_to=None, parent_link=False, on_delete=None):
super(ManyToOneRel, self).__init__(
@ -1385,8 +1448,7 @@ class ManyToOneRel(ForeignObjectRel):
def get_related_field(self):
"""
Returns the Field in the 'to' object to which this relationship is
tied.
Return the Field in the 'to' object to which this relationship is tied.
"""
field = self.to._meta.get_field(self.field_name)
if not field.concrete:
@ -1399,6 +1461,13 @@ class ManyToOneRel(ForeignObjectRel):
class OneToOneRel(ManyToOneRel):
"""
Used by OneToOneField to store information about the relation.
``_meta.get_fields()`` returns this class to provide access to the field
flags for the reverse relation.
"""
def __init__(self, field, to, field_name, related_name=None, related_query_name=None,
limit_choices_to=None, parent_link=False, on_delete=None):
super(OneToOneRel, self).__init__(
@ -1414,6 +1483,13 @@ class OneToOneRel(ManyToOneRel):
class ManyToManyRel(ForeignObjectRel):
"""
Used by ManyToManyField to store information about the relation.
``_meta.get_fields()`` returns this class to provide access to the field
flags for the reverse relation.
"""
def __init__(self, field, to, related_name=None, related_query_name=None,
limit_choices_to=None, symmetrical=True, through=None, through_fields=None,
db_constraint=True):
@ -1437,7 +1513,7 @@ class ManyToManyRel(ForeignObjectRel):
def get_related_field(self):
"""
Returns the field in the 'to' object to which this relationship is tied.
Return the field in the 'to' object to which this relationship is tied.
Provided for symmetry with ManyToOneRel.
"""
opts = self.through._meta
@ -1452,6 +1528,10 @@ class ManyToManyRel(ForeignObjectRel):
class ForeignObject(RelatedField):
"""
Abstraction of the ForeignKey relation, supports multi-column relations.
"""
# Field flags
many_to_many = False
many_to_one = True
@ -1491,7 +1571,6 @@ class ForeignObject(RelatedField):
if rel_is_string or not self.requires_unique_target:
return []
# Skip if the
try:
self.foreign_related_fields
except FieldDoesNotExist:
@ -1640,7 +1719,7 @@ class ForeignObject(RelatedField):
def get_extra_descriptor_filter(self, instance):
"""
Returns an extra filter condition for related object fetching when
Return an extra filter condition for related object fetching when
user does 'instance.fieldname', that is the extra filter is used in
the descriptor of the field.
@ -1655,7 +1734,7 @@ class ForeignObject(RelatedField):
def get_extra_restriction(self, where_class, alias, related_alias):
"""
Returns a pair condition used for joining and subquery pushdown. The
Return a pair condition used for joining and subquery pushdown. The
condition is something that responds to as_sql(compiler, connection)
method.
@ -1765,6 +1844,14 @@ class ForeignObject(RelatedField):
class ForeignKey(ForeignObject):
"""
Provide a many-to-one relation by adding a column to the local model
to hold the remote value.
By default ForeignKey will target the pk of the remote model but this
behavior can be changed by using the ``to_field`` argument.
"""
# Field flags
many_to_many = False
many_to_one = True
@ -1981,10 +2068,11 @@ class ForeignKey(ForeignObject):
class OneToOneField(ForeignKey):
"""
A OneToOneField is essentially the same as a ForeignKey, with the exception
that always carries a "unique" constraint with it and the reverse relation
always returns the object pointed to (since there will only ever be one),
rather than returning a list.
that it always carries a "unique" constraint with it and the reverse
relation always returns the object pointed to (since there will only ever
be one), rather than returning a list.
"""
# Field flags
many_to_many = False
many_to_one = False
@ -2018,7 +2106,7 @@ class OneToOneField(ForeignKey):
setattr(instance, self.attname, data)
def _check_unique(self, **kwargs):
# override ForeignKey since check isn't applicable here
# Override ForeignKey since check isn't applicable here.
return []
@ -2078,6 +2166,15 @@ def create_many_to_many_intermediary_model(field, klass):
class ManyToManyField(RelatedField):
"""
Provide a many-to-many relation by using an intermediary model that
holds two ForeignKey fields pointed at the two sides of the relation.
Unless a ``through`` model was provided, ManyToManyField will use the
create_many_to_many_intermediary_model factory to automatically generate
the intermediary model.
"""
# Field flags
many_to_many = True
many_to_one = False
@ -2296,10 +2393,10 @@ class ManyToManyField(RelatedField):
)
)
# Validate `through_fields`
# Validate `through_fields`.
if self.rel.through_fields is not None:
# Validate that we're given an iterable of at least two items
# and that none of them is "falsy"
# and that none of them is "falsy".
if not (len(self.rel.through_fields) >= 2 and
self.rel.through_fields[0] and self.rel.through_fields[1]):
errors.append(
@ -2317,7 +2414,7 @@ class ManyToManyField(RelatedField):
# Validate the given through fields -- they should be actual
# fields on the through model, and also be foreign keys to the
# expected models
# expected models.
else:
assert from_model is not None, (
"ManyToManyField with intermediate "
@ -2372,7 +2469,7 @@ class ManyToManyField(RelatedField):
def deconstruct(self):
name, path, args, kwargs = super(ManyToManyField, self).deconstruct()
# Handle the simpler arguments
# Handle the simpler arguments.
if self.db_table is not None:
kwargs['db_table'] = self.db_table
if self.rel.db_constraint is not True:
@ -2395,7 +2492,7 @@ class ManyToManyField(RelatedField):
# of a swap.
swappable_setting = self.swappable_setting
if swappable_setting is not None:
# If it's already a settings reference, error
# If it's already a settings reference, error.
if hasattr(kwargs['to'], "setting_name"):
if kwargs['to'].setting_name != swappable_setting:
raise ValueError(
@ -2403,7 +2500,7 @@ class ManyToManyField(RelatedField):
"model that is swapped in place of more than one model "
"(%s and %s)" % (kwargs['to'].setting_name, swappable_setting)
)
# Set it
from django.db.migrations.writer import SettingsReference
kwargs['to'] = SettingsReference(
kwargs['to'],
@ -2439,7 +2536,10 @@ class ManyToManyField(RelatedField):
return Field.get_choices(self, include_blank=False)
def _get_m2m_db_table(self, opts):
"Function that can be curried to provide the m2m table name for this relation"
"""
Function that can be curried to provide the m2m table name for this
relation.
"""
if self.rel.through is not None:
return self.rel.through._meta.db_table
elif self.db_table:
@ -2449,7 +2549,10 @@ class ManyToManyField(RelatedField):
connection.ops.max_name_length())
def _get_m2m_attr(self, related, attr):
"Function that can be curried to provide the source accessor or DB column name for the m2m table"
"""
Function that can be curried to provide the source accessor or DB
column name for the m2m table.
"""
cache_attr = '_m2m_%s_cache' % attr
if hasattr(self, cache_attr):
return getattr(self, cache_attr)
@ -2464,7 +2567,10 @@ class ManyToManyField(RelatedField):
return getattr(self, cache_attr)
def _get_m2m_reverse_attr(self, related, attr):
"Function that can be curried to provide the related accessor or DB column name for the m2m table"
"""
Function that can be curried to provide the related accessor or DB
column name for the m2m table.
"""
cache_attr = '_m2m_reverse_%s_cache' % attr
if hasattr(self, cache_attr):
return getattr(self, cache_attr)
@ -2474,7 +2580,6 @@ class ManyToManyField(RelatedField):
else:
link_field_name = None
for f in self.rel.through._meta.fields:
# NOTE f.rel.to != f.related_model
if f.is_relation and f.rel.to == related.model:
if link_field_name is None and related.related_model == related.model:
# If this is an m2m-intermediate to self,
@ -2524,11 +2629,10 @@ class ManyToManyField(RelatedField):
if not self.rel.through and not cls._meta.abstract and not cls._meta.swapped:
self.rel.through = create_many_to_many_intermediary_model(self, cls)
# Add the descriptor for the m2m relation
# Add the descriptor for the m2m relation.
setattr(cls, self.name, ManyRelatedObjectsDescriptor(self.rel, reverse=False))
# Set up the accessor for the m2m table name for the relation
# Set up the accessor for the m2m table name for the relation.
self.m2m_db_table = curry(self._get_m2m_db_table, cls._meta)
# Populate some necessary rel arguments so that cross-app relations
@ -2544,7 +2648,7 @@ class ManyToManyField(RelatedField):
if not self.rel.is_hidden() and not related.related_model._meta.swapped:
setattr(cls, related.get_accessor_name(), ManyRelatedObjectsDescriptor(self.rel, reverse=True))
# Set up the accessors for the column names on the m2m table
# Set up the accessors for the column names on the m2m table.
self.m2m_column_name = curry(self._get_m2m_attr, related, 'column')
self.m2m_reverse_name = curry(self._get_m2m_reverse_attr, related, 'column')
@ -2560,7 +2664,9 @@ class ManyToManyField(RelatedField):
pass
def value_from_object(self, obj):
"Returns the value of this field in the given model instance."
"""
Return the value of this field in the given model instance.
"""
return getattr(obj, self.attname).all()
def save_form_data(self, instance, data):