Fixed #22341 -- Split django.db.models.fields.related.
At 2800 lines it was the largest module in the django package. This commit brings it down to a more manageable 1620 lines. Very small changes were performed to uniformize import style.
This commit is contained in:
parent
1335aa2fb9
commit
005c9fc45f
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,890 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
from operator import attrgetter
|
||||
|
||||
from django.db import connections, router, transaction
|
||||
from django.db.models import Q, signals
|
||||
from django.db.models.query import QuerySet
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
|
||||
class ReverseSingleRelatedObjectDescriptor(object):
|
||||
"""
|
||||
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()
|
||||
|
||||
@cached_property
|
||||
def RelatedObjectDoesNotExist(self):
|
||||
# The exception can't be created at initialization time since the
|
||||
# related model might not be resolved yet; `rel.model` might still be
|
||||
# a string model reference.
|
||||
return type(
|
||||
str('RelatedObjectDoesNotExist'),
|
||||
(self.field.remote_field.model.DoesNotExist, AttributeError),
|
||||
{}
|
||||
)
|
||||
|
||||
def is_cached(self, instance):
|
||||
return hasattr(instance, self.cache_name)
|
||||
|
||||
def get_queryset(self, **hints):
|
||||
manager = self.field.remote_field.model._default_manager
|
||||
# If the related manager indicates that it should be used for
|
||||
# related fields, respect that.
|
||||
if not getattr(manager, 'use_for_related_fields', False):
|
||||
manager = self.field.remote_field.model._base_manager
|
||||
return manager.db_manager(hints=hints).all()
|
||||
|
||||
def get_prefetch_queryset(self, instances, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
queryset._add_hints(instance=instances[0])
|
||||
|
||||
rel_obj_attr = self.field.get_foreign_related_value
|
||||
instance_attr = self.field.get_local_related_value
|
||||
instances_dict = {instance_attr(inst): inst for inst in instances}
|
||||
related_field = self.field.foreign_related_fields[0]
|
||||
|
||||
# FIXME: This will need to be revisited when we introduce support for
|
||||
# composite fields. In the meantime we take this practical approach to
|
||||
# solve a regression on 1.6 when the reverse manager in hidden
|
||||
# (related_name ends with a '+'). Refs #21410.
|
||||
# The check for len(...) == 1 is a special case that allows the query
|
||||
# to be join-less and smaller. Refs #21760.
|
||||
if self.field.remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1:
|
||||
query = {'%s__in' % related_field.name: set(instance_attr(inst)[0] for inst in instances)}
|
||||
else:
|
||||
query = {'%s__in' % self.field.related_query_name(): instances}
|
||||
queryset = queryset.filter(**query)
|
||||
|
||||
# Since we're going to assign directly in the cache,
|
||||
# we must manage the reverse relation cache manually.
|
||||
if not self.field.remote_field.multiple:
|
||||
rel_obj_cache_name = self.field.remote_field.get_cache_name()
|
||||
for rel_obj in queryset:
|
||||
instance = instances_dict[rel_obj_attr(rel_obj)]
|
||||
setattr(rel_obj, rel_obj_cache_name, instance)
|
||||
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
|
||||
|
||||
def __get__(self, instance, instance_type=None):
|
||||
if instance is None:
|
||||
return self
|
||||
try:
|
||||
rel_obj = getattr(instance, self.cache_name)
|
||||
except AttributeError:
|
||||
val = self.field.get_local_related_value(instance)
|
||||
if None in val:
|
||||
rel_obj = None
|
||||
else:
|
||||
qs = self.get_queryset(instance=instance)
|
||||
qs = qs.filter(**self.field.get_reverse_related_filter(instance))
|
||||
# Assuming the database enforces foreign keys, this won't fail.
|
||||
rel_obj = qs.get()
|
||||
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)
|
||||
)
|
||||
else:
|
||||
return rel_obj
|
||||
|
||||
def __set__(self, instance, value):
|
||||
# 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:
|
||||
raise ValueError(
|
||||
'Cannot assign None: "%s.%s" does not allow null values.' %
|
||||
(instance._meta.object_name, self.field.name)
|
||||
)
|
||||
elif value is not None and not isinstance(value, self.field.remote_field.model):
|
||||
raise ValueError(
|
||||
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
|
||||
value,
|
||||
instance._meta.object_name,
|
||||
self.field.name,
|
||||
self.field.remote_field.model._meta.object_name,
|
||||
)
|
||||
)
|
||||
elif value is not None:
|
||||
if instance._state.db is None:
|
||||
instance._state.db = router.db_for_write(instance.__class__, instance=value)
|
||||
elif value._state.db is None:
|
||||
value._state.db = router.db_for_write(value.__class__, instance=instance)
|
||||
elif value._state.db is not None and instance._state.db is not None:
|
||||
if not router.allow_relation(value, instance):
|
||||
raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
|
||||
|
||||
# If we're setting the value of a OneToOneField to None, we need to clear
|
||||
# out the cache on any old related object. Otherwise, deleting the
|
||||
# previously-related object will also cause this object to be deleted,
|
||||
# which is wrong.
|
||||
if value is None:
|
||||
# Look up the previously-related object, which may still be available
|
||||
# since we've not yet cleared out the related field.
|
||||
# Use the cache directly, instead of the accessor; if we haven't
|
||||
# populated the cache, then we don't care - we're only accessing
|
||||
# the object to invalidate the accessor cache, so there's no
|
||||
# need to populate the cache just to expire it again.
|
||||
related = getattr(instance, self.cache_name, None)
|
||||
|
||||
# If we've got an old related object, we need to clear out its
|
||||
# cache. This cache also might not exist if the related object
|
||||
# hasn't been accessed yet.
|
||||
if related is not None:
|
||||
setattr(related, self.field.remote_field.get_cache_name(), None)
|
||||
|
||||
for lh_field, rh_field in self.field.related_fields:
|
||||
setattr(instance, lh_field.attname, None)
|
||||
|
||||
# Set the values of the related field.
|
||||
else:
|
||||
for lh_field, rh_field in self.field.related_fields:
|
||||
setattr(instance, lh_field.attname, getattr(value, rh_field.attname))
|
||||
|
||||
# Since we already know what the related object is, seed the related
|
||||
# object caches now, too. This avoids another db hit if you get the
|
||||
# object you just set.
|
||||
setattr(instance, self.cache_name, value)
|
||||
if value is not None and not self.field.remote_field.multiple:
|
||||
setattr(value, self.field.remote_field.get_cache_name(), instance)
|
||||
|
||||
|
||||
class SingleRelatedObjectDescriptor(object):
|
||||
"""
|
||||
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()
|
||||
|
||||
@cached_property
|
||||
def RelatedObjectDoesNotExist(self):
|
||||
# The exception isn't created at initialization time for the sake of
|
||||
# consistency with `ReverseSingleRelatedObjectDescriptor`.
|
||||
return type(
|
||||
str('RelatedObjectDoesNotExist'),
|
||||
(self.related.related_model.DoesNotExist, AttributeError),
|
||||
{}
|
||||
)
|
||||
|
||||
def is_cached(self, instance):
|
||||
return hasattr(instance, self.cache_name)
|
||||
|
||||
def get_queryset(self, **hints):
|
||||
manager = self.related.related_model._default_manager
|
||||
# If the related manager indicates that it should be used for
|
||||
# related fields, respect that.
|
||||
if not getattr(manager, 'use_for_related_fields', False):
|
||||
manager = self.related.related_model._base_manager
|
||||
return manager.db_manager(hints=hints).all()
|
||||
|
||||
def get_prefetch_queryset(self, instances, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = self.get_queryset()
|
||||
queryset._add_hints(instance=instances[0])
|
||||
|
||||
rel_obj_attr = attrgetter(self.related.field.attname)
|
||||
instance_attr = lambda obj: obj._get_pk_val()
|
||||
instances_dict = {instance_attr(inst): inst for inst in instances}
|
||||
query = {'%s__in' % self.related.field.name: instances}
|
||||
queryset = queryset.filter(**query)
|
||||
|
||||
# Since we're going to assign directly in the cache,
|
||||
# we must manage the reverse relation cache manually.
|
||||
rel_obj_cache_name = self.related.field.get_cache_name()
|
||||
for rel_obj in queryset:
|
||||
instance = instances_dict[rel_obj_attr(rel_obj)]
|
||||
setattr(rel_obj, rel_obj_cache_name, instance)
|
||||
return queryset, rel_obj_attr, instance_attr, True, self.cache_name
|
||||
|
||||
def __get__(self, instance, instance_type=None):
|
||||
if instance is None:
|
||||
return self
|
||||
try:
|
||||
rel_obj = getattr(instance, self.cache_name)
|
||||
except AttributeError:
|
||||
related_pk = instance._get_pk_val()
|
||||
if related_pk is None:
|
||||
rel_obj = None
|
||||
else:
|
||||
filter_args = self.related.field.get_forward_related_filter(instance)
|
||||
try:
|
||||
rel_obj = self.get_queryset(instance=instance).get(**filter_args)
|
||||
except self.related.related_model.DoesNotExist:
|
||||
rel_obj = None
|
||||
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." % (
|
||||
instance.__class__.__name__,
|
||||
self.related.get_accessor_name()
|
||||
)
|
||||
)
|
||||
else:
|
||||
return rel_obj
|
||||
|
||||
def __set__(self, instance, value):
|
||||
# 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.
|
||||
|
||||
# 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.related.field.null is False:
|
||||
raise ValueError(
|
||||
'Cannot assign None: "%s.%s" does not allow null values.' % (
|
||||
instance._meta.object_name,
|
||||
self.related.get_accessor_name(),
|
||||
)
|
||||
)
|
||||
elif value is not None and not isinstance(value, self.related.related_model):
|
||||
raise ValueError(
|
||||
'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
|
||||
value,
|
||||
instance._meta.object_name,
|
||||
self.related.get_accessor_name(),
|
||||
self.related.related_model._meta.object_name,
|
||||
)
|
||||
)
|
||||
elif value is not None:
|
||||
if instance._state.db is None:
|
||||
instance._state.db = router.db_for_write(instance.__class__, instance=value)
|
||||
elif value._state.db is None:
|
||||
value._state.db = router.db_for_write(value.__class__, instance=instance)
|
||||
elif value._state.db is not None and instance._state.db is not None:
|
||||
if not router.allow_relation(value, instance):
|
||||
raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
|
||||
|
||||
related_pk = tuple(getattr(instance, field.attname) for field in self.related.field.foreign_related_fields)
|
||||
# Set the value of the related field to the value of the related object's related field
|
||||
for index, field in enumerate(self.related.field.local_related_fields):
|
||||
setattr(value, field.attname, related_pk[index])
|
||||
|
||||
# Since we already know what the related object is, seed the related
|
||||
# object caches now, too. This avoids another db hit if you get the
|
||||
# object you just set.
|
||||
setattr(instance, self.cache_name, value)
|
||||
setattr(value, self.related.field.get_cache_name(), instance)
|
||||
|
||||
|
||||
class ForeignRelatedObjectsDescriptor(object):
|
||||
"""
|
||||
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
|
||||
|
||||
@cached_property
|
||||
def related_manager_cls(self):
|
||||
return create_foreign_related_manager(
|
||||
self.rel.related_model._default_manager.__class__,
|
||||
self.rel,
|
||||
)
|
||||
|
||||
def __get__(self, instance, instance_type=None):
|
||||
if instance is None:
|
||||
return self
|
||||
|
||||
return self.related_manager_cls(instance)
|
||||
|
||||
def __set__(self, instance, value):
|
||||
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.
|
||||
"""
|
||||
|
||||
class RelatedManager(superclass):
|
||||
def __init__(self, instance):
|
||||
super(RelatedManager, self).__init__()
|
||||
|
||||
self.instance = instance
|
||||
self.model = rel.related_model
|
||||
self.field = rel.field
|
||||
|
||||
self.core_filters = {self.field.name: instance}
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
# 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)
|
||||
return manager_class(self.instance)
|
||||
do_not_call_in_templates = True
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return self.instance._prefetched_objects_cache[self.field.related_query_name()]
|
||||
except (AttributeError, KeyError):
|
||||
db = self._db or router.db_for_read(self.model, instance=self.instance)
|
||||
empty_strings_as_null = connections[db].features.interprets_empty_strings_as_nulls
|
||||
qs = super(RelatedManager, self).get_queryset()
|
||||
qs._add_hints(instance=self.instance)
|
||||
if self._db:
|
||||
qs = qs.using(self._db)
|
||||
qs = qs.filter(**self.core_filters)
|
||||
for field in self.field.foreign_related_fields:
|
||||
val = getattr(self.instance, field.attname)
|
||||
if val is None or (val == '' and empty_strings_as_null):
|
||||
return qs.none()
|
||||
qs._known_related_objects = {self.field: {self.instance.pk: self.instance}}
|
||||
return qs
|
||||
|
||||
def get_prefetch_queryset(self, instances, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = super(RelatedManager, self).get_queryset()
|
||||
|
||||
queryset._add_hints(instance=instances[0])
|
||||
queryset = queryset.using(queryset._db or self._db)
|
||||
|
||||
rel_obj_attr = self.field.get_local_related_value
|
||||
instance_attr = self.field.get_foreign_related_value
|
||||
instances_dict = {instance_attr(inst): inst for inst in instances}
|
||||
query = {'%s__in' % self.field.name: instances}
|
||||
queryset = queryset.filter(**query)
|
||||
|
||||
# Since we just bypassed this class' get_queryset(), we must manage
|
||||
# the reverse relation manually.
|
||||
for rel_obj in queryset:
|
||||
instance = instances_dict[rel_obj_attr(rel_obj)]
|
||||
setattr(rel_obj, self.field.name, instance)
|
||||
cache_name = self.field.related_query_name()
|
||||
return queryset, rel_obj_attr, instance_attr, False, cache_name
|
||||
|
||||
def add(self, *objs, **kwargs):
|
||||
bulk = kwargs.pop('bulk', True)
|
||||
objs = list(objs)
|
||||
db = router.db_for_write(self.model, instance=self.instance)
|
||||
|
||||
def check_and_update_obj(obj):
|
||||
if not isinstance(obj, self.model):
|
||||
raise TypeError("'%s' instance expected, got %r" % (
|
||||
self.model._meta.object_name, obj,
|
||||
))
|
||||
setattr(obj, self.field.name, self.instance)
|
||||
|
||||
if bulk:
|
||||
pks = []
|
||||
for obj in objs:
|
||||
check_and_update_obj(obj)
|
||||
if obj._state.adding or obj._state.db != db:
|
||||
raise ValueError(
|
||||
"%r instance isn't saved. Use bulk=False or save "
|
||||
"the object first." % obj
|
||||
)
|
||||
pks.append(obj.pk)
|
||||
self.model._base_manager.using(db).filter(pk__in=pks).update(**{
|
||||
self.field.name: self.instance,
|
||||
})
|
||||
else:
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
for obj in objs:
|
||||
check_and_update_obj(obj)
|
||||
obj.save()
|
||||
add.alters_data = True
|
||||
|
||||
def create(self, **kwargs):
|
||||
kwargs[self.field.name] = self.instance
|
||||
db = router.db_for_write(self.model, instance=self.instance)
|
||||
return super(RelatedManager, self.db_manager(db)).create(**kwargs)
|
||||
create.alters_data = True
|
||||
|
||||
def get_or_create(self, **kwargs):
|
||||
kwargs[self.field.name] = self.instance
|
||||
db = router.db_for_write(self.model, instance=self.instance)
|
||||
return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
|
||||
get_or_create.alters_data = True
|
||||
|
||||
def update_or_create(self, **kwargs):
|
||||
kwargs[self.field.name] = self.instance
|
||||
db = router.db_for_write(self.model, instance=self.instance)
|
||||
return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
|
||||
update_or_create.alters_data = True
|
||||
|
||||
# remove() and clear() are only provided if the ForeignKey can have a value of null.
|
||||
if rel.field.null:
|
||||
def remove(self, *objs, **kwargs):
|
||||
if not objs:
|
||||
return
|
||||
bulk = kwargs.pop('bulk', True)
|
||||
val = self.field.get_foreign_related_value(self.instance)
|
||||
old_ids = set()
|
||||
for obj in objs:
|
||||
# Is obj actually part of this descriptor set?
|
||||
if self.field.get_local_related_value(obj) == val:
|
||||
old_ids.add(obj.pk)
|
||||
else:
|
||||
raise self.field.remote_field.model.DoesNotExist(
|
||||
"%r is not related to %r." % (obj, self.instance)
|
||||
)
|
||||
self._clear(self.filter(pk__in=old_ids), bulk)
|
||||
remove.alters_data = True
|
||||
|
||||
def clear(self, **kwargs):
|
||||
bulk = kwargs.pop('bulk', True)
|
||||
self._clear(self, bulk)
|
||||
clear.alters_data = True
|
||||
|
||||
def _clear(self, queryset, bulk):
|
||||
db = router.db_for_write(self.model, instance=self.instance)
|
||||
queryset = queryset.using(db)
|
||||
if bulk:
|
||||
# `QuerySet.update()` is intrinsically atomic.
|
||||
queryset.update(**{self.field.name: None})
|
||||
else:
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
for obj in queryset:
|
||||
setattr(obj, self.field.name, None)
|
||||
obj.save(update_fields=[self.field.name])
|
||||
_clear.alters_data = True
|
||||
|
||||
def set(self, objs, **kwargs):
|
||||
# Force evaluation of `objs` in case it's a queryset whose value
|
||||
# could be affected by `manager.clear()`. Refs #19816.
|
||||
objs = tuple(objs)
|
||||
|
||||
bulk = kwargs.pop('bulk', True)
|
||||
clear = kwargs.pop('clear', False)
|
||||
|
||||
if self.field.null:
|
||||
db = router.db_for_write(self.model, instance=self.instance)
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
if clear:
|
||||
self.clear()
|
||||
self.add(*objs, bulk=bulk)
|
||||
else:
|
||||
old_objs = set(self.using(db).all())
|
||||
new_objs = []
|
||||
for obj in objs:
|
||||
if obj in old_objs:
|
||||
old_objs.remove(obj)
|
||||
else:
|
||||
new_objs.append(obj)
|
||||
|
||||
self.remove(*old_objs, bulk=bulk)
|
||||
self.add(*new_objs, bulk=bulk)
|
||||
else:
|
||||
self.add(*objs, bulk=bulk)
|
||||
set.alters_data = True
|
||||
|
||||
return RelatedManager
|
||||
|
||||
|
||||
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)
|
||||
|
||||
self.reverse = reverse
|
||||
|
||||
@property
|
||||
def through(self):
|
||||
# through is provided so that you have easy access to the through
|
||||
# model (Book.authors.through) for inlines, etc. This is done as
|
||||
# a property to ensure that the fully resolved value is returned.
|
||||
return self.rel.through
|
||||
|
||||
@cached_property
|
||||
def related_manager_cls(self):
|
||||
model = self.rel.related_model if self.reverse else self.rel.model
|
||||
return create_many_related_manager(
|
||||
model._default_manager.__class__,
|
||||
self.rel,
|
||||
reverse=self.reverse,
|
||||
)
|
||||
|
||||
|
||||
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.
|
||||
"""
|
||||
|
||||
class ManyRelatedManager(superclass):
|
||||
def __init__(self, instance=None):
|
||||
super(ManyRelatedManager, self).__init__()
|
||||
|
||||
self.instance = instance
|
||||
|
||||
if not reverse:
|
||||
self.model = rel.model
|
||||
self.query_field_name = rel.field.related_query_name()
|
||||
self.prefetch_cache_name = rel.field.name
|
||||
self.source_field_name = rel.field.m2m_field_name()
|
||||
self.target_field_name = rel.field.m2m_reverse_field_name()
|
||||
self.symmetrical = rel.symmetrical
|
||||
else:
|
||||
self.model = rel.related_model
|
||||
self.query_field_name = rel.field.name
|
||||
self.prefetch_cache_name = rel.field.related_query_name()
|
||||
self.source_field_name = rel.field.m2m_reverse_field_name()
|
||||
self.target_field_name = rel.field.m2m_field_name()
|
||||
self.symmetrical = False
|
||||
|
||||
self.through = rel.through
|
||||
self.reverse = reverse
|
||||
|
||||
self.source_field = self.through._meta.get_field(self.source_field_name)
|
||||
self.target_field = self.through._meta.get_field(self.target_field_name)
|
||||
|
||||
self.core_filters = {}
|
||||
for lh_field, rh_field in self.source_field.related_fields:
|
||||
core_filter_key = '%s__%s' % (self.query_field_name, rh_field.name)
|
||||
self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
|
||||
|
||||
self.related_val = self.source_field.get_foreign_related_value(instance)
|
||||
if None in self.related_val:
|
||||
raise ValueError('"%r" needs to have a value for field "%s" before '
|
||||
'this many-to-many relationship can be used.' %
|
||||
(instance, self.source_field_name))
|
||||
# Even if this relation is not to pk, we require still pk value.
|
||||
# The wish is that the instance has been already saved to DB,
|
||||
# although having a pk value isn't a guarantee of that.
|
||||
if instance.pk is None:
|
||||
raise ValueError("%r instance needs to have a primary key value before "
|
||||
"a many-to-many relationship can be used." %
|
||||
instance.__class__.__name__)
|
||||
|
||||
def __call__(self, **kwargs):
|
||||
# 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)
|
||||
return manager_class(instance=self.instance)
|
||||
do_not_call_in_templates = True
|
||||
|
||||
def _build_remove_filters(self, removed_vals):
|
||||
filters = Q(**{self.source_field_name: self.related_val})
|
||||
# No need to add a subquery condition if removed_vals is a QuerySet without
|
||||
# filters.
|
||||
removed_vals_filters = (not isinstance(removed_vals, QuerySet) or
|
||||
removed_vals._has_filters())
|
||||
if removed_vals_filters:
|
||||
filters &= Q(**{'%s__in' % self.target_field_name: removed_vals})
|
||||
if self.symmetrical:
|
||||
symmetrical_filters = Q(**{self.target_field_name: self.related_val})
|
||||
if removed_vals_filters:
|
||||
symmetrical_filters &= Q(
|
||||
**{'%s__in' % self.source_field_name: removed_vals})
|
||||
filters |= symmetrical_filters
|
||||
return filters
|
||||
|
||||
def get_queryset(self):
|
||||
try:
|
||||
return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
|
||||
except (AttributeError, KeyError):
|
||||
qs = super(ManyRelatedManager, self).get_queryset()
|
||||
qs._add_hints(instance=self.instance)
|
||||
if self._db:
|
||||
qs = qs.using(self._db)
|
||||
return qs._next_is_sticky().filter(**self.core_filters)
|
||||
|
||||
def get_prefetch_queryset(self, instances, queryset=None):
|
||||
if queryset is None:
|
||||
queryset = super(ManyRelatedManager, self).get_queryset()
|
||||
|
||||
queryset._add_hints(instance=instances[0])
|
||||
queryset = queryset.using(queryset._db or self._db)
|
||||
|
||||
query = {'%s__in' % self.query_field_name: instances}
|
||||
queryset = queryset._next_is_sticky().filter(**query)
|
||||
|
||||
# M2M: need to annotate the query in order to get the primary model
|
||||
# that the secondary model was actually related to. We know that
|
||||
# there will already be a join on the join table, so we can just add
|
||||
# the select.
|
||||
|
||||
# For non-autocreated 'through' models, can't assume we are
|
||||
# dealing with PK values.
|
||||
fk = self.through._meta.get_field(self.source_field_name)
|
||||
join_table = self.through._meta.db_table
|
||||
connection = connections[queryset.db]
|
||||
qn = connection.ops.quote_name
|
||||
queryset = queryset.extra(select={
|
||||
'_prefetch_related_val_%s' % f.attname:
|
||||
'%s.%s' % (qn(join_table), qn(f.column)) for f in fk.local_related_fields})
|
||||
return (
|
||||
queryset,
|
||||
lambda result: tuple(
|
||||
getattr(result, '_prefetch_related_val_%s' % f.attname)
|
||||
for f in fk.local_related_fields
|
||||
),
|
||||
lambda inst: tuple(
|
||||
f.get_db_prep_value(getattr(inst, f.attname), connection)
|
||||
for f in fk.foreign_related_fields
|
||||
),
|
||||
False,
|
||||
self.prefetch_cache_name,
|
||||
)
|
||||
|
||||
def add(self, *objs):
|
||||
if not rel.through._meta.auto_created:
|
||||
opts = self.through._meta
|
||||
raise AttributeError(
|
||||
"Cannot use add() on a ManyToManyField which specifies an "
|
||||
"intermediary model. Use %s.%s's Manager instead." %
|
||||
(opts.app_label, opts.object_name)
|
||||
)
|
||||
|
||||
db = router.db_for_write(self.through, instance=self.instance)
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
self._add_items(self.source_field_name, self.target_field_name, *objs)
|
||||
|
||||
# If this is a symmetrical m2m relation to self, add the mirror entry in the m2m table
|
||||
if self.symmetrical:
|
||||
self._add_items(self.target_field_name, self.source_field_name, *objs)
|
||||
add.alters_data = True
|
||||
|
||||
def remove(self, *objs):
|
||||
if not rel.through._meta.auto_created:
|
||||
opts = self.through._meta
|
||||
raise AttributeError(
|
||||
"Cannot use remove() on a ManyToManyField which specifies "
|
||||
"an intermediary model. Use %s.%s's Manager instead." %
|
||||
(opts.app_label, opts.object_name)
|
||||
)
|
||||
self._remove_items(self.source_field_name, self.target_field_name, *objs)
|
||||
remove.alters_data = True
|
||||
|
||||
def clear(self):
|
||||
db = router.db_for_write(self.through, instance=self.instance)
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
signals.m2m_changed.send(sender=self.through, action="pre_clear",
|
||||
instance=self.instance, reverse=self.reverse,
|
||||
model=self.model, pk_set=None, using=db)
|
||||
|
||||
filters = self._build_remove_filters(super(ManyRelatedManager, self).get_queryset().using(db))
|
||||
self.through._default_manager.using(db).filter(filters).delete()
|
||||
|
||||
signals.m2m_changed.send(sender=self.through, action="post_clear",
|
||||
instance=self.instance, reverse=self.reverse,
|
||||
model=self.model, pk_set=None, using=db)
|
||||
clear.alters_data = True
|
||||
|
||||
def set(self, objs, **kwargs):
|
||||
if not rel.through._meta.auto_created:
|
||||
opts = self.through._meta
|
||||
raise AttributeError(
|
||||
"Cannot set values on a ManyToManyField which specifies an "
|
||||
"intermediary model. Use %s.%s's Manager instead." %
|
||||
(opts.app_label, opts.object_name)
|
||||
)
|
||||
|
||||
# Force evaluation of `objs` in case it's a queryset whose value
|
||||
# could be affected by `manager.clear()`. Refs #19816.
|
||||
objs = tuple(objs)
|
||||
|
||||
clear = kwargs.pop('clear', False)
|
||||
|
||||
db = router.db_for_write(self.through, instance=self.instance)
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
if clear:
|
||||
self.clear()
|
||||
self.add(*objs)
|
||||
else:
|
||||
old_ids = set(self.using(db).values_list(self.target_field.target_field.attname, flat=True))
|
||||
|
||||
new_objs = []
|
||||
for obj in objs:
|
||||
fk_val = (self.target_field.get_foreign_related_value(obj)[0]
|
||||
if isinstance(obj, self.model) else obj)
|
||||
|
||||
if fk_val in old_ids:
|
||||
old_ids.remove(fk_val)
|
||||
else:
|
||||
new_objs.append(obj)
|
||||
|
||||
self.remove(*old_ids)
|
||||
self.add(*new_objs)
|
||||
set.alters_data = True
|
||||
|
||||
def create(self, **kwargs):
|
||||
# This check needs to be done here, since we can't later remove this
|
||||
# from the method lookup table, as we do with add and remove.
|
||||
if not self.through._meta.auto_created:
|
||||
opts = self.through._meta
|
||||
raise AttributeError(
|
||||
"Cannot use create() on a ManyToManyField which specifies "
|
||||
"an intermediary model. Use %s.%s's Manager instead." %
|
||||
(opts.app_label, opts.object_name)
|
||||
)
|
||||
db = router.db_for_write(self.instance.__class__, instance=self.instance)
|
||||
new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs)
|
||||
self.add(new_obj)
|
||||
return new_obj
|
||||
create.alters_data = True
|
||||
|
||||
def get_or_create(self, **kwargs):
|
||||
db = router.db_for_write(self.instance.__class__, instance=self.instance)
|
||||
obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
|
||||
# We only need to add() if created because if we got an object back
|
||||
# from get() then the relationship already exists.
|
||||
if created:
|
||||
self.add(obj)
|
||||
return obj, created
|
||||
get_or_create.alters_data = True
|
||||
|
||||
def update_or_create(self, **kwargs):
|
||||
db = router.db_for_write(self.instance.__class__, instance=self.instance)
|
||||
obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs)
|
||||
# We only need to add() if created because if we got an object back
|
||||
# from get() then the relationship already exists.
|
||||
if created:
|
||||
self.add(obj)
|
||||
return obj, created
|
||||
update_or_create.alters_data = True
|
||||
|
||||
def _add_items(self, source_field_name, target_field_name, *objs):
|
||||
# source_field_name: the PK fieldname in join table for the source object
|
||||
# target_field_name: the PK fieldname in join table for the target object
|
||||
# *objs - objects to add. Either object instances, or primary keys of object instances.
|
||||
|
||||
# If there aren't any objects, there is nothing to do.
|
||||
from django.db.models import Model
|
||||
if objs:
|
||||
new_ids = set()
|
||||
for obj in objs:
|
||||
if isinstance(obj, self.model):
|
||||
if not router.allow_relation(obj, self.instance):
|
||||
raise ValueError(
|
||||
'Cannot add "%r": instance is on database "%s", value is on database "%s"' %
|
||||
(obj, self.instance._state.db, obj._state.db)
|
||||
)
|
||||
fk_val = self.through._meta.get_field(
|
||||
target_field_name).get_foreign_related_value(obj)[0]
|
||||
if fk_val is None:
|
||||
raise ValueError(
|
||||
'Cannot add "%r": the value for field "%s" is None' %
|
||||
(obj, target_field_name)
|
||||
)
|
||||
new_ids.add(fk_val)
|
||||
elif isinstance(obj, Model):
|
||||
raise TypeError(
|
||||
"'%s' instance expected, got %r" %
|
||||
(self.model._meta.object_name, obj)
|
||||
)
|
||||
else:
|
||||
new_ids.add(obj)
|
||||
|
||||
db = router.db_for_write(self.through, instance=self.instance)
|
||||
vals = (self.through._default_manager.using(db)
|
||||
.values_list(target_field_name, flat=True)
|
||||
.filter(**{
|
||||
source_field_name: self.related_val[0],
|
||||
'%s__in' % target_field_name: new_ids,
|
||||
}))
|
||||
new_ids = new_ids - set(vals)
|
||||
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
if self.reverse or source_field_name == self.source_field_name:
|
||||
# Don't send the signal when we are inserting the
|
||||
# duplicate data row for symmetrical reverse entries.
|
||||
signals.m2m_changed.send(sender=self.through, action='pre_add',
|
||||
instance=self.instance, reverse=self.reverse,
|
||||
model=self.model, pk_set=new_ids, using=db)
|
||||
|
||||
# Add the ones that aren't there already
|
||||
self.through._default_manager.using(db).bulk_create([
|
||||
self.through(**{
|
||||
'%s_id' % source_field_name: self.related_val[0],
|
||||
'%s_id' % target_field_name: obj_id,
|
||||
})
|
||||
for obj_id in new_ids
|
||||
])
|
||||
|
||||
if self.reverse or source_field_name == self.source_field_name:
|
||||
# Don't send the signal when we are inserting the
|
||||
# duplicate data row for symmetrical reverse entries.
|
||||
signals.m2m_changed.send(sender=self.through, action='post_add',
|
||||
instance=self.instance, reverse=self.reverse,
|
||||
model=self.model, pk_set=new_ids, using=db)
|
||||
|
||||
def _remove_items(self, source_field_name, target_field_name, *objs):
|
||||
# source_field_name: the PK colname in join table for the source object
|
||||
# target_field_name: the PK colname in join table for the target object
|
||||
# *objs - objects to remove
|
||||
if not objs:
|
||||
return
|
||||
|
||||
# Check that all the objects are of the right type
|
||||
old_ids = set()
|
||||
for obj in objs:
|
||||
if isinstance(obj, self.model):
|
||||
fk_val = self.target_field.get_foreign_related_value(obj)[0]
|
||||
old_ids.add(fk_val)
|
||||
else:
|
||||
old_ids.add(obj)
|
||||
|
||||
db = router.db_for_write(self.through, instance=self.instance)
|
||||
with transaction.atomic(using=db, savepoint=False):
|
||||
# Send a signal to the other end if need be.
|
||||
signals.m2m_changed.send(sender=self.through, action="pre_remove",
|
||||
instance=self.instance, reverse=self.reverse,
|
||||
model=self.model, pk_set=old_ids, using=db)
|
||||
target_model_qs = super(ManyRelatedManager, self).get_queryset()
|
||||
if target_model_qs._has_filters():
|
||||
old_vals = target_model_qs.using(db).filter(**{
|
||||
'%s__in' % self.target_field.target_field.attname: old_ids})
|
||||
else:
|
||||
old_vals = old_ids
|
||||
filters = self._build_remove_filters(old_vals)
|
||||
self.through._default_manager.using(db).filter(filters).delete()
|
||||
|
||||
signals.m2m_changed.send(sender=self.through, action="post_remove",
|
||||
instance=self.instance, reverse=self.reverse,
|
||||
model=self.model, pk_set=old_ids, using=db)
|
||||
|
||||
return ManyRelatedManager
|
|
@ -0,0 +1,304 @@
|
|||
from __future__ import unicode_literals
|
||||
|
||||
import warnings
|
||||
|
||||
from django.core import exceptions
|
||||
from django.utils.deprecation import RemovedInDjango20Warning
|
||||
from django.utils.encoding import smart_text
|
||||
from django.utils.functional import cached_property
|
||||
|
||||
from . import BLANK_CHOICE_DASH
|
||||
|
||||
|
||||
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
|
||||
concrete = False
|
||||
editable = False
|
||||
is_relation = True
|
||||
|
||||
# Reverse relations are always nullable (Django can't enforce that a
|
||||
# foreign key on the related model points to this model).
|
||||
null = True
|
||||
|
||||
def __init__(self, field, to, related_name=None, related_query_name=None,
|
||||
limit_choices_to=None, parent_link=False, on_delete=None):
|
||||
self.field = field
|
||||
self.model = to
|
||||
self.related_name = related_name
|
||||
self.related_query_name = related_query_name
|
||||
self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
|
||||
self.parent_link = parent_link
|
||||
self.on_delete = on_delete
|
||||
|
||||
self.symmetrical = False
|
||||
self.multiple = True
|
||||
|
||||
# Some of the following cached_properties can't be initialized in
|
||||
# __init__ as the field doesn't have its model yet. Calling these methods
|
||||
# before field.contribute_to_class() has been called will result in
|
||||
# AttributeError
|
||||
@property
|
||||
def to(self):
|
||||
warnings.warn(
|
||||
"Usage of ForeignObjectRel.to attribute has been deprecated. "
|
||||
"Use the model attribute instead.",
|
||||
RemovedInDjango20Warning, 2)
|
||||
return self.model
|
||||
|
||||
@cached_property
|
||||
def hidden(self):
|
||||
return self.is_hidden()
|
||||
|
||||
@cached_property
|
||||
def name(self):
|
||||
return self.field.related_query_name()
|
||||
|
||||
@property
|
||||
def remote_field(self):
|
||||
return self.field
|
||||
|
||||
@property
|
||||
def target_field(self):
|
||||
"""
|
||||
When filtering against this relation, returns the field on the remote
|
||||
model against which the filtering should happen.
|
||||
"""
|
||||
target_fields = self.get_path_info()[-1].target_fields
|
||||
if len(target_fields) > 1:
|
||||
raise exceptions.FieldError("Can't use target_field for multicolumn relations.")
|
||||
return target_fields[0]
|
||||
|
||||
@cached_property
|
||||
def related_model(self):
|
||||
if not self.field.model:
|
||||
raise AttributeError(
|
||||
"This property can't be accessed before self.field.contribute_to_class has been called.")
|
||||
return self.field.model
|
||||
|
||||
@cached_property
|
||||
def many_to_many(self):
|
||||
return self.field.many_to_many
|
||||
|
||||
@cached_property
|
||||
def many_to_one(self):
|
||||
return self.field.one_to_many
|
||||
|
||||
@cached_property
|
||||
def one_to_many(self):
|
||||
return self.field.many_to_one
|
||||
|
||||
@cached_property
|
||||
def one_to_one(self):
|
||||
return self.field.one_to_one
|
||||
|
||||
def get_prep_lookup(self, lookup_name, value):
|
||||
return self.field.get_prep_lookup(lookup_name, value)
|
||||
|
||||
def get_lookup(self, lookup_name):
|
||||
return self.field.get_lookup(lookup_name)
|
||||
|
||||
def get_internal_type(self):
|
||||
return self.field.get_internal_type()
|
||||
|
||||
@property
|
||||
def db_type(self):
|
||||
return self.field.db_type
|
||||
|
||||
def __repr__(self):
|
||||
return '<%s: %s.%s>' % (
|
||||
type(self).__name__,
|
||||
self.related_model._meta.app_label,
|
||||
self.related_model._meta.model_name,
|
||||
)
|
||||
|
||||
def get_choices(self, include_blank=True, blank_choice=BLANK_CHOICE_DASH,
|
||||
limit_to_currently_related=False):
|
||||
"""
|
||||
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
|
||||
initially for utilization by RelatedFieldListFilter.
|
||||
"""
|
||||
first_choice = blank_choice if include_blank else []
|
||||
queryset = self.related_model._default_manager.all()
|
||||
if limit_to_currently_related:
|
||||
queryset = queryset.complex_filter(
|
||||
{'%s__isnull' % self.related_model._meta.model_name: False}
|
||||
)
|
||||
lst = [(x._get_pk_val(), smart_text(x)) for x in queryset]
|
||||
return first_choice + lst
|
||||
|
||||
def get_db_prep_lookup(self, lookup_type, value, connection, prepared=False):
|
||||
# Defer to the actual field definition for db prep
|
||||
return self.field.get_db_prep_lookup(lookup_type, value, connection=connection, prepared=prepared)
|
||||
|
||||
def is_hidden(self):
|
||||
"Should the related object be hidden?"
|
||||
return self.related_name is not None and self.related_name[-1] == '+'
|
||||
|
||||
def get_joining_columns(self):
|
||||
return self.field.get_reverse_joining_columns()
|
||||
|
||||
def get_extra_restriction(self, where_class, alias, related_alias):
|
||||
return self.field.get_extra_restriction(where_class, related_alias, alias)
|
||||
|
||||
def set_field_name(self):
|
||||
"""
|
||||
Set the related field's name, this is not available until later stages
|
||||
of app loading, so set_field_name is called from
|
||||
set_attributes_from_rel()
|
||||
"""
|
||||
# By default foreign object doesn't relate to any remote field (for
|
||||
# example custom multicolumn joins currently have no remote field).
|
||||
self.field_name = None
|
||||
|
||||
def get_accessor_name(self, model=None):
|
||||
# This method encapsulates the logic that decides what name to give an
|
||||
# accessor descriptor that retrieves related many-to-one or
|
||||
# many-to-many objects. It uses the lower-cased object_name + "_set",
|
||||
# but this can be overridden with the "related_name" option.
|
||||
# Due to backwards compatibility ModelForms need to be able to provide
|
||||
# an alternate model. See BaseInlineFormSet.get_default_prefix().
|
||||
opts = model._meta if model else self.related_model._meta
|
||||
model = model or self.related_model
|
||||
if self.multiple:
|
||||
# If this is a symmetrical m2m relation on self, there is no reverse accessor.
|
||||
if self.symmetrical and model == self.model:
|
||||
return None
|
||||
if self.related_name:
|
||||
return self.related_name
|
||||
if opts.default_related_name:
|
||||
return opts.default_related_name % {
|
||||
'model_name': opts.model_name.lower(),
|
||||
'app_label': opts.app_label.lower(),
|
||||
}
|
||||
return opts.model_name + ('_set' if self.multiple else '')
|
||||
|
||||
def get_cache_name(self):
|
||||
return "_%s_cache" % self.get_accessor_name()
|
||||
|
||||
def get_path_info(self):
|
||||
return self.field.get_reverse_path_info()
|
||||
|
||||
|
||||
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__(
|
||||
field, to,
|
||||
related_name=related_name,
|
||||
related_query_name=related_query_name,
|
||||
limit_choices_to=limit_choices_to,
|
||||
parent_link=parent_link,
|
||||
on_delete=on_delete,
|
||||
)
|
||||
|
||||
self.field_name = field_name
|
||||
|
||||
def __getstate__(self):
|
||||
state = self.__dict__.copy()
|
||||
state.pop('related_model', None)
|
||||
return state
|
||||
|
||||
def get_related_field(self):
|
||||
"""
|
||||
Return the Field in the 'to' object to which this relationship is tied.
|
||||
"""
|
||||
field = self.model._meta.get_field(self.field_name)
|
||||
if not field.concrete:
|
||||
raise exceptions.FieldDoesNotExist("No related field named '%s'" %
|
||||
self.field_name)
|
||||
return field
|
||||
|
||||
def set_field_name(self):
|
||||
self.field_name = self.field_name or self.model._meta.pk.name
|
||||
|
||||
|
||||
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__(
|
||||
field, to, field_name,
|
||||
related_name=related_name,
|
||||
related_query_name=related_query_name,
|
||||
limit_choices_to=limit_choices_to,
|
||||
parent_link=parent_link,
|
||||
on_delete=on_delete,
|
||||
)
|
||||
|
||||
self.multiple = False
|
||||
|
||||
|
||||
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):
|
||||
super(ManyToManyRel, self).__init__(
|
||||
field, to,
|
||||
related_name=related_name,
|
||||
related_query_name=related_query_name,
|
||||
limit_choices_to=limit_choices_to,
|
||||
)
|
||||
|
||||
if through and not db_constraint:
|
||||
raise ValueError("Can't supply a through model and db_constraint=False")
|
||||
self.through = through
|
||||
|
||||
if through_fields and not through:
|
||||
raise ValueError("Cannot specify through_fields without a through model")
|
||||
self.through_fields = through_fields
|
||||
|
||||
self.symmetrical = symmetrical
|
||||
self.db_constraint = db_constraint
|
||||
|
||||
def get_related_field(self):
|
||||
"""
|
||||
Return the field in the 'to' object to which this relationship is tied.
|
||||
Provided for symmetry with ManyToOneRel.
|
||||
"""
|
||||
opts = self.through._meta
|
||||
if self.through_fields:
|
||||
field = opts.get_field(self.through_fields[0])
|
||||
else:
|
||||
for field in opts.fields:
|
||||
rel = getattr(field, 'remote_field', None)
|
||||
if rel and rel.model == self.model:
|
||||
break
|
||||
return field.foreign_related_fields[0]
|
Loading…
Reference in New Issue