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