Plain is headed towards 1.0! Subscribe for development updates →

  1"""
  2Accessors for related objects.
  3
  4When a field defines a relation between two models, each model class provides
  5an attribute to access related instances of the other model class (unless the
  6reverse accessor has been disabled with related_name='+').
  7
  8Accessors are implemented as descriptors in order to customize access and
  9assignment. This module defines the descriptor classes.
 10
 11Forward accessors follow foreign keys. Reverse accessors trace them back. For
 12example, with the following models::
 13
 14    class Parent(Model):
 15        pass
 16
 17    class Child(Model):
 18        parent = ForeignKey(Parent, related_name='children')
 19
 20 ``child.parent`` is a forward many-to-one relation. ``parent.children`` is a
 21reverse many-to-one relation.
 22
 231. Related instance on the forward side of a many-to-one relation:
 24   ``ForwardManyToOneDescriptor``.
 25
 26   Uniqueness of foreign key values is irrelevant to accessing the related
 27   instance, making the many-to-one and one-to-one cases identical as far as
 28   the descriptor is concerned. The constraint is checked upstream (unicity
 29   validation in forms) or downstream (unique indexes in the database).
 30
 312. Related objects manager for related instances on the reverse side of a
 32   many-to-one relation: ``ReverseManyToOneDescriptor``.
 33
 34   Unlike the previous two classes, this one provides access to a collection
 35   of objects. It returns a manager rather than an instance.
 36
 373. Related objects manager for related instances on the forward or reverse
 38   sides of a many-to-many relation: ``ManyToManyDescriptor``.
 39
 40   Many-to-many relations are symmetrical. The syntax of Plain models
 41   requires declaring them on one side but that's an implementation detail.
 42   They could be declared on the other side without any change in behavior.
 43   Therefore the forward and reverse descriptors can be the same.
 44
 45   If you're looking for ``ForwardManyToManyDescriptor`` or
 46   ``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
 47"""
 48
 49from functools import cached_property
 50
 51from plain.exceptions import FieldError
 52from plain.models import transaction
 53from plain.models.db import (
 54    NotSupportedError,
 55    db_connection,
 56)
 57from plain.models.expressions import Window
 58from plain.models.functions import RowNumber
 59from plain.models.lookups import GreaterThan, LessThanOrEqual
 60from plain.models.query import QuerySet
 61from plain.models.query_utils import DeferredAttribute, Q
 62from plain.models.utils import resolve_callables
 63
 64
 65class ForeignKeyDeferredAttribute(DeferredAttribute):
 66    def __set__(self, instance, value):
 67        if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(
 68            instance
 69        ):
 70            self.field.delete_cached_value(instance)
 71        instance.__dict__[self.field.attname] = value
 72
 73
 74def _filter_prefetch_queryset(queryset, field_name, instances):
 75    predicate = Q(**{f"{field_name}__in": instances})
 76    if queryset.query.is_sliced:
 77        if not db_connection.features.supports_over_clause:
 78            raise NotSupportedError(
 79                "Prefetching from a limited queryset is only supported on backends "
 80                "that support window functions."
 81            )
 82        low_mark, high_mark = queryset.query.low_mark, queryset.query.high_mark
 83        order_by = [expr for expr, _ in queryset.query.get_compiler().get_order_by()]
 84        window = Window(RowNumber(), partition_by=field_name, order_by=order_by)
 85        predicate &= GreaterThan(window, low_mark)
 86        if high_mark is not None:
 87            predicate &= LessThanOrEqual(window, high_mark)
 88        queryset.query.clear_limits()
 89    return queryset.filter(predicate)
 90
 91
 92class ForwardManyToOneDescriptor:
 93    """
 94    Accessor to the related object on the forward side of a many-to-one relation.
 95
 96    In the example::
 97
 98        class Child(Model):
 99            parent = ForeignKey(Parent, related_name='children')
100
101    ``Child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
102    """
103
104    def __init__(self, field_with_rel):
105        self.field = field_with_rel
106
107    @cached_property
108    def RelatedObjectDoesNotExist(self):
109        # The exception can't be created at initialization time since the
110        # related model might not be resolved yet; `self.field.model` might
111        # still be a string model reference.
112        return type(
113            "RelatedObjectDoesNotExist",
114            (self.field.remote_field.model.DoesNotExist, AttributeError),
115            {
116                "__module__": self.field.model.__module__,
117                "__qualname__": f"{self.field.model.__qualname__}.{self.field.name}.RelatedObjectDoesNotExist",
118            },
119        )
120
121    def is_cached(self, instance):
122        return self.field.is_cached(instance)
123
124    def get_queryset(self, **hints):
125        qs = self.field.remote_field.model._base_manager.get_queryset()
126        qs._add_hints(**hints)
127        return qs.all()
128
129    def get_prefetch_queryset(self, instances, queryset=None):
130        if queryset is None:
131            queryset = self.get_queryset()
132        queryset._add_hints(instance=instances[0])
133
134        rel_obj_attr = self.field.get_foreign_related_value
135        instance_attr = self.field.get_local_related_value
136        instances_dict = {instance_attr(inst): inst for inst in instances}
137        related_field = self.field.foreign_related_fields[0]
138        remote_field = self.field.remote_field
139
140        # FIXME: This will need to be revisited when we introduce support for
141        # composite fields. In the meantime we take this practical approach to
142        # solve a regression on 1.6 when the reverse manager in hidden
143        # (related_name ends with a '+'). Refs #21410.
144        # The check for len(...) == 1 is a special case that allows the query
145        # to be join-less and smaller. Refs #21760.
146        if remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1:
147            query = {
148                f"{related_field.name}__in": {
149                    instance_attr(inst)[0] for inst in instances
150                }
151            }
152        else:
153            query = {f"{self.field.related_query_name()}__in": instances}
154        queryset = queryset.filter(**query)
155
156        # Since we're going to assign directly in the cache,
157        # we must manage the reverse relation cache manually.
158        if not remote_field.multiple:
159            for rel_obj in queryset:
160                instance = instances_dict[rel_obj_attr(rel_obj)]
161                remote_field.set_cached_value(rel_obj, instance)
162        return (
163            queryset,
164            rel_obj_attr,
165            instance_attr,
166            True,
167            self.field.get_cache_name(),
168            False,
169        )
170
171    def get_object(self, instance):
172        qs = self.get_queryset(instance=instance)
173        # Assuming the database enforces foreign keys, this won't fail.
174        return qs.get(self.field.get_reverse_related_filter(instance))
175
176    def __get__(self, instance, cls=None):
177        """
178        Get the related instance through the forward relation.
179
180        With the example above, when getting ``child.parent``:
181
182        - ``self`` is the descriptor managing the ``parent`` attribute
183        - ``instance`` is the ``child`` instance
184        - ``cls`` is the ``Child`` class (we don't need it)
185        """
186        if instance is None:
187            return self
188
189        # The related instance is loaded from the database and then cached
190        # by the field on the model instance state. It can also be pre-cached
191        # by the reverse accessor.
192        try:
193            rel_obj = self.field.get_cached_value(instance)
194        except KeyError:
195            has_value = None not in self.field.get_local_related_value(instance)
196            rel_obj = None
197
198            if rel_obj is None and has_value:
199                rel_obj = self.get_object(instance)
200                remote_field = self.field.remote_field
201                # If this is a one-to-one relation, set the reverse accessor
202                # cache on the related object to the current instance to avoid
203                # an extra SQL query if it's accessed later on.
204                if not remote_field.multiple:
205                    remote_field.set_cached_value(rel_obj, instance)
206            self.field.set_cached_value(instance, rel_obj)
207
208        if rel_obj is None and not self.field.allow_null:
209            raise self.RelatedObjectDoesNotExist(
210                f"{self.field.model.__name__} has no {self.field.name}."
211            )
212        else:
213            return rel_obj
214
215    def __set__(self, instance, value):
216        """
217        Set the related instance through the forward relation.
218
219        With the example above, when setting ``child.parent = parent``:
220
221        - ``self`` is the descriptor managing the ``parent`` attribute
222        - ``instance`` is the ``child`` instance
223        - ``value`` is the ``parent`` instance on the right of the equal sign
224        """
225        # An object must be an instance of the related class.
226        if value is not None and not isinstance(
227            value, self.field.remote_field.model._meta.concrete_model
228        ):
229            raise ValueError(
230                f'Cannot assign "{value!r}": "{instance._meta.object_name}.{self.field.name}" must be a "{self.field.remote_field.model._meta.object_name}" instance.'
231            )
232        remote_field = self.field.remote_field
233        # If we're setting the value of a OneToOneField to None, we need to clear
234        # out the cache on any old related object. Otherwise, deleting the
235        # previously-related object will also cause this object to be deleted,
236        # which is wrong.
237        if value is None:
238            # Look up the previously-related object, which may still be available
239            # since we've not yet cleared out the related field.
240            # Use the cache directly, instead of the accessor; if we haven't
241            # populated the cache, then we don't care - we're only accessing
242            # the object to invalidate the accessor cache, so there's no
243            # need to populate the cache just to expire it again.
244            related = self.field.get_cached_value(instance, default=None)
245
246            # If we've got an old related object, we need to clear out its
247            # cache. This cache also might not exist if the related object
248            # hasn't been accessed yet.
249            if related is not None:
250                remote_field.set_cached_value(related, None)
251
252            for lh_field, rh_field in self.field.related_fields:
253                setattr(instance, lh_field.attname, None)
254
255        # Set the values of the related field.
256        else:
257            for lh_field, rh_field in self.field.related_fields:
258                setattr(instance, lh_field.attname, getattr(value, rh_field.attname))
259
260        # Set the related instance cache used by __get__ to avoid an SQL query
261        # when accessing the attribute we just set.
262        self.field.set_cached_value(instance, value)
263
264        # If this is a one-to-one relation, set the reverse accessor cache on
265        # the related object to the current instance to avoid an extra SQL
266        # query if it's accessed later on.
267        if value is not None and not remote_field.multiple:
268            remote_field.set_cached_value(value, instance)
269
270    def __reduce__(self):
271        """
272        Pickling should return the instance attached by self.field on the
273        model, not a new copy of that descriptor. Use getattr() to retrieve
274        the instance directly from the model.
275        """
276        return getattr, (self.field.model, self.field.name)
277
278
279class ReverseManyToOneDescriptor:
280    """
281    Accessor to the related objects manager on the reverse side of a
282    many-to-one relation.
283
284    In the example::
285
286        class Child(Model):
287            parent = ForeignKey(Parent, related_name='children')
288
289    ``Parent.children`` is a ``ReverseManyToOneDescriptor`` instance.
290
291    Most of the implementation is delegated to a dynamically defined manager
292    class built by ``create_forward_many_to_many_manager()`` defined below.
293    """
294
295    def __init__(self, rel):
296        self.rel = rel
297        self.field = rel.field
298
299    @cached_property
300    def related_manager_cls(self):
301        related_model = self.rel.related_model
302
303        return create_reverse_many_to_one_manager(
304            related_model._default_manager.__class__,
305            self.rel,
306        )
307
308    def __get__(self, instance, cls=None):
309        """
310        Get the related objects through the reverse relation.
311
312        With the example above, when getting ``parent.children``:
313
314        - ``self`` is the descriptor managing the ``children`` attribute
315        - ``instance`` is the ``parent`` instance
316        - ``cls`` is the ``Parent`` class (unused)
317        """
318        if instance is None:
319            return self
320
321        return self.related_manager_cls(instance)
322
323    def _get_set_deprecation_msg_params(self):
324        return (
325            "reverse side of a related set",
326            self.rel.get_accessor_name(),
327        )
328
329    def __set__(self, instance, value):
330        raise TypeError(
331            "Direct assignment to the {} is prohibited. Use {}.set() instead.".format(
332                *self._get_set_deprecation_msg_params()
333            ),
334        )
335
336
337def create_reverse_many_to_one_manager(superclass, rel):
338    """
339    Create a manager for the reverse side of a many-to-one relation.
340
341    This manager subclasses another manager, generally the default manager of
342    the related model, and adds behaviors specific to many-to-one relations.
343    """
344
345    class RelatedManager(superclass):
346        def __init__(self, instance):
347            super().__init__()
348
349            self.instance = instance
350            self.model = rel.related_model
351            self.field = rel.field
352
353            self.core_filters = {self.field.name: instance}
354
355        def __call__(self, *, manager):
356            manager = getattr(self.model, manager)
357            manager_class = create_reverse_many_to_one_manager(manager.__class__, rel)
358            return manager_class(self.instance)
359
360        def _check_fk_val(self):
361            for field in self.field.foreign_related_fields:
362                if getattr(self.instance, field.attname) is None:
363                    raise ValueError(
364                        f'"{self.instance!r}" needs to have a value for field '
365                        f'"{field.attname}" before this relationship can be used.'
366                    )
367
368        def _apply_rel_filters(self, queryset):
369            """
370            Filter the queryset for the instance this manager is bound to.
371            """
372            queryset._add_hints(instance=self.instance)
373            queryset._defer_next_filter = True
374            queryset = queryset.filter(**self.core_filters)
375            for field in self.field.foreign_related_fields:
376                val = getattr(self.instance, field.attname)
377                if val is None:
378                    return queryset.none()
379            if self.field.many_to_one:
380                # Guard against field-like objects such as GenericRelation
381                # that abuse create_reverse_many_to_one_manager() with reverse
382                # one-to-many relationships instead and break known related
383                # objects assignment.
384                try:
385                    target_field = self.field.target_field
386                except FieldError:
387                    # The relationship has multiple target fields. Use a tuple
388                    # for related object id.
389                    rel_obj_id = tuple(
390                        [
391                            getattr(self.instance, target_field.attname)
392                            for target_field in self.field.path_infos[-1].target_fields
393                        ]
394                    )
395                else:
396                    rel_obj_id = getattr(self.instance, target_field.attname)
397                queryset._known_related_objects = {
398                    self.field: {rel_obj_id: self.instance}
399                }
400            return queryset
401
402        def _remove_prefetched_objects(self):
403            try:
404                self.instance._prefetched_objects_cache.pop(
405                    self.field.remote_field.get_cache_name()
406                )
407            except (AttributeError, KeyError):
408                pass  # nothing to clear from cache
409
410        def get_queryset(self):
411            # Even if this relation is not to pk, we require still pk value.
412            # The wish is that the instance has been already saved to DB,
413            # although having a pk value isn't a guarantee of that.
414            if self.instance.pk is None:
415                raise ValueError(
416                    f"{self.instance.__class__.__name__!r} instance needs to have a "
417                    f"primary key value before this relationship can be used."
418                )
419            try:
420                return self.instance._prefetched_objects_cache[
421                    self.field.remote_field.get_cache_name()
422                ]
423            except (AttributeError, KeyError):
424                queryset = super().get_queryset()
425                return self._apply_rel_filters(queryset)
426
427        def get_prefetch_queryset(self, instances, queryset=None):
428            if queryset is None:
429                queryset = super().get_queryset()
430
431            queryset._add_hints(instance=instances[0])
432
433            rel_obj_attr = self.field.get_local_related_value
434            instance_attr = self.field.get_foreign_related_value
435            instances_dict = {instance_attr(inst): inst for inst in instances}
436            queryset = _filter_prefetch_queryset(queryset, self.field.name, instances)
437
438            # Since we just bypassed this class' get_queryset(), we must manage
439            # the reverse relation manually.
440            for rel_obj in queryset:
441                if not self.field.is_cached(rel_obj):
442                    instance = instances_dict[rel_obj_attr(rel_obj)]
443                    setattr(rel_obj, self.field.name, instance)
444            cache_name = self.field.remote_field.get_cache_name()
445            return queryset, rel_obj_attr, instance_attr, False, cache_name, False
446
447        def add(self, *objs, bulk=True):
448            self._check_fk_val()
449            self._remove_prefetched_objects()
450
451            def check_and_update_obj(obj):
452                if not isinstance(obj, self.model):
453                    raise TypeError(
454                        f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
455                    )
456                setattr(obj, self.field.name, self.instance)
457
458            if bulk:
459                pks = []
460                for obj in objs:
461                    check_and_update_obj(obj)
462                    if obj._state.adding:
463                        raise ValueError(
464                            f"{obj!r} instance isn't saved. Use bulk=False or save "
465                            "the object first."
466                        )
467                    pks.append(obj.pk)
468                self.model._base_manager.filter(pk__in=pks).update(
469                    **{
470                        self.field.name: self.instance,
471                    }
472                )
473            else:
474                with transaction.atomic(savepoint=False):
475                    for obj in objs:
476                        check_and_update_obj(obj)
477                        obj.save()
478
479        def create(self, **kwargs):
480            self._check_fk_val()
481            kwargs[self.field.name] = self.instance
482            return super().create(**kwargs)
483
484        def get_or_create(self, **kwargs):
485            self._check_fk_val()
486            kwargs[self.field.name] = self.instance
487            return super().get_or_create(**kwargs)
488
489        def update_or_create(self, **kwargs):
490            self._check_fk_val()
491            kwargs[self.field.name] = self.instance
492            return super().update_or_create(**kwargs)
493
494        # remove() and clear() are only provided if the ForeignKey can have a
495        # value of null.
496        if rel.field.allow_null:
497
498            def remove(self, *objs, bulk=True):
499                if not objs:
500                    return
501                self._check_fk_val()
502                val = self.field.get_foreign_related_value(self.instance)
503                old_ids = set()
504                for obj in objs:
505                    if not isinstance(obj, self.model):
506                        raise TypeError(
507                            f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
508                        )
509                    # Is obj actually part of this descriptor set?
510                    if self.field.get_local_related_value(obj) == val:
511                        old_ids.add(obj.pk)
512                    else:
513                        raise self.field.remote_field.model.DoesNotExist(
514                            f"{obj!r} is not related to {self.instance!r}."
515                        )
516                self._clear(self.filter(pk__in=old_ids), bulk)
517
518            def clear(self, *, bulk=True):
519                self._check_fk_val()
520                self._clear(self, bulk)
521
522            def _clear(self, queryset, bulk):
523                self._remove_prefetched_objects()
524                if bulk:
525                    # `QuerySet.update()` is intrinsically atomic.
526                    queryset.update(**{self.field.name: None})
527                else:
528                    with transaction.atomic(savepoint=False):
529                        for obj in queryset:
530                            setattr(obj, self.field.name, None)
531                            obj.save(update_fields=[self.field.name])
532
533        def set(self, objs, *, bulk=True, clear=False):
534            self._check_fk_val()
535            # Force evaluation of `objs` in case it's a queryset whose value
536            # could be affected by `manager.clear()`. Refs #19816.
537            objs = tuple(objs)
538
539            if self.field.allow_null:
540                with transaction.atomic(savepoint=False):
541                    if clear:
542                        self.clear(bulk=bulk)
543                        self.add(*objs, bulk=bulk)
544                    else:
545                        old_objs = set(self.all())
546                        new_objs = []
547                        for obj in objs:
548                            if obj in old_objs:
549                                old_objs.remove(obj)
550                            else:
551                                new_objs.append(obj)
552
553                        self.remove(*old_objs, bulk=bulk)
554                        self.add(*new_objs, bulk=bulk)
555            else:
556                self.add(*objs, bulk=bulk)
557
558    return RelatedManager
559
560
561class ManyToManyDescriptor(ReverseManyToOneDescriptor):
562    """
563    Accessor to the related objects manager on the forward and reverse sides of
564    a many-to-many relation.
565
566    In the example::
567
568        class Pizza(Model):
569            toppings = ManyToManyField(Topping, related_name='pizzas')
570
571    ``Pizza.toppings`` and ``Topping.pizzas`` are ``ManyToManyDescriptor``
572    instances.
573
574    Most of the implementation is delegated to a dynamically defined manager
575    class built by ``create_forward_many_to_many_manager()`` defined below.
576    """
577
578    def __init__(self, rel, reverse=False):
579        super().__init__(rel)
580
581        self.reverse = reverse
582
583    @property
584    def through(self):
585        # through is provided so that you have easy access to the through
586        # model (Book.authors.through) for inlines, etc. This is done as
587        # a property to ensure that the fully resolved value is returned.
588        return self.rel.through
589
590    @cached_property
591    def related_manager_cls(self):
592        related_model = self.rel.related_model if self.reverse else self.rel.model
593
594        return create_forward_many_to_many_manager(
595            related_model._default_manager.__class__,
596            self.rel,
597            reverse=self.reverse,
598        )
599
600    def _get_set_deprecation_msg_params(self):
601        return (
602            "%s side of a many-to-many set"
603            % ("reverse" if self.reverse else "forward"),
604            self.rel.get_accessor_name() if self.reverse else self.field.name,
605        )
606
607
608def create_forward_many_to_many_manager(superclass, rel, reverse):
609    """
610    Create a manager for the either side of a many-to-many relation.
611
612    This manager subclasses another manager, generally the default manager of
613    the related model, and adds behaviors specific to many-to-many relations.
614    """
615
616    class ManyRelatedManager(superclass):
617        def __init__(self, instance=None):
618            super().__init__()
619
620            self.instance = instance
621
622            if not reverse:
623                self.model = rel.model
624                self.query_field_name = rel.field.related_query_name()
625                self.prefetch_cache_name = rel.field.name
626                self.source_field_name = rel.field.m2m_field_name()
627                self.target_field_name = rel.field.m2m_reverse_field_name()
628                self.symmetrical = rel.symmetrical
629            else:
630                self.model = rel.related_model
631                self.query_field_name = rel.field.name
632                self.prefetch_cache_name = rel.field.related_query_name()
633                self.source_field_name = rel.field.m2m_reverse_field_name()
634                self.target_field_name = rel.field.m2m_field_name()
635                self.symmetrical = False
636
637            self.through = rel.through
638            self.reverse = reverse
639
640            self.source_field = self.through._meta.get_field(self.source_field_name)
641            self.target_field = self.through._meta.get_field(self.target_field_name)
642
643            self.core_filters = {}
644            self.pk_field_names = {}
645            for lh_field, rh_field in self.source_field.related_fields:
646                core_filter_key = f"{self.query_field_name}__{rh_field.name}"
647                self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
648                self.pk_field_names[lh_field.name] = rh_field.name
649
650            self.related_val = self.source_field.get_foreign_related_value(instance)
651            if None in self.related_val:
652                raise ValueError(
653                    f'"{instance!r}" needs to have a value for field "{self.pk_field_names[self.source_field_name]}" before '
654                    "this many-to-many relationship can be used."
655                )
656            # Even if this relation is not to pk, we require still pk value.
657            # The wish is that the instance has been already saved to DB,
658            # although having a pk value isn't a guarantee of that.
659            if instance.pk is None:
660                raise ValueError(
661                    f"{instance.__class__.__name__!r} instance needs to have a primary key value before "
662                    "a many-to-many relationship can be used."
663                )
664
665        def __call__(self, *, manager):
666            manager = getattr(self.model, manager)
667            manager_class = create_forward_many_to_many_manager(
668                manager.__class__, rel, reverse
669            )
670            return manager_class(instance=self.instance)
671
672        def _build_remove_filters(self, removed_vals):
673            filters = Q.create([(self.source_field_name, self.related_val)])
674            # No need to add a subquery condition if removed_vals is a QuerySet without
675            # filters.
676            removed_vals_filters = (
677                not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
678            )
679            if removed_vals_filters:
680                filters &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
681            if self.symmetrical:
682                symmetrical_filters = Q.create(
683                    [(self.target_field_name, self.related_val)]
684                )
685                if removed_vals_filters:
686                    symmetrical_filters &= Q.create(
687                        [(f"{self.source_field_name}__in", removed_vals)]
688                    )
689                filters |= symmetrical_filters
690            return filters
691
692        def _apply_rel_filters(self, queryset):
693            """
694            Filter the queryset for the instance this manager is bound to.
695            """
696            queryset._add_hints(instance=self.instance)
697            queryset._defer_next_filter = True
698            return queryset._next_is_sticky().filter(**self.core_filters)
699
700        def _remove_prefetched_objects(self):
701            try:
702                self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
703            except (AttributeError, KeyError):
704                pass  # nothing to clear from cache
705
706        def get_queryset(self):
707            try:
708                return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
709            except (AttributeError, KeyError):
710                queryset = super().get_queryset()
711                return self._apply_rel_filters(queryset)
712
713        def get_prefetch_queryset(self, instances, queryset=None):
714            if queryset is None:
715                queryset = super().get_queryset()
716
717            queryset._add_hints(instance=instances[0])
718            queryset = _filter_prefetch_queryset(
719                queryset._next_is_sticky(), self.query_field_name, instances
720            )
721
722            # M2M: need to annotate the query in order to get the primary model
723            # that the secondary model was actually related to. We know that
724            # there will already be a join on the join table, so we can just add
725            # the select.
726
727            # For non-autocreated 'through' models, can't assume we are
728            # dealing with PK values.
729            fk = self.through._meta.get_field(self.source_field_name)
730            join_table = fk.model._meta.db_table
731            qn = db_connection.ops.quote_name
732            queryset = queryset.extra(
733                select={
734                    f"_prefetch_related_val_{f.attname}": f"{qn(join_table)}.{qn(f.column)}"
735                    for f in fk.local_related_fields
736                }
737            )
738            return (
739                queryset,
740                lambda result: tuple(
741                    getattr(result, f"_prefetch_related_val_{f.attname}")
742                    for f in fk.local_related_fields
743                ),
744                lambda inst: tuple(
745                    f.get_db_prep_value(getattr(inst, f.attname), db_connection)
746                    for f in fk.foreign_related_fields
747                ),
748                False,
749                self.prefetch_cache_name,
750                False,
751            )
752
753        def add(self, *objs, through_defaults=None):
754            self._remove_prefetched_objects()
755            with transaction.atomic(savepoint=False):
756                self._add_items(
757                    self.source_field_name,
758                    self.target_field_name,
759                    *objs,
760                    through_defaults=through_defaults,
761                )
762                # If this is a symmetrical m2m relation to self, add the mirror
763                # entry in the m2m table.
764                if self.symmetrical:
765                    self._add_items(
766                        self.target_field_name,
767                        self.source_field_name,
768                        *objs,
769                        through_defaults=through_defaults,
770                    )
771
772        def remove(self, *objs):
773            self._remove_prefetched_objects()
774            self._remove_items(self.source_field_name, self.target_field_name, *objs)
775
776        def clear(self):
777            with transaction.atomic(savepoint=False):
778                self._remove_prefetched_objects()
779                filters = self._build_remove_filters(super().get_queryset())
780                self.through._default_manager.filter(filters).delete()
781
782        def set(self, objs, *, clear=False, through_defaults=None):
783            # Force evaluation of `objs` in case it's a queryset whose value
784            # could be affected by `manager.clear()`. Refs #19816.
785            objs = tuple(objs)
786
787            with transaction.atomic(savepoint=False):
788                if clear:
789                    self.clear()
790                    self.add(*objs, through_defaults=through_defaults)
791                else:
792                    old_ids = set(
793                        self.values_list(
794                            self.target_field.target_field.attname, flat=True
795                        )
796                    )
797
798                    new_objs = []
799                    for obj in objs:
800                        fk_val = (
801                            self.target_field.get_foreign_related_value(obj)[0]
802                            if isinstance(obj, self.model)
803                            else self.target_field.get_prep_value(obj)
804                        )
805                        if fk_val in old_ids:
806                            old_ids.remove(fk_val)
807                        else:
808                            new_objs.append(obj)
809
810                    self.remove(*old_ids)
811                    self.add(*new_objs, through_defaults=through_defaults)
812
813        def create(self, *, through_defaults=None, **kwargs):
814            new_obj = super().create(**kwargs)
815            self.add(new_obj, through_defaults=through_defaults)
816            return new_obj
817
818        def get_or_create(self, *, through_defaults=None, **kwargs):
819            obj, created = super().get_or_create(**kwargs)
820            # We only need to add() if created because if we got an object back
821            # from get() then the relationship already exists.
822            if created:
823                self.add(obj, through_defaults=through_defaults)
824            return obj, created
825
826        def update_or_create(self, *, through_defaults=None, **kwargs):
827            obj, created = super().update_or_create(**kwargs)
828            # We only need to add() if created because if we got an object back
829            # from get() then the relationship already exists.
830            if created:
831                self.add(obj, through_defaults=through_defaults)
832            return obj, created
833
834        def _get_target_ids(self, target_field_name, objs):
835            """
836            Return the set of ids of `objs` that the target field references.
837            """
838            from plain.models import Model
839
840            target_ids = set()
841            target_field = self.through._meta.get_field(target_field_name)
842            for obj in objs:
843                if isinstance(obj, self.model):
844                    target_id = target_field.get_foreign_related_value(obj)[0]
845                    if target_id is None:
846                        raise ValueError(
847                            f'Cannot add "{obj!r}": the value for field "{target_field_name}" is None'
848                        )
849                    target_ids.add(target_id)
850                elif isinstance(obj, Model):
851                    raise TypeError(
852                        f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
853                    )
854                else:
855                    target_ids.add(target_field.get_prep_value(obj))
856            return target_ids
857
858        def _get_missing_target_ids(
859            self, source_field_name, target_field_name, target_ids
860        ):
861            """
862            Return the subset of ids of `objs` that aren't already assigned to
863            this relationship.
864            """
865            vals = self.through._default_manager.values_list(
866                target_field_name, flat=True
867            ).filter(
868                **{
869                    source_field_name: self.related_val[0],
870                    f"{target_field_name}__in": target_ids,
871                }
872            )
873            return target_ids.difference(vals)
874
875        def _add_items(
876            self, source_field_name, target_field_name, *objs, through_defaults=None
877        ):
878            # source_field_name: the PK fieldname in join table for the source object
879            # target_field_name: the PK fieldname in join table for the target object
880            # *objs - objects to add. Either object instances, or primary keys
881            # of object instances.
882            if not objs:
883                return
884
885            through_defaults = dict(resolve_callables(through_defaults or {}))
886            target_ids = self._get_target_ids(target_field_name, objs)
887
888            missing_target_ids = self._get_missing_target_ids(
889                source_field_name, target_field_name, target_ids
890            )
891            with transaction.atomic(savepoint=False):
892                # Add the ones that aren't there already.
893                self.through._default_manager.bulk_create(
894                    [
895                        self.through(
896                            **through_defaults,
897                            **{
898                                f"{source_field_name}_id": self.related_val[0],
899                                f"{target_field_name}_id": target_id,
900                            },
901                        )
902                        for target_id in missing_target_ids
903                    ],
904                )
905
906        def _remove_items(self, source_field_name, target_field_name, *objs):
907            # source_field_name: the PK colname in join table for the source object
908            # target_field_name: the PK colname in join table for the target object
909            # *objs - objects to remove. Either object instances, or primary
910            # keys of object instances.
911            if not objs:
912                return
913
914            # Check that all the objects are of the right type
915            old_ids = set()
916            for obj in objs:
917                if isinstance(obj, self.model):
918                    fk_val = self.target_field.get_foreign_related_value(obj)[0]
919                    old_ids.add(fk_val)
920                else:
921                    old_ids.add(obj)
922
923            with transaction.atomic(savepoint=False):
924                target_model_qs = super().get_queryset()
925                if target_model_qs._has_filters():
926                    old_vals = target_model_qs.filter(
927                        **{f"{self.target_field.target_field.attname}__in": old_ids}
928                    )
929                else:
930                    old_vals = old_ids
931                filters = self._build_remove_filters(old_vals)
932                self.through._default_manager.filter(filters).delete()
933
934    return ManyRelatedManager