Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/fields/related_descriptors.py: 13%

569 statements  

« prev     ^ index     » next       coverage.py v7.6.9, created at 2024-12-23 11:16 -0600

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__": f"{self.field.model.__qualname__}.{self.field.name}.RelatedObjectDoesNotExist", 

140 }, 

141 ) 

142 

143 def is_cached(self, instance): 

144 return self.field.is_cached(instance) 

145 

146 def get_queryset(self, **hints): 

147 return self.field.remote_field.model._base_manager.db_manager(hints=hints).all() 

148 

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]) 

153 

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 

159 

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) 

175 

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 ) 

190 

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)) 

195 

196 def __get__(self, instance, cls=None): 

197 """ 

198 Get the related instance through the forward relation. 

199 

200 With the example above, when getting ``child.parent``: 

201 

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 

208 

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) 

239 

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 

246 

247 def __set__(self, instance, value): 

248 """ 

249 Set the related instance through the forward relation. 

250 

251 With the example above, when setting ``child.parent = parent``: 

252 

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 ) 

278 

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) 

292 

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) 

298 

299 for lh_field, rh_field in self.field.related_fields: 

300 setattr(instance, lh_field.attname, None) 

301 

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)) 

306 

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) 

310 

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) 

316 

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) 

324 

325 

326class ForwardOneToOneDescriptor(ForwardManyToOneDescriptor): 

327 """ 

328 Accessor to the related object on the forward side of a one-to-one relation. 

329 

330 In the example:: 

331 

332 class Restaurant(Model): 

333 place = OneToOneField(Place, related_name='restaurant') 

334 

335 ``Restaurant.place`` is a ``ForwardOneToOneDescriptor`` instance. 

336 """ 

337 

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] 

345 

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) 

356 

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) 

375 

376 

377class ReverseOneToOneDescriptor: 

378 """ 

379 Accessor to the related object on the reverse side of a one-to-one 

380 relation. 

381 

382 In the example:: 

383 

384 class Restaurant(Model): 

385 place = OneToOneField(Place, related_name='restaurant') 

386 

387 ``Place.restaurant`` is a ``ReverseOneToOneDescriptor`` instance. 

388 """ 

389 

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 

394 

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 ) 

407 

408 def is_cached(self, instance): 

409 return self.related.is_cached(instance) 

410 

411 def get_queryset(self, **hints): 

412 return self.related.related_model._base_manager.db_manager(hints=hints).all() 

413 

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]) 

418 

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) 

424 

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 ) 

438 

439 def __get__(self, instance, cls=None): 

440 """ 

441 Get the related instance through the reverse relation. 

442 

443 With the example above, when getting ``place.restaurant``: 

444 

445 - ``self`` is the descriptor managing the ``restaurant`` attribute 

446 - ``instance`` is the ``place`` instance 

447 - ``cls`` is the ``Place`` class (unused) 

448 

449 Keep in mind that ``Restaurant`` holds the foreign key to ``Place``. 

450 """ 

451 if instance is None: 

452 return self 

453 

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) 

475 

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 

482 

483 def __set__(self, instance, value): 

484 """ 

485 Set the related instance through the reverse relation. 

486 

487 With the example above, when setting ``place.restaurant = restaurant``: 

488 

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 

492 

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. 

498 

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 ) 

530 

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]) 

539 

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) 

543 

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) 

547 

548 def __reduce__(self): 

549 # Same purpose as ForwardManyToOneDescriptor.__reduce__(). 

550 return getattr, (self.related.model, self.related.name) 

551 

552 

553class ReverseManyToOneDescriptor: 

554 """ 

555 Accessor to the related objects manager on the reverse side of a 

556 many-to-one relation. 

557 

558 In the example:: 

559 

560 class Child(Model): 

561 parent = ForeignKey(Parent, related_name='children') 

562 

563 ``Parent.children`` is a ``ReverseManyToOneDescriptor`` instance. 

564 

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 """ 

568 

569 def __init__(self, rel): 

570 self.rel = rel 

571 self.field = rel.field 

572 

573 @cached_property 

574 def related_manager_cls(self): 

575 related_model = self.rel.related_model 

576 

577 return create_reverse_many_to_one_manager( 

578 related_model._default_manager.__class__, 

579 self.rel, 

580 ) 

581 

582 def __get__(self, instance, cls=None): 

583 """ 

584 Get the related objects through the reverse relation. 

585 

586 With the example above, when getting ``parent.children``: 

587 

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 

594 

595 return self.related_manager_cls(instance) 

596 

597 def _get_set_deprecation_msg_params(self): 

598 return ( 

599 "reverse side of a related set", 

600 self.rel.get_accessor_name(), 

601 ) 

602 

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 ) 

609 

610 

611def create_reverse_many_to_one_manager(superclass, rel): 

612 """ 

613 Create a manager for the reverse side of a many-to-one relation. 

614 

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 """ 

618 

619 class RelatedManager(superclass, AltersData): 

620 def __init__(self, instance): 

621 super().__init__() 

622 

623 self.instance = instance 

624 self.model = rel.related_model 

625 self.field = rel.field 

626 

627 self.core_filters = {self.field.name: instance} 

628 

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) 

633 

634 do_not_call_in_templates = True 

635 

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 ) 

643 

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 

683 

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 

691 

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) 

708 

709 def get_prefetch_queryset(self, instances, queryset=None): 

710 if queryset is None: 

711 queryset = super().get_queryset() 

712 

713 queryset._add_hints(instance=instances[0]) 

714 queryset = queryset.using(queryset._db or self._db) 

715 

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) 

720 

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 

729 

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) 

734 

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) 

741 

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() 

762 

763 add.alters_data = True 

764 

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) 

770 

771 create.alters_data = True 

772 

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) 

778 

779 get_or_create.alters_data = True 

780 

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) 

786 

787 update_or_create.alters_data = True 

788 

789 # remove() and clear() are only provided if the ForeignKey can have a 

790 # value of null. 

791 if rel.field.null: 

792 

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) 

812 

813 remove.alters_data = True 

814 

815 def clear(self, *, bulk=True): 

816 self._check_fk_val() 

817 self._clear(self, bulk) 

818 

819 clear.alters_data = True 

820 

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]) 

833 

834 _clear.alters_data = True 

835 

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) 

841 

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) 

856 

857 self.remove(*old_objs, bulk=bulk) 

858 self.add(*new_objs, bulk=bulk) 

859 else: 

860 self.add(*objs, bulk=bulk) 

861 

862 set.alters_data = True 

863 

864 return RelatedManager 

865 

866 

867class ManyToManyDescriptor(ReverseManyToOneDescriptor): 

868 """ 

869 Accessor to the related objects manager on the forward and reverse sides of 

870 a many-to-many relation. 

871 

872 In the example:: 

873 

874 class Pizza(Model): 

875 toppings = ManyToManyField(Topping, related_name='pizzas') 

876 

877 ``Pizza.toppings`` and ``Topping.pizzas`` are ``ManyToManyDescriptor`` 

878 instances. 

879 

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 """ 

883 

884 def __init__(self, rel, reverse=False): 

885 super().__init__(rel) 

886 

887 self.reverse = reverse 

888 

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 

895 

896 @cached_property 

897 def related_manager_cls(self): 

898 related_model = self.rel.related_model if self.reverse else self.rel.model 

899 

900 return create_forward_many_to_many_manager( 

901 related_model._default_manager.__class__, 

902 self.rel, 

903 reverse=self.reverse, 

904 ) 

905 

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 ) 

912 

913 

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. 

917 

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 """ 

921 

922 class ManyRelatedManager(superclass, AltersData): 

923 def __init__(self, instance=None): 

924 super().__init__() 

925 

926 self.instance = instance 

927 

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 

942 

943 self.through = rel.through 

944 self.reverse = reverse 

945 

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) 

948 

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 

955 

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 ) 

970 

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) 

977 

978 do_not_call_in_templates = True 

979 

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 

999 

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) 

1009 

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 

1015 

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) 

1022 

1023 def get_prefetch_queryset(self, instances, queryset=None): 

1024 if queryset is None: 

1025 queryset = super().get_queryset() 

1026 

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 ) 

1032 

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. 

1037 

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 ) 

1064 

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 ) 

1084 

1085 add.alters_data = True 

1086 

1087 def remove(self, *objs): 

1088 self._remove_prefetched_objects() 

1089 self._remove_items(self.source_field_name, self.target_field_name, *objs) 

1090 

1091 remove.alters_data = True 

1092 

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() 

1108 

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 ) 

1118 

1119 clear.alters_data = True 

1120 

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) 

1125 

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 ) 

1137 

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) 

1149 

1150 self.remove(*old_ids) 

1151 self.add(*new_objs, through_defaults=through_defaults) 

1152 

1153 set.alters_data = True 

1154 

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 

1160 

1161 create.alters_data = True 

1162 

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 

1173 

1174 get_or_create.alters_data = True 

1175 

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 

1186 

1187 update_or_create.alters_data = True 

1188 

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 

1194 

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 

1217 

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) 

1236 

1237 def _get_add_plan(self, db, source_field_name): 

1238 """ 

1239 Return a boolean triple of the way the add should be performed. 

1240 

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 ) 

1269 

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 

1279 

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 

1300 

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 ) 

1329 

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 ) 

1340 

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 

1348 

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) 

1357 

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() 

1379 

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 ) 

1389 

1390 return ManyRelatedManager