summaryrefslogtreecommitdiff
path: root/django/db/models/fields/related_descriptors.py
diff options
context:
space:
mode:
Diffstat (limited to 'django/db/models/fields/related_descriptors.py')
-rw-r--r--django/db/models/fields/related_descriptors.py425
1 files changed, 294 insertions, 131 deletions
diff --git a/django/db/models/fields/related_descriptors.py b/django/db/models/fields/related_descriptors.py
index 9c50ef16ce..3f67ed8166 100644
--- a/django/db/models/fields/related_descriptors.py
+++ b/django/db/models/fields/related_descriptors.py
@@ -74,7 +74,9 @@ from django.utils.functional import cached_property
class ForeignKeyDeferredAttribute(DeferredAttribute):
def __set__(self, instance, value):
- if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(instance):
+ if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(
+ instance
+ ):
self.field.delete_cached_value(instance)
instance.__dict__[self.field.attname] = value
@@ -101,14 +103,16 @@ class ForwardManyToOneDescriptor:
# related model might not be resolved yet; `self.field.model` might
# still be a string model reference.
return type(
- 'RelatedObjectDoesNotExist',
- (self.field.remote_field.model.DoesNotExist, AttributeError), {
- '__module__': self.field.model.__module__,
- '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % (
+ "RelatedObjectDoesNotExist",
+ (self.field.remote_field.model.DoesNotExist, AttributeError),
+ {
+ "__module__": self.field.model.__module__,
+ "__qualname__": "%s.%s.RelatedObjectDoesNotExist"
+ % (
self.field.model.__qualname__,
self.field.name,
),
- }
+ },
)
def is_cached(self, instance):
@@ -135,9 +139,12 @@ class ForwardManyToOneDescriptor:
# The check for len(...) == 1 is a special case that allows the query
# to be join-less and smaller. Refs #21760.
if remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1:
- query = {'%s__in' % related_field.name: {instance_attr(inst)[0] for inst in instances}}
+ query = {
+ "%s__in"
+ % related_field.name: {instance_attr(inst)[0] for inst in instances}
+ }
else:
- query = {'%s__in' % self.field.related_query_name(): instances}
+ query = {"%s__in" % self.field.related_query_name(): instances}
queryset = queryset.filter(**query)
# Since we're going to assign directly in the cache,
@@ -146,7 +153,14 @@ class ForwardManyToOneDescriptor:
for rel_obj in queryset:
instance = instances_dict[rel_obj_attr(rel_obj)]
remote_field.set_cached_value(rel_obj, instance)
- return queryset, rel_obj_attr, instance_attr, True, self.field.get_cache_name(), False
+ return (
+ queryset,
+ rel_obj_attr,
+ instance_attr,
+ True,
+ self.field.get_cache_name(),
+ False,
+ )
def get_object(self, instance):
qs = self.get_queryset(instance=instance)
@@ -173,7 +187,11 @@ class ForwardManyToOneDescriptor:
rel_obj = self.field.get_cached_value(instance)
except KeyError:
has_value = None not in self.field.get_local_related_value(instance)
- ancestor_link = instance._meta.get_ancestor_link(self.field.model) if has_value else None
+ ancestor_link = (
+ instance._meta.get_ancestor_link(self.field.model)
+ if has_value
+ else None
+ )
if ancestor_link and ancestor_link.is_cached(instance):
# An ancestor link will exist if this field is defined on a
# multi-table inheritance parent of the instance's class.
@@ -211,9 +229,12 @@ class ForwardManyToOneDescriptor:
- ``value`` is the ``parent`` instance on the right of the equal sign
"""
# An object must be an instance of the related class.
- if value is not None and not isinstance(value, self.field.remote_field.model._meta.concrete_model):
+ if value is not None and not isinstance(
+ value, self.field.remote_field.model._meta.concrete_model
+ ):
raise ValueError(
- 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
+ 'Cannot assign "%r": "%s.%s" must be a "%s" instance.'
+ % (
value,
instance._meta.object_name,
self.field.name,
@@ -222,11 +243,18 @@ class ForwardManyToOneDescriptor:
)
elif value is not None:
if instance._state.db is None:
- instance._state.db = router.db_for_write(instance.__class__, instance=value)
+ instance._state.db = router.db_for_write(
+ instance.__class__, instance=value
+ )
if value._state.db is None:
- value._state.db = router.db_for_write(value.__class__, instance=instance)
+ value._state.db = router.db_for_write(
+ value.__class__, instance=instance
+ )
if not router.allow_relation(value, instance):
- raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
+ raise ValueError(
+ 'Cannot assign "%r": the current database router prevents this relation.'
+ % value
+ )
remote_field = self.field.remote_field
# If we're setting the value of a OneToOneField to None, we need to clear
@@ -314,12 +342,15 @@ class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor):
opts = instance._meta
# Inherited primary key fields from this object's base classes.
inherited_pk_fields = [
- field for field in opts.concrete_fields
+ field
+ for field in opts.concrete_fields
if field.primary_key and field.remote_field
]
for field in inherited_pk_fields:
rel_model_pk_name = field.remote_field.model._meta.pk.attname
- raw_value = getattr(value, rel_model_pk_name) if value is not None else None
+ raw_value = (
+ getattr(value, rel_model_pk_name) if value is not None else None
+ )
setattr(instance, rel_model_pk_name, raw_value)
@@ -346,13 +377,15 @@ class ReverseOneToOneDescriptor:
# The exception isn't created at initialization time for the sake of
# consistency with `ForwardManyToOneDescriptor`.
return type(
- 'RelatedObjectDoesNotExist',
- (self.related.related_model.DoesNotExist, AttributeError), {
- '__module__': self.related.model.__module__,
- '__qualname__': '%s.%s.RelatedObjectDoesNotExist' % (
+ "RelatedObjectDoesNotExist",
+ (self.related.related_model.DoesNotExist, AttributeError),
+ {
+ "__module__": self.related.model.__module__,
+ "__qualname__": "%s.%s.RelatedObjectDoesNotExist"
+ % (
self.related.model.__qualname__,
self.related.name,
- )
+ ),
},
)
@@ -370,7 +403,7 @@ class ReverseOneToOneDescriptor:
rel_obj_attr = self.related.field.get_local_related_value
instance_attr = self.related.field.get_foreign_related_value
instances_dict = {instance_attr(inst): inst for inst in instances}
- query = {'%s__in' % self.related.field.name: instances}
+ query = {"%s__in" % self.related.field.name: instances}
queryset = queryset.filter(**query)
# Since we're going to assign directly in the cache,
@@ -378,7 +411,14 @@ class ReverseOneToOneDescriptor:
for rel_obj in queryset:
instance = instances_dict[rel_obj_attr(rel_obj)]
self.related.field.set_cached_value(rel_obj, instance)
- return queryset, rel_obj_attr, instance_attr, True, self.related.get_cache_name(), False
+ return (
+ queryset,
+ rel_obj_attr,
+ instance_attr,
+ True,
+ self.related.get_cache_name(),
+ False,
+ )
def __get__(self, instance, cls=None):
"""
@@ -419,10 +459,8 @@ class ReverseOneToOneDescriptor:
if rel_obj is None:
raise self.RelatedObjectDoesNotExist(
- "%s has no %s." % (
- instance.__class__.__name__,
- self.related.get_accessor_name()
- )
+ "%s has no %s."
+ % (instance.__class__.__name__, self.related.get_accessor_name())
)
else:
return rel_obj
@@ -458,7 +496,8 @@ class ReverseOneToOneDescriptor:
elif not isinstance(value, self.related.related_model):
# An object must be an instance of the related class.
raise ValueError(
- 'Cannot assign "%r": "%s.%s" must be a "%s" instance.' % (
+ 'Cannot assign "%r": "%s.%s" must be a "%s" instance.'
+ % (
value,
instance._meta.object_name,
self.related.get_accessor_name(),
@@ -467,13 +506,23 @@ class ReverseOneToOneDescriptor:
)
else:
if instance._state.db is None:
- instance._state.db = router.db_for_write(instance.__class__, instance=value)
+ instance._state.db = router.db_for_write(
+ instance.__class__, instance=value
+ )
if value._state.db is None:
- value._state.db = router.db_for_write(value.__class__, instance=instance)
+ value._state.db = router.db_for_write(
+ value.__class__, instance=instance
+ )
if not router.allow_relation(value, instance):
- raise ValueError('Cannot assign "%r": the current database router prevents this relation.' % value)
+ 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)
+ 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])
@@ -548,13 +597,13 @@ class ReverseManyToOneDescriptor:
def _get_set_deprecation_msg_params(self):
return (
- 'reverse side of a related set',
+ "reverse side of a related set",
self.rel.get_accessor_name(),
)
def __set__(self, instance, value):
raise TypeError(
- 'Direct assignment to the %s is prohibited. Use %s.set() instead.'
+ "Direct assignment to the %s is prohibited. Use %s.set() instead."
% self._get_set_deprecation_msg_params(),
)
@@ -581,6 +630,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
manager = getattr(self.model, manager)
manager_class = create_reverse_many_to_one_manager(manager.__class__, rel)
return manager_class(self.instance)
+
do_not_call_in_templates = True
def _apply_rel_filters(self, queryset):
@@ -588,7 +638,9 @@ def create_reverse_many_to_one_manager(superclass, rel):
Filter the queryset for the instance this manager is bound to.
"""
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
+ empty_strings_as_null = connections[
+ db
+ ].features.interprets_empty_strings_as_nulls
queryset._add_hints(instance=self.instance)
if self._db:
queryset = queryset.using(self._db)
@@ -596,7 +648,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
queryset = queryset.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):
+ if val is None or (val == "" and empty_strings_as_null):
return queryset.none()
if self.field.many_to_one:
# Guard against field-like objects such as GenericRelation
@@ -608,24 +660,32 @@ def create_reverse_many_to_one_manager(superclass, rel):
except FieldError:
# The relationship has multiple target fields. Use a tuple
# for related object id.
- rel_obj_id = tuple([
- getattr(self.instance, target_field.attname)
- for target_field in self.field.path_infos[-1].target_fields
- ])
+ rel_obj_id = tuple(
+ [
+ getattr(self.instance, target_field.attname)
+ for target_field in self.field.path_infos[-1].target_fields
+ ]
+ )
else:
rel_obj_id = getattr(self.instance, target_field.attname)
- queryset._known_related_objects = {self.field: {rel_obj_id: self.instance}}
+ queryset._known_related_objects = {
+ self.field: {rel_obj_id: self.instance}
+ }
return queryset
def _remove_prefetched_objects(self):
try:
- self.instance._prefetched_objects_cache.pop(self.field.remote_field.get_cache_name())
+ self.instance._prefetched_objects_cache.pop(
+ self.field.remote_field.get_cache_name()
+ )
except (AttributeError, KeyError):
pass # nothing to clear from cache
def get_queryset(self):
try:
- return self.instance._prefetched_objects_cache[self.field.remote_field.get_cache_name()]
+ return self.instance._prefetched_objects_cache[
+ self.field.remote_field.get_cache_name()
+ ]
except (AttributeError, KeyError):
queryset = super().get_queryset()
return self._apply_rel_filters(queryset)
@@ -640,7 +700,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
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}
+ query = {"%s__in" % self.field.name: instances}
queryset = queryset.filter(**query)
# Since we just bypassed this class' get_queryset(), we must manage
@@ -658,9 +718,13 @@ def create_reverse_many_to_one_manager(superclass, rel):
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,
- ))
+ raise TypeError(
+ "'%s' instance expected, got %r"
+ % (
+ self.model._meta.object_name,
+ obj,
+ )
+ )
setattr(obj, self.field.name, self.instance)
if bulk:
@@ -673,36 +737,43 @@ def create_reverse_many_to_one_manager(superclass, rel):
"the object first." % obj
)
pks.append(obj.pk)
- self.model._base_manager.using(db).filter(pk__in=pks).update(**{
- self.field.name: self.instance,
- })
+ 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, bulk=True):
if not objs:
return
@@ -710,9 +781,13 @@ def create_reverse_many_to_one_manager(superclass, rel):
old_ids = set()
for obj in objs:
if not isinstance(obj, self.model):
- raise TypeError("'%s' instance expected, got %r" % (
- self.model._meta.object_name, obj,
- ))
+ raise TypeError(
+ "'%s' instance expected, got %r"
+ % (
+ self.model._meta.object_name,
+ obj,
+ )
+ )
# Is obj actually part of this descriptor set?
if self.field.get_local_related_value(obj) == val:
old_ids.add(obj.pk)
@@ -721,10 +796,12 @@ def create_reverse_many_to_one_manager(superclass, rel):
"%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, *, bulk=True):
self._clear(self, bulk)
+
clear.alters_data = True
def _clear(self, queryset, bulk):
@@ -739,6 +816,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
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, *, bulk=True, clear=False):
@@ -765,6 +843,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
self.add(*new_objs, bulk=bulk)
else:
self.add(*objs, bulk=bulk)
+
set.alters_data = True
return RelatedManager
@@ -822,7 +901,8 @@ class ManyToManyDescriptor(ReverseManyToOneDescriptor):
def _get_set_deprecation_msg_params(self):
return (
- '%s side of a many-to-many set' % ('reverse' if self.reverse else 'forward'),
+ "%s side of a many-to-many set"
+ % ("reverse" if self.reverse else "forward"),
self.rel.get_accessor_name() if self.reverse else self.field.name,
)
@@ -865,41 +945,51 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
self.core_filters = {}
self.pk_field_names = {}
for lh_field, rh_field in self.source_field.related_fields:
- core_filter_key = '%s__%s' % (self.query_field_name, rh_field.name)
+ core_filter_key = "%s__%s" % (self.query_field_name, rh_field.name)
self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
self.pk_field_names[lh_field.name] = rh_field.name
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.pk_field_names[self.source_field_name]))
+ raise ValueError(
+ '"%r" needs to have a value for field "%s" before '
+ "this many-to-many relationship can be used."
+ % (instance, self.pk_field_names[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__)
+ 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, *, manager):
manager = getattr(self.model, manager)
- manager_class = create_forward_many_to_many_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
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())
+ removed_vals_filters = (
+ not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
+ )
if removed_vals_filters:
- filters &= Q((f'{self.target_field_name}__in', removed_vals))
+ filters &= Q((f"{self.target_field_name}__in", removed_vals))
if self.symmetrical:
symmetrical_filters = Q((self.target_field_name, self.related_val))
if removed_vals_filters:
- symmetrical_filters &= Q((f'{self.source_field_name}__in', removed_vals))
+ symmetrical_filters &= Q(
+ (f"{self.source_field_name}__in", removed_vals)
+ )
filters |= symmetrical_filters
return filters
@@ -933,7 +1023,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
queryset._add_hints(instance=instances[0])
queryset = queryset.using(queryset._db or self._db)
- query = {'%s__in' % self.query_field_name: instances}
+ 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
@@ -947,13 +1037,18 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
join_table = fk.model._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})
+ 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)
+ getattr(result, "_prefetch_related_val_%s" % f.attname)
for f in fk.local_related_fields
),
lambda inst: tuple(
@@ -970,7 +1065,9 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
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,
+ self.source_field_name,
+ self.target_field_name,
+ *objs,
through_defaults=through_defaults,
)
# If this is a symmetrical m2m relation to self, add the mirror
@@ -982,30 +1079,41 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
*objs,
through_defaults=through_defaults,
)
+
add.alters_data = True
def remove(self, *objs):
self._remove_prefetched_objects()
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,
+ sender=self.through,
+ action="pre_clear",
+ instance=self.instance,
+ reverse=self.reverse,
+ model=self.model,
+ pk_set=None,
+ using=db,
)
self._remove_prefetched_objects()
filters = self._build_remove_filters(super().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,
+ 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, *, clear=False, through_defaults=None):
@@ -1019,7 +1127,11 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
self.clear()
self.add(*objs, through_defaults=through_defaults)
else:
- old_ids = set(self.using(db).values_list(self.target_field.target_field.attname, flat=True))
+ old_ids = set(
+ self.using(db).values_list(
+ self.target_field.target_field.attname, flat=True
+ )
+ )
new_objs = []
for obj in objs:
@@ -1035,6 +1147,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
self.remove(*old_ids)
self.add(*new_objs, through_defaults=through_defaults)
+
set.alters_data = True
def create(self, *, through_defaults=None, **kwargs):
@@ -1042,26 +1155,33 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs)
self.add(new_obj, through_defaults=through_defaults)
return new_obj
+
create.alters_data = True
def get_or_create(self, *, through_defaults=None, **kwargs):
db = router.db_for_write(self.instance.__class__, instance=self.instance)
- obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(**kwargs)
+ 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, through_defaults=through_defaults)
return obj, created
+
get_or_create.alters_data = True
def update_or_create(self, *, through_defaults=None, **kwargs):
db = router.db_for_write(self.instance.__class__, instance=self.instance)
- obj, created = super(ManyRelatedManager, self.db_manager(db)).update_or_create(**kwargs)
+ 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, through_defaults=through_defaults)
return obj, created
+
update_or_create.alters_data = True
def _get_target_ids(self, target_field_name, objs):
@@ -1069,6 +1189,7 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
Return the set of ids of `objs` that the target field references.
"""
from django.db.models import Model
+
target_ids = set()
target_field = self.through._meta.get_field(target_field_name)
for obj in objs:
@@ -1076,36 +1197,42 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
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)
+ 'value is on database "%s"'
+ % (obj, self.instance._state.db, obj._state.db)
)
target_id = target_field.get_foreign_related_value(obj)[0]
if target_id is None:
raise ValueError(
- 'Cannot add "%r": the value for field "%s" is None' %
- (obj, target_field_name)
+ 'Cannot add "%r": the value for field "%s" is None'
+ % (obj, target_field_name)
)
target_ids.add(target_id)
elif isinstance(obj, Model):
raise TypeError(
- "'%s' instance expected, got %r" %
- (self.model._meta.object_name, obj)
+ "'%s' instance expected, got %r"
+ % (self.model._meta.object_name, obj)
)
else:
target_ids.add(target_field.get_prep_value(obj))
return target_ids
- def _get_missing_target_ids(self, source_field_name, target_field_name, db, target_ids):
+ def _get_missing_target_ids(
+ self, source_field_name, target_field_name, db, target_ids
+ ):
"""
Return the subset of ids of `objs` that aren't already assigned to
this relationship.
"""
- 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: target_ids,
- })
+ 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: target_ids,
+ }
+ )
+ )
return target_ids.difference(vals)
def _get_add_plan(self, db, source_field_name):
@@ -1123,21 +1250,27 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
# user-defined intermediary models as they could have other fields
# causing conflicts which must be surfaced.
can_ignore_conflicts = (
- self.through._meta.auto_created is not False and
- connections[db].features.supports_ignore_conflicts
+ self.through._meta.auto_created is not False
+ and connections[db].features.supports_ignore_conflicts
)
# Don't send the signal when inserting duplicate data row
# for symmetrical reverse entries.
- must_send_signals = (self.reverse or source_field_name == self.source_field_name) and (
- signals.m2m_changed.has_listeners(self.through)
- )
+ must_send_signals = (
+ self.reverse or source_field_name == self.source_field_name
+ ) and (signals.m2m_changed.has_listeners(self.through))
# Fast addition through bulk insertion can only be performed
# if no m2m_changed listeners are connected for self.through
# as they require the added set of ids to be provided via
# pk_set.
- return can_ignore_conflicts, must_send_signals, (can_ignore_conflicts and not must_send_signals)
+ return (
+ can_ignore_conflicts,
+ must_send_signals,
+ (can_ignore_conflicts and not must_send_signals),
+ )
- def _add_items(self, source_field_name, target_field_name, *objs, through_defaults=None):
+ def _add_items(
+ self, source_field_name, target_field_name, *objs, through_defaults=None
+ ):
# 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.
@@ -1147,15 +1280,22 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
through_defaults = dict(resolve_callables(through_defaults or {}))
target_ids = self._get_target_ids(target_field_name, objs)
db = router.db_for_write(self.through, instance=self.instance)
- can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan(db, source_field_name)
+ can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan(
+ db, source_field_name
+ )
if can_fast_add:
- self.through._default_manager.using(db).bulk_create([
- self.through(**{
- '%s_id' % source_field_name: self.related_val[0],
- '%s_id' % target_field_name: target_id,
- })
- for target_id in target_ids
- ], ignore_conflicts=True)
+ self.through._default_manager.using(db).bulk_create(
+ [
+ self.through(
+ **{
+ "%s_id" % source_field_name: self.related_val[0],
+ "%s_id" % target_field_name: target_id,
+ }
+ )
+ for target_id in target_ids
+ ],
+ ignore_conflicts=True,
+ )
return
missing_target_ids = self._get_missing_target_ids(
@@ -1164,24 +1304,38 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
with transaction.atomic(using=db, savepoint=False):
if must_send_signals:
signals.m2m_changed.send(
- sender=self.through, action='pre_add',
- instance=self.instance, reverse=self.reverse,
- model=self.model, pk_set=missing_target_ids, using=db,
+ sender=self.through,
+ action="pre_add",
+ instance=self.instance,
+ reverse=self.reverse,
+ model=self.model,
+ pk_set=missing_target_ids,
+ using=db,
)
# Add the ones that aren't there already.
- self.through._default_manager.using(db).bulk_create([
- self.through(**through_defaults, **{
- '%s_id' % source_field_name: self.related_val[0],
- '%s_id' % target_field_name: target_id,
- })
- for target_id in missing_target_ids
- ], ignore_conflicts=can_ignore_conflicts)
+ self.through._default_manager.using(db).bulk_create(
+ [
+ self.through(
+ **through_defaults,
+ **{
+ "%s_id" % source_field_name: self.related_val[0],
+ "%s_id" % target_field_name: target_id,
+ },
+ )
+ for target_id in missing_target_ids
+ ],
+ ignore_conflicts=can_ignore_conflicts,
+ )
if must_send_signals:
signals.m2m_changed.send(
- sender=self.through, action='post_add',
- instance=self.instance, reverse=self.reverse,
- model=self.model, pk_set=missing_target_ids, using=db,
+ sender=self.through,
+ action="post_add",
+ instance=self.instance,
+ reverse=self.reverse,
+ model=self.model,
+ pk_set=missing_target_ids,
+ using=db,
)
def _remove_items(self, source_field_name, target_field_name, *objs):
@@ -1205,23 +1359,32 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
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,
+ 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().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})
+ 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,
+ sender=self.through,
+ action="post_remove",
+ instance=self.instance,
+ reverse=self.reverse,
+ model=self.model,
+ pk_set=old_ids,
+ using=db,
)
return ManyRelatedManager