diff options
Diffstat (limited to 'django/db/models/fields/related_descriptors.py')
| -rw-r--r-- | django/db/models/fields/related_descriptors.py | 425 |
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 |
