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