Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/fields/related_descriptors.py: 24%
569 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-23 11:16 -0600
1"""
2Accessors for related objects.
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='+').
8Accessors are implemented as descriptors in order to customize access and
9assignment. This module defines the descriptor classes.
11Forward accessors follow foreign keys. Reverse accessors trace them back. For
12example, with the following models::
14 class Parent(Model):
15 pass
17 class Child(Model):
18 parent = ForeignKey(Parent, related_name='children')
20 ``child.parent`` is a forward many-to-one relation. ``parent.children`` is a
21reverse many-to-one relation.
23There are three types of relations (many-to-one, one-to-one, and many-to-many)
24and two directions (forward and reverse) for a total of six combinations.
261. Related instance on the forward side of a many-to-one relation:
27 ``ForwardManyToOneDescriptor``.
29 Uniqueness of foreign key values is irrelevant to accessing the related
30 instance, making the many-to-one and one-to-one cases identical as far as
31 the descriptor is concerned. The constraint is checked upstream (unicity
32 validation in forms) or downstream (unique indexes in the database).
342. Related instance on the forward side of a one-to-one
35 relation: ``ForwardOneToOneDescriptor``.
37 It avoids querying the database when accessing the parent link field in
38 a multi-table inheritance scenario.
403. Related instance on the reverse side of a one-to-one relation:
41 ``ReverseOneToOneDescriptor``.
43 One-to-one relations are asymmetrical, despite the apparent symmetry of the
44 name, because they're implemented in the database with a foreign key from
45 one table to another. As a consequence ``ReverseOneToOneDescriptor`` is
46 slightly different from ``ForwardManyToOneDescriptor``.
484. Related objects manager for related instances on the reverse side of a
49 many-to-one relation: ``ReverseManyToOneDescriptor``.
51 Unlike the previous two classes, this one provides access to a collection
52 of objects. It returns a manager rather than an instance.
545. Related objects manager for related instances on the forward or reverse
55 sides of a many-to-many relation: ``ManyToManyDescriptor``.
57 Many-to-many relations are symmetrical. The syntax of Plain models
58 requires declaring them on one side but that's an implementation detail.
59 They could be declared on the other side without any change in behavior.
60 Therefore the forward and reverse descriptors can be the same.
62 If you're looking for ``ForwardManyToManyDescriptor`` or
63 ``ReverseManyToManyDescriptor``, use ``ManyToManyDescriptor`` instead.
64"""
66from plain.exceptions import FieldError
67from plain.models import signals, transaction
68from plain.models.db import (
69 DEFAULT_DB_ALIAS,
70 NotSupportedError,
71 connections,
72 router,
73)
74from plain.models.expressions import Window
75from plain.models.functions import RowNumber
76from plain.models.lookups import GreaterThan, LessThanOrEqual
77from plain.models.query import QuerySet
78from plain.models.query_utils import DeferredAttribute, Q
79from plain.models.utils import AltersData, resolve_callables
80from plain.utils.functional import cached_property
83class ForeignKeyDeferredAttribute(DeferredAttribute):
84 def __set__(self, instance, value):
85 if instance.__dict__.get(self.field.attname) != value and self.field.is_cached(
86 instance
87 ):
88 self.field.delete_cached_value(instance)
89 instance.__dict__[self.field.attname] = value
92def _filter_prefetch_queryset(queryset, field_name, instances):
93 predicate = Q(**{f"{field_name}__in": instances})
94 db = queryset._db or DEFAULT_DB_ALIAS
95 if queryset.query.is_sliced:
96 if not connections[db].features.supports_over_clause:
97 raise NotSupportedError(
98 "Prefetching from a limited queryset is only supported on backends "
99 "that support window functions."
100 )
101 low_mark, high_mark = queryset.query.low_mark, queryset.query.high_mark
102 order_by = [
103 expr for expr, _ in queryset.query.get_compiler(using=db).get_order_by()
104 ]
105 window = Window(RowNumber(), partition_by=field_name, order_by=order_by)
106 predicate &= GreaterThan(window, low_mark)
107 if high_mark is not None:
108 predicate &= LessThanOrEqual(window, high_mark)
109 queryset.query.clear_limits()
110 return queryset.filter(predicate)
113class ForwardManyToOneDescriptor:
114 """
115 Accessor to the related object on the forward side of a many-to-one or
116 one-to-one (via ForwardOneToOneDescriptor subclass) relation.
118 In the example::
120 class Child(Model):
121 parent = ForeignKey(Parent, related_name='children')
123 ``Child.parent`` is a ``ForwardManyToOneDescriptor`` instance.
124 """
126 def __init__(self, field_with_rel):
127 self.field = field_with_rel
129 @cached_property
130 def RelatedObjectDoesNotExist(self):
131 # The exception can't be created at initialization time since the
132 # related model might not be resolved yet; `self.field.model` might
133 # still be a string model reference.
134 return type(
135 "RelatedObjectDoesNotExist",
136 (self.field.remote_field.model.DoesNotExist, AttributeError),
137 {
138 "__module__": self.field.model.__module__,
139 "__qualname__": f"{self.field.model.__qualname__}.{self.field.name}.RelatedObjectDoesNotExist",
140 },
141 )
143 def is_cached(self, instance):
144 return self.field.is_cached(instance)
146 def get_queryset(self, **hints):
147 return self.field.remote_field.model._base_manager.db_manager(hints=hints).all()
149 def get_prefetch_queryset(self, instances, queryset=None):
150 if queryset is None:
151 queryset = self.get_queryset()
152 queryset._add_hints(instance=instances[0])
154 rel_obj_attr = self.field.get_foreign_related_value
155 instance_attr = self.field.get_local_related_value
156 instances_dict = {instance_attr(inst): inst for inst in instances}
157 related_field = self.field.foreign_related_fields[0]
158 remote_field = self.field.remote_field
160 # FIXME: This will need to be revisited when we introduce support for
161 # composite fields. In the meantime we take this practical approach to
162 # solve a regression on 1.6 when the reverse manager in hidden
163 # (related_name ends with a '+'). Refs #21410.
164 # The check for len(...) == 1 is a special case that allows the query
165 # to be join-less and smaller. Refs #21760.
166 if remote_field.is_hidden() or len(self.field.foreign_related_fields) == 1:
167 query = {
168 f"{related_field.name}__in": {
169 instance_attr(inst)[0] for inst in instances
170 }
171 }
172 else:
173 query = {f"{self.field.related_query_name()}__in": instances}
174 queryset = queryset.filter(**query)
176 # Since we're going to assign directly in the cache,
177 # we must manage the reverse relation cache manually.
178 if not remote_field.multiple:
179 for rel_obj in queryset:
180 instance = instances_dict[rel_obj_attr(rel_obj)]
181 remote_field.set_cached_value(rel_obj, instance)
182 return (
183 queryset,
184 rel_obj_attr,
185 instance_attr,
186 True,
187 self.field.get_cache_name(),
188 False,
189 )
191 def get_object(self, instance):
192 qs = self.get_queryset(instance=instance)
193 # Assuming the database enforces foreign keys, this won't fail.
194 return qs.get(self.field.get_reverse_related_filter(instance))
196 def __get__(self, instance, cls=None):
197 """
198 Get the related instance through the forward relation.
200 With the example above, when getting ``child.parent``:
202 - ``self`` is the descriptor managing the ``parent`` attribute
203 - ``instance`` is the ``child`` instance
204 - ``cls`` is the ``Child`` class (we don't need it)
205 """
206 if instance is None:
207 return self
209 # The related instance is loaded from the database and then cached
210 # by the field on the model instance state. It can also be pre-cached
211 # by the reverse accessor (ReverseOneToOneDescriptor).
212 try:
213 rel_obj = self.field.get_cached_value(instance)
214 except KeyError:
215 has_value = None not in self.field.get_local_related_value(instance)
216 ancestor_link = (
217 instance._meta.get_ancestor_link(self.field.model)
218 if has_value
219 else None
220 )
221 if ancestor_link and ancestor_link.is_cached(instance):
222 # An ancestor link will exist if this field is defined on a
223 # multi-table inheritance parent of the instance's class.
224 ancestor = ancestor_link.get_cached_value(instance)
225 # The value might be cached on an ancestor if the instance
226 # originated from walking down the inheritance chain.
227 rel_obj = self.field.get_cached_value(ancestor, default=None)
228 else:
229 rel_obj = None
230 if rel_obj is None and has_value:
231 rel_obj = self.get_object(instance)
232 remote_field = self.field.remote_field
233 # If this is a one-to-one relation, set the reverse accessor
234 # cache on the related object to the current instance to avoid
235 # an extra SQL query if it's accessed later on.
236 if not remote_field.multiple:
237 remote_field.set_cached_value(rel_obj, instance)
238 self.field.set_cached_value(instance, rel_obj)
240 if rel_obj is None and not self.field.null:
241 raise self.RelatedObjectDoesNotExist(
242 f"{self.field.model.__name__} has no {self.field.name}."
243 )
244 else:
245 return rel_obj
247 def __set__(self, instance, value):
248 """
249 Set the related instance through the forward relation.
251 With the example above, when setting ``child.parent = parent``:
253 - ``self`` is the descriptor managing the ``parent`` attribute
254 - ``instance`` is the ``child`` instance
255 - ``value`` is the ``parent`` instance on the right of the equal sign
256 """
257 # An object must be an instance of the related class.
258 if value is not None and not isinstance(
259 value, self.field.remote_field.model._meta.concrete_model
260 ):
261 raise ValueError(
262 f'Cannot assign "{value!r}": "{instance._meta.object_name}.{self.field.name}" must be a "{self.field.remote_field.model._meta.object_name}" instance.'
263 )
264 elif value is not None:
265 if instance._state.db is None:
266 instance._state.db = router.db_for_write(
267 instance.__class__, instance=value
268 )
269 if value._state.db is None:
270 value._state.db = router.db_for_write(
271 value.__class__, instance=instance
272 )
273 if not router.allow_relation(value, instance):
274 raise ValueError(
275 f'Cannot assign "{value!r}": the current database router prevents this '
276 "relation."
277 )
279 remote_field = self.field.remote_field
280 # If we're setting the value of a OneToOneField to None, we need to clear
281 # out the cache on any old related object. Otherwise, deleting the
282 # previously-related object will also cause this object to be deleted,
283 # which is wrong.
284 if value is None:
285 # Look up the previously-related object, which may still be available
286 # since we've not yet cleared out the related field.
287 # Use the cache directly, instead of the accessor; if we haven't
288 # populated the cache, then we don't care - we're only accessing
289 # the object to invalidate the accessor cache, so there's no
290 # need to populate the cache just to expire it again.
291 related = self.field.get_cached_value(instance, default=None)
293 # If we've got an old related object, we need to clear out its
294 # cache. This cache also might not exist if the related object
295 # hasn't been accessed yet.
296 if related is not None:
297 remote_field.set_cached_value(related, None)
299 for lh_field, rh_field in self.field.related_fields:
300 setattr(instance, lh_field.attname, None)
302 # Set the values of the related field.
303 else:
304 for lh_field, rh_field in self.field.related_fields:
305 setattr(instance, lh_field.attname, getattr(value, rh_field.attname))
307 # Set the related instance cache used by __get__ to avoid an SQL query
308 # when accessing the attribute we just set.
309 self.field.set_cached_value(instance, value)
311 # If this is a one-to-one relation, set the reverse accessor cache on
312 # the related object to the current instance to avoid an extra SQL
313 # query if it's accessed later on.
314 if value is not None and not remote_field.multiple:
315 remote_field.set_cached_value(value, instance)
317 def __reduce__(self):
318 """
319 Pickling should return the instance attached by self.field on the
320 model, not a new copy of that descriptor. Use getattr() to retrieve
321 the instance directly from the model.
322 """
323 return getattr, (self.field.model, self.field.name)
326class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor):
327 """
328 Accessor to the related object on the forward side of a one-to-one relation.
330 In the example::
332 class Restaurant(Model):
333 place = OneToOneField(Place, related_name='restaurant')
335 ``Restaurant.place`` is a ``ForwardOneToOneDescriptor`` instance.
336 """
338 def get_object(self, instance):
339 if self.field.remote_field.parent_link:
340 deferred = instance.get_deferred_fields()
341 # Because it's a parent link, all the data is available in the
342 # instance, so populate the parent model with this data.
343 rel_model = self.field.remote_field.model
344 fields = [field.attname for field in rel_model._meta.concrete_fields]
346 # If any of the related model's fields are deferred, fallback to
347 # fetching all fields from the related model. This avoids a query
348 # on the related model for every deferred field.
349 if not any(field in fields for field in deferred):
350 kwargs = {field: getattr(instance, field) for field in fields}
351 obj = rel_model(**kwargs)
352 obj._state.adding = instance._state.adding
353 obj._state.db = instance._state.db
354 return obj
355 return super().get_object(instance)
357 def __set__(self, instance, value):
358 super().__set__(instance, value)
359 # If the primary key is a link to a parent model and a parent instance
360 # is being set, update the value of the inherited pk(s).
361 if self.field.primary_key and self.field.remote_field.parent_link:
362 opts = instance._meta
363 # Inherited primary key fields from this object's base classes.
364 inherited_pk_fields = [
365 field
366 for field in opts.concrete_fields
367 if field.primary_key and field.remote_field
368 ]
369 for field in inherited_pk_fields:
370 rel_model_pk_name = field.remote_field.model._meta.pk.attname
371 raw_value = (
372 getattr(value, rel_model_pk_name) if value is not None else None
373 )
374 setattr(instance, rel_model_pk_name, raw_value)
377class ReverseOneToOneDescriptor:
378 """
379 Accessor to the related object on the reverse side of a one-to-one
380 relation.
382 In the example::
384 class Restaurant(Model):
385 place = OneToOneField(Place, related_name='restaurant')
387 ``Place.restaurant`` is a ``ReverseOneToOneDescriptor`` instance.
388 """
390 def __init__(self, related):
391 # Following the example above, `related` is an instance of OneToOneRel
392 # which represents the reverse restaurant field (place.restaurant).
393 self.related = related
395 @cached_property
396 def RelatedObjectDoesNotExist(self):
397 # The exception isn't created at initialization time for the sake of
398 # consistency with `ForwardManyToOneDescriptor`.
399 return type(
400 "RelatedObjectDoesNotExist",
401 (self.related.related_model.DoesNotExist, AttributeError),
402 {
403 "__module__": self.related.model.__module__,
404 "__qualname__": f"{self.related.model.__qualname__}.{self.related.name}.RelatedObjectDoesNotExist",
405 },
406 )
408 def is_cached(self, instance):
409 return self.related.is_cached(instance)
411 def get_queryset(self, **hints):
412 return self.related.related_model._base_manager.db_manager(hints=hints).all()
414 def get_prefetch_queryset(self, instances, queryset=None):
415 if queryset is None:
416 queryset = self.get_queryset()
417 queryset._add_hints(instance=instances[0])
419 rel_obj_attr = self.related.field.get_local_related_value
420 instance_attr = self.related.field.get_foreign_related_value
421 instances_dict = {instance_attr(inst): inst for inst in instances}
422 query = {f"{self.related.field.name}__in": instances}
423 queryset = queryset.filter(**query)
425 # Since we're going to assign directly in the cache,
426 # we must manage the reverse relation cache manually.
427 for rel_obj in queryset:
428 instance = instances_dict[rel_obj_attr(rel_obj)]
429 self.related.field.set_cached_value(rel_obj, instance)
430 return (
431 queryset,
432 rel_obj_attr,
433 instance_attr,
434 True,
435 self.related.get_cache_name(),
436 False,
437 )
439 def __get__(self, instance, cls=None):
440 """
441 Get the related instance through the reverse relation.
443 With the example above, when getting ``place.restaurant``:
445 - ``self`` is the descriptor managing the ``restaurant`` attribute
446 - ``instance`` is the ``place`` instance
447 - ``cls`` is the ``Place`` class (unused)
449 Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
450 """
451 if instance is None:
452 return self
454 # The related instance is loaded from the database and then cached
455 # by the field on the model instance state. It can also be pre-cached
456 # by the forward accessor (ForwardManyToOneDescriptor).
457 try:
458 rel_obj = self.related.get_cached_value(instance)
459 except KeyError:
460 related_pk = instance.pk
461 if related_pk is None:
462 rel_obj = None
463 else:
464 filter_args = self.related.field.get_forward_related_filter(instance)
465 try:
466 rel_obj = self.get_queryset(instance=instance).get(**filter_args)
467 except self.related.related_model.DoesNotExist:
468 rel_obj = None
469 else:
470 # Set the forward accessor cache on the related object to
471 # the current instance to avoid an extra SQL query if it's
472 # accessed later on.
473 self.related.field.set_cached_value(rel_obj, instance)
474 self.related.set_cached_value(instance, rel_obj)
476 if rel_obj is None:
477 raise self.RelatedObjectDoesNotExist(
478 f"{instance.__class__.__name__} has no {self.related.get_accessor_name()}."
479 )
480 else:
481 return rel_obj
483 def __set__(self, instance, value):
484 """
485 Set the related instance through the reverse relation.
487 With the example above, when setting ``place.restaurant = restaurant``:
489 - ``self`` is the descriptor managing the ``restaurant`` attribute
490 - ``instance`` is the ``place`` instance
491 - ``value`` is the ``restaurant`` instance on the right of the equal sign
493 Keep in mind that ``Restaurant`` holds the foreign key to ``Place``.
494 """
495 # The similarity of the code below to the code in
496 # ForwardManyToOneDescriptor is annoying, but there's a bunch
497 # of small differences that would make a common base class convoluted.
499 if value is None:
500 # Update the cached related instance (if any) & clear the cache.
501 # Following the example above, this would be the cached
502 # ``restaurant`` instance (if any).
503 rel_obj = self.related.get_cached_value(instance, default=None)
504 if rel_obj is not None:
505 # Remove the ``restaurant`` instance from the ``place``
506 # instance cache.
507 self.related.delete_cached_value(instance)
508 # Set the ``place`` field on the ``restaurant``
509 # instance to None.
510 setattr(rel_obj, self.related.field.name, None)
511 elif not isinstance(value, self.related.related_model):
512 # An object must be an instance of the related class.
513 raise ValueError(
514 f'Cannot assign "{value!r}": "{instance._meta.object_name}.{self.related.get_accessor_name()}" must be a "{self.related.related_model._meta.object_name}" instance.'
515 )
516 else:
517 if instance._state.db is None:
518 instance._state.db = router.db_for_write(
519 instance.__class__, instance=value
520 )
521 if value._state.db is None:
522 value._state.db = router.db_for_write(
523 value.__class__, instance=instance
524 )
525 if not router.allow_relation(value, instance):
526 raise ValueError(
527 f'Cannot assign "{value!r}": the current database router prevents this '
528 "relation."
529 )
531 related_pk = tuple(
532 getattr(instance, field.attname)
533 for field in self.related.field.foreign_related_fields
534 )
535 # Set the value of the related field to the value of the related
536 # object's related field.
537 for index, field in enumerate(self.related.field.local_related_fields):
538 setattr(value, field.attname, related_pk[index])
540 # Set the related instance cache used by __get__ to avoid an SQL query
541 # when accessing the attribute we just set.
542 self.related.set_cached_value(instance, value)
544 # Set the forward accessor cache on the related object to the current
545 # instance to avoid an extra SQL query if it's accessed later on.
546 self.related.field.set_cached_value(value, instance)
548 def __reduce__(self):
549 # Same purpose as ForwardManyToOneDescriptor.__reduce__().
550 return getattr, (self.related.model, self.related.name)
553class ReverseManyToOneDescriptor:
554 """
555 Accessor to the related objects manager on the reverse side of a
556 many-to-one relation.
558 In the example::
560 class Child(Model):
561 parent = ForeignKey(Parent, related_name='children')
563 ``Parent.children`` is a ``ReverseManyToOneDescriptor`` instance.
565 Most of the implementation is delegated to a dynamically defined manager
566 class built by ``create_forward_many_to_many_manager()`` defined below.
567 """
569 def __init__(self, rel):
570 self.rel = rel
571 self.field = rel.field
573 @cached_property
574 def related_manager_cls(self):
575 related_model = self.rel.related_model
577 return create_reverse_many_to_one_manager(
578 related_model._default_manager.__class__,
579 self.rel,
580 )
582 def __get__(self, instance, cls=None):
583 """
584 Get the related objects through the reverse relation.
586 With the example above, when getting ``parent.children``:
588 - ``self`` is the descriptor managing the ``children`` attribute
589 - ``instance`` is the ``parent`` instance
590 - ``cls`` is the ``Parent`` class (unused)
591 """
592 if instance is None:
593 return self
595 return self.related_manager_cls(instance)
597 def _get_set_deprecation_msg_params(self):
598 return (
599 "reverse side of a related set",
600 self.rel.get_accessor_name(),
601 )
603 def __set__(self, instance, value):
604 raise TypeError(
605 "Direct assignment to the {} is prohibited. Use {}.set() instead.".format(
606 *self._get_set_deprecation_msg_params()
607 ),
608 )
611def create_reverse_many_to_one_manager(superclass, rel):
612 """
613 Create a manager for the reverse side of a many-to-one relation.
615 This manager subclasses another manager, generally the default manager of
616 the related model, and adds behaviors specific to many-to-one relations.
617 """
619 class RelatedManager(superclass, AltersData):
620 def __init__(self, instance):
621 super().__init__()
623 self.instance = instance
624 self.model = rel.related_model
625 self.field = rel.field
627 self.core_filters = {self.field.name: instance}
629 def __call__(self, *, manager):
630 manager = getattr(self.model, manager)
631 manager_class = create_reverse_many_to_one_manager(manager.__class__, rel)
632 return manager_class(self.instance)
634 do_not_call_in_templates = True
636 def _check_fk_val(self):
637 for field in self.field.foreign_related_fields:
638 if getattr(self.instance, field.attname) is None:
639 raise ValueError(
640 f'"{self.instance!r}" needs to have a value for field '
641 f'"{field.attname}" before this relationship can be used.'
642 )
644 def _apply_rel_filters(self, queryset):
645 """
646 Filter the queryset for the instance this manager is bound to.
647 """
648 db = self._db or router.db_for_read(self.model, instance=self.instance)
649 empty_strings_as_null = connections[
650 db
651 ].features.interprets_empty_strings_as_nulls
652 queryset._add_hints(instance=self.instance)
653 if self._db:
654 queryset = queryset.using(self._db)
655 queryset._defer_next_filter = True
656 queryset = queryset.filter(**self.core_filters)
657 for field in self.field.foreign_related_fields:
658 val = getattr(self.instance, field.attname)
659 if val is None or (val == "" and empty_strings_as_null):
660 return queryset.none()
661 if self.field.many_to_one:
662 # Guard against field-like objects such as GenericRelation
663 # that abuse create_reverse_many_to_one_manager() with reverse
664 # one-to-many relationships instead and break known related
665 # objects assignment.
666 try:
667 target_field = self.field.target_field
668 except FieldError:
669 # The relationship has multiple target fields. Use a tuple
670 # for related object id.
671 rel_obj_id = tuple(
672 [
673 getattr(self.instance, target_field.attname)
674 for target_field in self.field.path_infos[-1].target_fields
675 ]
676 )
677 else:
678 rel_obj_id = getattr(self.instance, target_field.attname)
679 queryset._known_related_objects = {
680 self.field: {rel_obj_id: self.instance}
681 }
682 return queryset
684 def _remove_prefetched_objects(self):
685 try:
686 self.instance._prefetched_objects_cache.pop(
687 self.field.remote_field.get_cache_name()
688 )
689 except (AttributeError, KeyError):
690 pass # nothing to clear from cache
692 def get_queryset(self):
693 # Even if this relation is not to pk, we require still pk value.
694 # The wish is that the instance has been already saved to DB,
695 # although having a pk value isn't a guarantee of that.
696 if self.instance.pk is None:
697 raise ValueError(
698 f"{self.instance.__class__.__name__!r} instance needs to have a "
699 f"primary key value before this relationship can be used."
700 )
701 try:
702 return self.instance._prefetched_objects_cache[
703 self.field.remote_field.get_cache_name()
704 ]
705 except (AttributeError, KeyError):
706 queryset = super().get_queryset()
707 return self._apply_rel_filters(queryset)
709 def get_prefetch_queryset(self, instances, queryset=None):
710 if queryset is None:
711 queryset = super().get_queryset()
713 queryset._add_hints(instance=instances[0])
714 queryset = queryset.using(queryset._db or self._db)
716 rel_obj_attr = self.field.get_local_related_value
717 instance_attr = self.field.get_foreign_related_value
718 instances_dict = {instance_attr(inst): inst for inst in instances}
719 queryset = _filter_prefetch_queryset(queryset, self.field.name, instances)
721 # Since we just bypassed this class' get_queryset(), we must manage
722 # the reverse relation manually.
723 for rel_obj in queryset:
724 if not self.field.is_cached(rel_obj):
725 instance = instances_dict[rel_obj_attr(rel_obj)]
726 setattr(rel_obj, self.field.name, instance)
727 cache_name = self.field.remote_field.get_cache_name()
728 return queryset, rel_obj_attr, instance_attr, False, cache_name, False
730 def add(self, *objs, bulk=True):
731 self._check_fk_val()
732 self._remove_prefetched_objects()
733 db = router.db_for_write(self.model, instance=self.instance)
735 def check_and_update_obj(obj):
736 if not isinstance(obj, self.model):
737 raise TypeError(
738 f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
739 )
740 setattr(obj, self.field.name, self.instance)
742 if bulk:
743 pks = []
744 for obj in objs:
745 check_and_update_obj(obj)
746 if obj._state.adding or obj._state.db != db:
747 raise ValueError(
748 f"{obj!r} instance isn't saved. Use bulk=False or save "
749 "the object first."
750 )
751 pks.append(obj.pk)
752 self.model._base_manager.using(db).filter(pk__in=pks).update(
753 **{
754 self.field.name: self.instance,
755 }
756 )
757 else:
758 with transaction.atomic(using=db, savepoint=False):
759 for obj in objs:
760 check_and_update_obj(obj)
761 obj.save()
763 add.alters_data = True
765 def create(self, **kwargs):
766 self._check_fk_val()
767 kwargs[self.field.name] = self.instance
768 db = router.db_for_write(self.model, instance=self.instance)
769 return super(RelatedManager, self.db_manager(db)).create(**kwargs)
771 create.alters_data = True
773 def get_or_create(self, **kwargs):
774 self._check_fk_val()
775 kwargs[self.field.name] = self.instance
776 db = router.db_for_write(self.model, instance=self.instance)
777 return super(RelatedManager, self.db_manager(db)).get_or_create(**kwargs)
779 get_or_create.alters_data = True
781 def update_or_create(self, **kwargs):
782 self._check_fk_val()
783 kwargs[self.field.name] = self.instance
784 db = router.db_for_write(self.model, instance=self.instance)
785 return super(RelatedManager, self.db_manager(db)).update_or_create(**kwargs)
787 update_or_create.alters_data = True
789 # remove() and clear() are only provided if the ForeignKey can have a
790 # value of null.
791 if rel.field.null:
793 def remove(self, *objs, bulk=True):
794 if not objs:
795 return
796 self._check_fk_val()
797 val = self.field.get_foreign_related_value(self.instance)
798 old_ids = set()
799 for obj in objs:
800 if not isinstance(obj, self.model):
801 raise TypeError(
802 f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
803 )
804 # Is obj actually part of this descriptor set?
805 if self.field.get_local_related_value(obj) == val:
806 old_ids.add(obj.pk)
807 else:
808 raise self.field.remote_field.model.DoesNotExist(
809 f"{obj!r} is not related to {self.instance!r}."
810 )
811 self._clear(self.filter(pk__in=old_ids), bulk)
813 remove.alters_data = True
815 def clear(self, *, bulk=True):
816 self._check_fk_val()
817 self._clear(self, bulk)
819 clear.alters_data = True
821 def _clear(self, queryset, bulk):
822 self._remove_prefetched_objects()
823 db = router.db_for_write(self.model, instance=self.instance)
824 queryset = queryset.using(db)
825 if bulk:
826 # `QuerySet.update()` is intrinsically atomic.
827 queryset.update(**{self.field.name: None})
828 else:
829 with transaction.atomic(using=db, savepoint=False):
830 for obj in queryset:
831 setattr(obj, self.field.name, None)
832 obj.save(update_fields=[self.field.name])
834 _clear.alters_data = True
836 def set(self, objs, *, bulk=True, clear=False):
837 self._check_fk_val()
838 # Force evaluation of `objs` in case it's a queryset whose value
839 # could be affected by `manager.clear()`. Refs #19816.
840 objs = tuple(objs)
842 if self.field.null:
843 db = router.db_for_write(self.model, instance=self.instance)
844 with transaction.atomic(using=db, savepoint=False):
845 if clear:
846 self.clear(bulk=bulk)
847 self.add(*objs, bulk=bulk)
848 else:
849 old_objs = set(self.using(db).all())
850 new_objs = []
851 for obj in objs:
852 if obj in old_objs:
853 old_objs.remove(obj)
854 else:
855 new_objs.append(obj)
857 self.remove(*old_objs, bulk=bulk)
858 self.add(*new_objs, bulk=bulk)
859 else:
860 self.add(*objs, bulk=bulk)
862 set.alters_data = True
864 return RelatedManager
867class ManyToManyDescriptor(ReverseManyToOneDescriptor):
868 """
869 Accessor to the related objects manager on the forward and reverse sides of
870 a many-to-many relation.
872 In the example::
874 class Pizza(Model):
875 toppings = ManyToManyField(Topping, related_name='pizzas')
877 ``Pizza.toppings`` and ``Topping.pizzas`` are ``ManyToManyDescriptor``
878 instances.
880 Most of the implementation is delegated to a dynamically defined manager
881 class built by ``create_forward_many_to_many_manager()`` defined below.
882 """
884 def __init__(self, rel, reverse=False):
885 super().__init__(rel)
887 self.reverse = reverse
889 @property
890 def through(self):
891 # through is provided so that you have easy access to the through
892 # model (Book.authors.through) for inlines, etc. This is done as
893 # a property to ensure that the fully resolved value is returned.
894 return self.rel.through
896 @cached_property
897 def related_manager_cls(self):
898 related_model = self.rel.related_model if self.reverse else self.rel.model
900 return create_forward_many_to_many_manager(
901 related_model._default_manager.__class__,
902 self.rel,
903 reverse=self.reverse,
904 )
906 def _get_set_deprecation_msg_params(self):
907 return (
908 "%s side of a many-to-many set"
909 % ("reverse" if self.reverse else "forward"),
910 self.rel.get_accessor_name() if self.reverse else self.field.name,
911 )
914def create_forward_many_to_many_manager(superclass, rel, reverse):
915 """
916 Create a manager for the either side of a many-to-many relation.
918 This manager subclasses another manager, generally the default manager of
919 the related model, and adds behaviors specific to many-to-many relations.
920 """
922 class ManyRelatedManager(superclass, AltersData):
923 def __init__(self, instance=None):
924 super().__init__()
926 self.instance = instance
928 if not reverse:
929 self.model = rel.model
930 self.query_field_name = rel.field.related_query_name()
931 self.prefetch_cache_name = rel.field.name
932 self.source_field_name = rel.field.m2m_field_name()
933 self.target_field_name = rel.field.m2m_reverse_field_name()
934 self.symmetrical = rel.symmetrical
935 else:
936 self.model = rel.related_model
937 self.query_field_name = rel.field.name
938 self.prefetch_cache_name = rel.field.related_query_name()
939 self.source_field_name = rel.field.m2m_reverse_field_name()
940 self.target_field_name = rel.field.m2m_field_name()
941 self.symmetrical = False
943 self.through = rel.through
944 self.reverse = reverse
946 self.source_field = self.through._meta.get_field(self.source_field_name)
947 self.target_field = self.through._meta.get_field(self.target_field_name)
949 self.core_filters = {}
950 self.pk_field_names = {}
951 for lh_field, rh_field in self.source_field.related_fields:
952 core_filter_key = f"{self.query_field_name}__{rh_field.name}"
953 self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
954 self.pk_field_names[lh_field.name] = rh_field.name
956 self.related_val = self.source_field.get_foreign_related_value(instance)
957 if None in self.related_val:
958 raise ValueError(
959 f'"{instance!r}" needs to have a value for field "{self.pk_field_names[self.source_field_name]}" before '
960 "this many-to-many relationship can be used."
961 )
962 # Even if this relation is not to pk, we require still pk value.
963 # The wish is that the instance has been already saved to DB,
964 # although having a pk value isn't a guarantee of that.
965 if instance.pk is None:
966 raise ValueError(
967 f"{instance.__class__.__name__!r} instance needs to have a primary key value before "
968 "a many-to-many relationship can be used."
969 )
971 def __call__(self, *, manager):
972 manager = getattr(self.model, manager)
973 manager_class = create_forward_many_to_many_manager(
974 manager.__class__, rel, reverse
975 )
976 return manager_class(instance=self.instance)
978 do_not_call_in_templates = True
980 def _build_remove_filters(self, removed_vals):
981 filters = Q.create([(self.source_field_name, self.related_val)])
982 # No need to add a subquery condition if removed_vals is a QuerySet without
983 # filters.
984 removed_vals_filters = (
985 not isinstance(removed_vals, QuerySet) or removed_vals._has_filters()
986 )
987 if removed_vals_filters:
988 filters &= Q.create([(f"{self.target_field_name}__in", removed_vals)])
989 if self.symmetrical:
990 symmetrical_filters = Q.create(
991 [(self.target_field_name, self.related_val)]
992 )
993 if removed_vals_filters:
994 symmetrical_filters &= Q.create(
995 [(f"{self.source_field_name}__in", removed_vals)]
996 )
997 filters |= symmetrical_filters
998 return filters
1000 def _apply_rel_filters(self, queryset):
1001 """
1002 Filter the queryset for the instance this manager is bound to.
1003 """
1004 queryset._add_hints(instance=self.instance)
1005 if self._db:
1006 queryset = queryset.using(self._db)
1007 queryset._defer_next_filter = True
1008 return queryset._next_is_sticky().filter(**self.core_filters)
1010 def _remove_prefetched_objects(self):
1011 try:
1012 self.instance._prefetched_objects_cache.pop(self.prefetch_cache_name)
1013 except (AttributeError, KeyError):
1014 pass # nothing to clear from cache
1016 def get_queryset(self):
1017 try:
1018 return self.instance._prefetched_objects_cache[self.prefetch_cache_name]
1019 except (AttributeError, KeyError):
1020 queryset = super().get_queryset()
1021 return self._apply_rel_filters(queryset)
1023 def get_prefetch_queryset(self, instances, queryset=None):
1024 if queryset is None:
1025 queryset = super().get_queryset()
1027 queryset._add_hints(instance=instances[0])
1028 queryset = queryset.using(queryset._db or self._db)
1029 queryset = _filter_prefetch_queryset(
1030 queryset._next_is_sticky(), self.query_field_name, instances
1031 )
1033 # M2M: need to annotate the query in order to get the primary model
1034 # that the secondary model was actually related to. We know that
1035 # there will already be a join on the join table, so we can just add
1036 # the select.
1038 # For non-autocreated 'through' models, can't assume we are
1039 # dealing with PK values.
1040 fk = self.through._meta.get_field(self.source_field_name)
1041 join_table = fk.model._meta.db_table
1042 connection = connections[queryset.db]
1043 qn = connection.ops.quote_name
1044 queryset = queryset.extra(
1045 select={
1046 f"_prefetch_related_val_{f.attname}": f"{qn(join_table)}.{qn(f.column)}"
1047 for f in fk.local_related_fields
1048 }
1049 )
1050 return (
1051 queryset,
1052 lambda result: tuple(
1053 getattr(result, f"_prefetch_related_val_{f.attname}")
1054 for f in fk.local_related_fields
1055 ),
1056 lambda inst: tuple(
1057 f.get_db_prep_value(getattr(inst, f.attname), connection)
1058 for f in fk.foreign_related_fields
1059 ),
1060 False,
1061 self.prefetch_cache_name,
1062 False,
1063 )
1065 def add(self, *objs, through_defaults=None):
1066 self._remove_prefetched_objects()
1067 db = router.db_for_write(self.through, instance=self.instance)
1068 with transaction.atomic(using=db, savepoint=False):
1069 self._add_items(
1070 self.source_field_name,
1071 self.target_field_name,
1072 *objs,
1073 through_defaults=through_defaults,
1074 )
1075 # If this is a symmetrical m2m relation to self, add the mirror
1076 # entry in the m2m table.
1077 if self.symmetrical:
1078 self._add_items(
1079 self.target_field_name,
1080 self.source_field_name,
1081 *objs,
1082 through_defaults=through_defaults,
1083 )
1085 add.alters_data = True
1087 def remove(self, *objs):
1088 self._remove_prefetched_objects()
1089 self._remove_items(self.source_field_name, self.target_field_name, *objs)
1091 remove.alters_data = True
1093 def clear(self):
1094 db = router.db_for_write(self.through, instance=self.instance)
1095 with transaction.atomic(using=db, savepoint=False):
1096 signals.m2m_changed.send(
1097 sender=self.through,
1098 action="pre_clear",
1099 instance=self.instance,
1100 reverse=self.reverse,
1101 model=self.model,
1102 pk_set=None,
1103 using=db,
1104 )
1105 self._remove_prefetched_objects()
1106 filters = self._build_remove_filters(super().get_queryset().using(db))
1107 self.through._default_manager.using(db).filter(filters).delete()
1109 signals.m2m_changed.send(
1110 sender=self.through,
1111 action="post_clear",
1112 instance=self.instance,
1113 reverse=self.reverse,
1114 model=self.model,
1115 pk_set=None,
1116 using=db,
1117 )
1119 clear.alters_data = True
1121 def set(self, objs, *, clear=False, through_defaults=None):
1122 # Force evaluation of `objs` in case it's a queryset whose value
1123 # could be affected by `manager.clear()`. Refs #19816.
1124 objs = tuple(objs)
1126 db = router.db_for_write(self.through, instance=self.instance)
1127 with transaction.atomic(using=db, savepoint=False):
1128 if clear:
1129 self.clear()
1130 self.add(*objs, through_defaults=through_defaults)
1131 else:
1132 old_ids = set(
1133 self.using(db).values_list(
1134 self.target_field.target_field.attname, flat=True
1135 )
1136 )
1138 new_objs = []
1139 for obj in objs:
1140 fk_val = (
1141 self.target_field.get_foreign_related_value(obj)[0]
1142 if isinstance(obj, self.model)
1143 else self.target_field.get_prep_value(obj)
1144 )
1145 if fk_val in old_ids:
1146 old_ids.remove(fk_val)
1147 else:
1148 new_objs.append(obj)
1150 self.remove(*old_ids)
1151 self.add(*new_objs, through_defaults=through_defaults)
1153 set.alters_data = True
1155 def create(self, *, through_defaults=None, **kwargs):
1156 db = router.db_for_write(self.instance.__class__, instance=self.instance)
1157 new_obj = super(ManyRelatedManager, self.db_manager(db)).create(**kwargs)
1158 self.add(new_obj, through_defaults=through_defaults)
1159 return new_obj
1161 create.alters_data = True
1163 def get_or_create(self, *, through_defaults=None, **kwargs):
1164 db = router.db_for_write(self.instance.__class__, instance=self.instance)
1165 obj, created = super(ManyRelatedManager, self.db_manager(db)).get_or_create(
1166 **kwargs
1167 )
1168 # We only need to add() if created because if we got an object back
1169 # from get() then the relationship already exists.
1170 if created:
1171 self.add(obj, through_defaults=through_defaults)
1172 return obj, created
1174 get_or_create.alters_data = True
1176 def update_or_create(self, *, through_defaults=None, **kwargs):
1177 db = router.db_for_write(self.instance.__class__, instance=self.instance)
1178 obj, created = super(
1179 ManyRelatedManager, self.db_manager(db)
1180 ).update_or_create(**kwargs)
1181 # We only need to add() if created because if we got an object back
1182 # from get() then the relationship already exists.
1183 if created:
1184 self.add(obj, through_defaults=through_defaults)
1185 return obj, created
1187 update_or_create.alters_data = True
1189 def _get_target_ids(self, target_field_name, objs):
1190 """
1191 Return the set of ids of `objs` that the target field references.
1192 """
1193 from plain.models import Model
1195 target_ids = set()
1196 target_field = self.through._meta.get_field(target_field_name)
1197 for obj in objs:
1198 if isinstance(obj, self.model):
1199 if not router.allow_relation(obj, self.instance):
1200 raise ValueError(
1201 f'Cannot add "{obj!r}": instance is on database "{self.instance._state.db}", '
1202 f'value is on database "{obj._state.db}"'
1203 )
1204 target_id = target_field.get_foreign_related_value(obj)[0]
1205 if target_id is None:
1206 raise ValueError(
1207 f'Cannot add "{obj!r}": the value for field "{target_field_name}" is None'
1208 )
1209 target_ids.add(target_id)
1210 elif isinstance(obj, Model):
1211 raise TypeError(
1212 f"'{self.model._meta.object_name}' instance expected, got {obj!r}"
1213 )
1214 else:
1215 target_ids.add(target_field.get_prep_value(obj))
1216 return target_ids
1218 def _get_missing_target_ids(
1219 self, source_field_name, target_field_name, db, target_ids
1220 ):
1221 """
1222 Return the subset of ids of `objs` that aren't already assigned to
1223 this relationship.
1224 """
1225 vals = (
1226 self.through._default_manager.using(db)
1227 .values_list(target_field_name, flat=True)
1228 .filter(
1229 **{
1230 source_field_name: self.related_val[0],
1231 f"{target_field_name}__in": target_ids,
1232 }
1233 )
1234 )
1235 return target_ids.difference(vals)
1237 def _get_add_plan(self, db, source_field_name):
1238 """
1239 Return a boolean triple of the way the add should be performed.
1241 The first element is whether or not bulk_create(ignore_conflicts)
1242 can be used, the second whether or not signals must be sent, and
1243 the third element is whether or not the immediate bulk insertion
1244 with conflicts ignored can be performed.
1245 """
1246 # Conflicts can be ignored when the intermediary model is
1247 # auto-created as the only possible collision is on the
1248 # (source_id, target_id) tuple. The same assertion doesn't hold for
1249 # user-defined intermediary models as they could have other fields
1250 # causing conflicts which must be surfaced.
1251 can_ignore_conflicts = (
1252 self.through._meta.auto_created is not False
1253 and connections[db].features.supports_ignore_conflicts
1254 )
1255 # Don't send the signal when inserting duplicate data row
1256 # for symmetrical reverse entries.
1257 must_send_signals = (
1258 self.reverse or source_field_name == self.source_field_name
1259 ) and (signals.m2m_changed.has_listeners(self.through))
1260 # Fast addition through bulk insertion can only be performed
1261 # if no m2m_changed listeners are connected for self.through
1262 # as they require the added set of ids to be provided via
1263 # pk_set.
1264 return (
1265 can_ignore_conflicts,
1266 must_send_signals,
1267 (can_ignore_conflicts and not must_send_signals),
1268 )
1270 def _add_items(
1271 self, source_field_name, target_field_name, *objs, through_defaults=None
1272 ):
1273 # source_field_name: the PK fieldname in join table for the source object
1274 # target_field_name: the PK fieldname in join table for the target object
1275 # *objs - objects to add. Either object instances, or primary keys
1276 # of object instances.
1277 if not objs:
1278 return
1280 through_defaults = dict(resolve_callables(through_defaults or {}))
1281 target_ids = self._get_target_ids(target_field_name, objs)
1282 db = router.db_for_write(self.through, instance=self.instance)
1283 can_ignore_conflicts, must_send_signals, can_fast_add = self._get_add_plan(
1284 db, source_field_name
1285 )
1286 if can_fast_add:
1287 self.through._default_manager.using(db).bulk_create(
1288 [
1289 self.through(
1290 **{
1291 f"{source_field_name}_id": self.related_val[0],
1292 f"{target_field_name}_id": target_id,
1293 }
1294 )
1295 for target_id in target_ids
1296 ],
1297 ignore_conflicts=True,
1298 )
1299 return
1301 missing_target_ids = self._get_missing_target_ids(
1302 source_field_name, target_field_name, db, target_ids
1303 )
1304 with transaction.atomic(using=db, savepoint=False):
1305 if must_send_signals:
1306 signals.m2m_changed.send(
1307 sender=self.through,
1308 action="pre_add",
1309 instance=self.instance,
1310 reverse=self.reverse,
1311 model=self.model,
1312 pk_set=missing_target_ids,
1313 using=db,
1314 )
1315 # Add the ones that aren't there already.
1316 self.through._default_manager.using(db).bulk_create(
1317 [
1318 self.through(
1319 **through_defaults,
1320 **{
1321 f"{source_field_name}_id": self.related_val[0],
1322 f"{target_field_name}_id": target_id,
1323 },
1324 )
1325 for target_id in missing_target_ids
1326 ],
1327 ignore_conflicts=can_ignore_conflicts,
1328 )
1330 if must_send_signals:
1331 signals.m2m_changed.send(
1332 sender=self.through,
1333 action="post_add",
1334 instance=self.instance,
1335 reverse=self.reverse,
1336 model=self.model,
1337 pk_set=missing_target_ids,
1338 using=db,
1339 )
1341 def _remove_items(self, source_field_name, target_field_name, *objs):
1342 # source_field_name: the PK colname in join table for the source object
1343 # target_field_name: the PK colname in join table for the target object
1344 # *objs - objects to remove. Either object instances, or primary
1345 # keys of object instances.
1346 if not objs:
1347 return
1349 # Check that all the objects are of the right type
1350 old_ids = set()
1351 for obj in objs:
1352 if isinstance(obj, self.model):
1353 fk_val = self.target_field.get_foreign_related_value(obj)[0]
1354 old_ids.add(fk_val)
1355 else:
1356 old_ids.add(obj)
1358 db = router.db_for_write(self.through, instance=self.instance)
1359 with transaction.atomic(using=db, savepoint=False):
1360 # Send a signal to the other end if need be.
1361 signals.m2m_changed.send(
1362 sender=self.through,
1363 action="pre_remove",
1364 instance=self.instance,
1365 reverse=self.reverse,
1366 model=self.model,
1367 pk_set=old_ids,
1368 using=db,
1369 )
1370 target_model_qs = super().get_queryset()
1371 if target_model_qs._has_filters():
1372 old_vals = target_model_qs.using(db).filter(
1373 **{f"{self.target_field.target_field.attname}__in": old_ids}
1374 )
1375 else:
1376 old_vals = old_ids
1377 filters = self._build_remove_filters(old_vals)
1378 self.through._default_manager.using(db).filter(filters).delete()
1380 signals.m2m_changed.send(
1381 sender=self.through,
1382 action="post_remove",
1383 instance=self.instance,
1384 reverse=self.reverse,
1385 model=self.model,
1386 pk_set=old_ids,
1387 using=db,
1388 )
1390 return ManyRelatedManager