Coverage for /Users/davegaeddert/Developer/dropseed/plain/plain-models/plain/models/fields/related.py: 25%

734 statements  

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

1import functools 

2import inspect 

3from functools import partial 

4 

5from plain import exceptions, preflight 

6from plain.models.backends import utils 

7from plain.models.constants import LOOKUP_SEP 

8from plain.models.db import connection, router 

9from plain.models.deletion import CASCADE, SET_DEFAULT, SET_NULL 

10from plain.models.query_utils import PathInfo, Q 

11from plain.models.utils import make_model_tuple 

12from plain.packages import packages 

13from plain.runtime import settings 

14from plain.runtime.user_settings import SettingsReference 

15from plain.utils.functional import cached_property 

16 

17from . import Field 

18from .mixins import FieldCacheMixin 

19from .related_descriptors import ( 

20 ForeignKeyDeferredAttribute, 

21 ForwardManyToOneDescriptor, 

22 ForwardOneToOneDescriptor, 

23 ManyToManyDescriptor, 

24 ReverseManyToOneDescriptor, 

25 ReverseOneToOneDescriptor, 

26) 

27from .related_lookups import ( 

28 RelatedExact, 

29 RelatedGreaterThan, 

30 RelatedGreaterThanOrEqual, 

31 RelatedIn, 

32 RelatedIsNull, 

33 RelatedLessThan, 

34 RelatedLessThanOrEqual, 

35) 

36from .reverse_related import ForeignObjectRel, ManyToManyRel, ManyToOneRel, OneToOneRel 

37 

38RECURSIVE_RELATIONSHIP_CONSTANT = "self" 

39 

40 

41def resolve_relation(scope_model, relation): 

42 """ 

43 Transform relation into a model or fully-qualified model string of the form 

44 "package_label.ModelName", relative to scope_model. 

45 

46 The relation argument can be: 

47 * RECURSIVE_RELATIONSHIP_CONSTANT, i.e. the string "self", in which case 

48 the model argument will be returned. 

49 * A bare model name without an package_label, in which case scope_model's 

50 package_label will be prepended. 

51 * An "package_label.ModelName" string. 

52 * A model class, which will be returned unchanged. 

53 """ 

54 # Check for recursive relations 

55 if relation == RECURSIVE_RELATIONSHIP_CONSTANT: 

56 relation = scope_model 

57 

58 # Look for an "app.Model" relation 

59 if isinstance(relation, str): 

60 if "." not in relation: 

61 relation = f"{scope_model._meta.package_label}.{relation}" 

62 

63 return relation 

64 

65 

66def lazy_related_operation(function, model, *related_models, **kwargs): 

67 """ 

68 Schedule `function` to be called once `model` and all `related_models` 

69 have been imported and registered with the app registry. `function` will 

70 be called with the newly-loaded model classes as its positional arguments, 

71 plus any optional keyword arguments. 

72 

73 The `model` argument must be a model class. Each subsequent positional 

74 argument is another model, or a reference to another model - see 

75 `resolve_relation()` for the various forms these may take. Any relative 

76 references will be resolved relative to `model`. 

77 

78 This is a convenience wrapper for `Packages.lazy_model_operation` - the app 

79 registry model used is the one found in `model._meta.packages`. 

80 """ 

81 models = [model] + [resolve_relation(model, rel) for rel in related_models] 

82 model_keys = (make_model_tuple(m) for m in models) 

83 packages = model._meta.packages 

84 return packages.lazy_model_operation(partial(function, **kwargs), *model_keys) 

85 

86 

87class RelatedField(FieldCacheMixin, Field): 

88 """Base class that all relational fields inherit from.""" 

89 

90 # Field flags 

91 one_to_many = False 

92 one_to_one = False 

93 many_to_many = False 

94 many_to_one = False 

95 

96 def __init__( 

97 self, 

98 related_name=None, 

99 related_query_name=None, 

100 limit_choices_to=None, 

101 **kwargs, 

102 ): 

103 self._related_name = related_name 

104 self._related_query_name = related_query_name 

105 self._limit_choices_to = limit_choices_to 

106 super().__init__(**kwargs) 

107 

108 @cached_property 

109 def related_model(self): 

110 # Can't cache this property until all the models are loaded. 

111 packages.check_models_ready() 

112 return self.remote_field.model 

113 

114 def check(self, **kwargs): 

115 return [ 

116 *super().check(**kwargs), 

117 *self._check_related_name_is_valid(), 

118 *self._check_related_query_name_is_valid(), 

119 *self._check_relation_model_exists(), 

120 *self._check_referencing_to_swapped_model(), 

121 *self._check_clashes(), 

122 ] 

123 

124 def _check_related_name_is_valid(self): 

125 import keyword 

126 

127 related_name = self.remote_field.related_name 

128 if related_name is None: 

129 return [] 

130 is_valid_id = ( 

131 not keyword.iskeyword(related_name) and related_name.isidentifier() 

132 ) 

133 if not (is_valid_id or related_name.endswith("+")): 

134 return [ 

135 preflight.Error( 

136 f"The name '{self.remote_field.related_name}' is invalid related_name for field {self.model._meta.object_name}.{self.name}", 

137 hint=( 

138 "Related name must be a valid Python identifier or end with a " 

139 "'+'" 

140 ), 

141 obj=self, 

142 id="fields.E306", 

143 ) 

144 ] 

145 return [] 

146 

147 def _check_related_query_name_is_valid(self): 

148 if self.remote_field.is_hidden(): 

149 return [] 

150 rel_query_name = self.related_query_name() 

151 errors = [] 

152 if rel_query_name.endswith("_"): 

153 errors.append( 

154 preflight.Error( 

155 f"Reverse query name '{rel_query_name}' must not end with an underscore.", 

156 hint=( 

157 "Add or change a related_name or related_query_name " 

158 "argument for this field." 

159 ), 

160 obj=self, 

161 id="fields.E308", 

162 ) 

163 ) 

164 if LOOKUP_SEP in rel_query_name: 

165 errors.append( 

166 preflight.Error( 

167 f"Reverse query name '{rel_query_name}' must not contain '{LOOKUP_SEP}'.", 

168 hint=( 

169 "Add or change a related_name or related_query_name " 

170 "argument for this field." 

171 ), 

172 obj=self, 

173 id="fields.E309", 

174 ) 

175 ) 

176 return errors 

177 

178 def _check_relation_model_exists(self): 

179 rel_is_missing = self.remote_field.model not in self.opts.packages.get_models() 

180 rel_is_string = isinstance(self.remote_field.model, str) 

181 model_name = ( 

182 self.remote_field.model 

183 if rel_is_string 

184 else self.remote_field.model._meta.object_name 

185 ) 

186 if rel_is_missing and ( 

187 rel_is_string or not self.remote_field.model._meta.swapped 

188 ): 

189 return [ 

190 preflight.Error( 

191 f"Field defines a relation with model '{model_name}', which is either " 

192 "not installed, or is abstract.", 

193 obj=self, 

194 id="fields.E300", 

195 ) 

196 ] 

197 return [] 

198 

199 def _check_referencing_to_swapped_model(self): 

200 if ( 

201 self.remote_field.model not in self.opts.packages.get_models() 

202 and not isinstance(self.remote_field.model, str) 

203 and self.remote_field.model._meta.swapped 

204 ): 

205 return [ 

206 preflight.Error( 

207 f"Field defines a relation with the model '{self.remote_field.model._meta.label}', which has " 

208 "been swapped out.", 

209 hint=f"Update the relation to point at 'settings.{self.remote_field.model._meta.swappable}'.", 

210 obj=self, 

211 id="fields.E301", 

212 ) 

213 ] 

214 return [] 

215 

216 def _check_clashes(self): 

217 """Check accessor and reverse query name clashes.""" 

218 from plain.models.base import ModelBase 

219 

220 errors = [] 

221 opts = self.model._meta 

222 

223 # f.remote_field.model may be a string instead of a model. Skip if 

224 # model name is not resolved. 

225 if not isinstance(self.remote_field.model, ModelBase): 

226 return [] 

227 

228 # Consider that we are checking field `Model.foreign` and the models 

229 # are: 

230 # 

231 # class Target(models.Model): 

232 # model = models.IntegerField() 

233 # model_set = models.IntegerField() 

234 # 

235 # class Model(models.Model): 

236 # foreign = models.ForeignKey(Target) 

237 # m2m = models.ManyToManyField(Target) 

238 

239 # rel_opts.object_name == "Target" 

240 rel_opts = self.remote_field.model._meta 

241 # If the field doesn't install a backward relation on the target model 

242 # (so `is_hidden` returns True), then there are no clashes to check 

243 # and we can skip these fields. 

244 rel_is_hidden = self.remote_field.is_hidden() 

245 rel_name = self.remote_field.get_accessor_name() # i. e. "model_set" 

246 rel_query_name = self.related_query_name() # i. e. "model" 

247 # i.e. "package_label.Model.field". 

248 field_name = f"{opts.label}.{self.name}" 

249 

250 # Check clashes between accessor or reverse query name of `field` 

251 # and any other field name -- i.e. accessor for Model.foreign is 

252 # model_set and it clashes with Target.model_set. 

253 potential_clashes = rel_opts.fields + rel_opts.many_to_many 

254 for clash_field in potential_clashes: 

255 # i.e. "package_label.Target.model_set". 

256 clash_name = f"{rel_opts.label}.{clash_field.name}" 

257 if not rel_is_hidden and clash_field.name == rel_name: 

258 errors.append( 

259 preflight.Error( 

260 f"Reverse accessor '{rel_opts.object_name}.{rel_name}' " 

261 f"for '{field_name}' clashes with field name " 

262 f"'{clash_name}'.", 

263 hint=( 

264 f"Rename field '{clash_name}', or add/change a related_name " 

265 f"argument to the definition for field '{field_name}'." 

266 ), 

267 obj=self, 

268 id="fields.E302", 

269 ) 

270 ) 

271 

272 if clash_field.name == rel_query_name: 

273 errors.append( 

274 preflight.Error( 

275 f"Reverse query name for '{field_name}' clashes with field name '{clash_name}'.", 

276 hint=( 

277 f"Rename field '{clash_name}', or add/change a related_name " 

278 f"argument to the definition for field '{field_name}'." 

279 ), 

280 obj=self, 

281 id="fields.E303", 

282 ) 

283 ) 

284 

285 # Check clashes between accessors/reverse query names of `field` and 

286 # any other field accessor -- i. e. Model.foreign accessor clashes with 

287 # Model.m2m accessor. 

288 potential_clashes = (r for r in rel_opts.related_objects if r.field is not self) 

289 for clash_field in potential_clashes: 

290 # i.e. "package_label.Model.m2m". 

291 clash_name = ( 

292 f"{clash_field.related_model._meta.label}.{clash_field.field.name}" 

293 ) 

294 if not rel_is_hidden and clash_field.get_accessor_name() == rel_name: 

295 errors.append( 

296 preflight.Error( 

297 f"Reverse accessor '{rel_opts.object_name}.{rel_name}' " 

298 f"for '{field_name}' clashes with reverse accessor for " 

299 f"'{clash_name}'.", 

300 hint=( 

301 "Add or change a related_name argument " 

302 f"to the definition for '{field_name}' or '{clash_name}'." 

303 ), 

304 obj=self, 

305 id="fields.E304", 

306 ) 

307 ) 

308 

309 if clash_field.get_accessor_name() == rel_query_name: 

310 errors.append( 

311 preflight.Error( 

312 f"Reverse query name for '{field_name}' clashes with reverse query name " 

313 f"for '{clash_name}'.", 

314 hint=( 

315 "Add or change a related_name argument " 

316 f"to the definition for '{field_name}' or '{clash_name}'." 

317 ), 

318 obj=self, 

319 id="fields.E305", 

320 ) 

321 ) 

322 

323 return errors 

324 

325 def db_type(self, connection): 

326 # By default related field will not have a column as it relates to 

327 # columns from another table. 

328 return None 

329 

330 def contribute_to_class(self, cls, name, private_only=False, **kwargs): 

331 super().contribute_to_class(cls, name, private_only=private_only, **kwargs) 

332 

333 self.opts = cls._meta 

334 

335 if not cls._meta.abstract: 

336 if self.remote_field.related_name: 

337 related_name = self.remote_field.related_name 

338 else: 

339 related_name = self.opts.default_related_name 

340 if related_name: 

341 related_name %= { 

342 "class": cls.__name__.lower(), 

343 "model_name": cls._meta.model_name.lower(), 

344 "package_label": cls._meta.package_label.lower(), 

345 } 

346 self.remote_field.related_name = related_name 

347 

348 if self.remote_field.related_query_name: 

349 related_query_name = self.remote_field.related_query_name % { 

350 "class": cls.__name__.lower(), 

351 "package_label": cls._meta.package_label.lower(), 

352 } 

353 self.remote_field.related_query_name = related_query_name 

354 

355 def resolve_related_class(model, related, field): 

356 field.remote_field.model = related 

357 field.do_related_class(related, model) 

358 

359 lazy_related_operation( 

360 resolve_related_class, cls, self.remote_field.model, field=self 

361 ) 

362 

363 def deconstruct(self): 

364 name, path, args, kwargs = super().deconstruct() 

365 if self._limit_choices_to: 

366 kwargs["limit_choices_to"] = self._limit_choices_to 

367 if self._related_name is not None: 

368 kwargs["related_name"] = self._related_name 

369 if self._related_query_name is not None: 

370 kwargs["related_query_name"] = self._related_query_name 

371 return name, path, args, kwargs 

372 

373 def get_forward_related_filter(self, obj): 

374 """ 

375 Return the keyword arguments that when supplied to 

376 self.model.object.filter(), would select all instances related through 

377 this field to the remote obj. This is used to build the querysets 

378 returned by related descriptors. obj is an instance of 

379 self.related_field.model. 

380 """ 

381 return { 

382 f"{self.name}__{rh_field.name}": getattr(obj, rh_field.attname) 

383 for _, rh_field in self.related_fields 

384 } 

385 

386 def get_reverse_related_filter(self, obj): 

387 """ 

388 Complement to get_forward_related_filter(). Return the keyword 

389 arguments that when passed to self.related_field.model.object.filter() 

390 select all instances of self.related_field.model related through 

391 this field to obj. obj is an instance of self.model. 

392 """ 

393 base_q = Q.create( 

394 [ 

395 (rh_field.attname, getattr(obj, lh_field.attname)) 

396 for lh_field, rh_field in self.related_fields 

397 ] 

398 ) 

399 descriptor_filter = self.get_extra_descriptor_filter(obj) 

400 if isinstance(descriptor_filter, dict): 

401 return base_q & Q(**descriptor_filter) 

402 elif descriptor_filter: 

403 return base_q & descriptor_filter 

404 return base_q 

405 

406 @property 

407 def swappable_setting(self): 

408 """ 

409 Get the setting that this is powered from for swapping, or None 

410 if it's not swapped in / marked with swappable=False. 

411 """ 

412 if self.swappable: 

413 # Work out string form of "to" 

414 if isinstance(self.remote_field.model, str): 

415 to_string = self.remote_field.model 

416 else: 

417 to_string = self.remote_field.model._meta.label 

418 return packages.get_swappable_settings_name(to_string) 

419 return None 

420 

421 def set_attributes_from_rel(self): 

422 self.name = self.name or ( 

423 self.remote_field.model._meta.model_name 

424 + "_" 

425 + self.remote_field.model._meta.pk.name 

426 ) 

427 self.remote_field.set_field_name() 

428 

429 def do_related_class(self, other, cls): 

430 self.set_attributes_from_rel() 

431 self.contribute_to_related_class(other, self.remote_field) 

432 

433 def get_limit_choices_to(self): 

434 """ 

435 Return ``limit_choices_to`` for this model field. 

436 

437 If it is a callable, it will be invoked and the result will be 

438 returned. 

439 """ 

440 if callable(self.remote_field.limit_choices_to): 

441 return self.remote_field.limit_choices_to() 

442 return self.remote_field.limit_choices_to 

443 

444 def related_query_name(self): 

445 """ 

446 Define the name that can be used to identify this related object in a 

447 table-spanning query. 

448 """ 

449 return ( 

450 self.remote_field.related_query_name 

451 or self.remote_field.related_name 

452 or self.opts.model_name 

453 ) 

454 

455 @property 

456 def target_field(self): 

457 """ 

458 When filtering against this relation, return the field on the remote 

459 model against which the filtering should happen. 

460 """ 

461 target_fields = self.path_infos[-1].target_fields 

462 if len(target_fields) > 1: 

463 raise exceptions.FieldError( 

464 "The relation has multiple target fields, but only single target field " 

465 "was asked for" 

466 ) 

467 return target_fields[0] 

468 

469 def get_cache_name(self): 

470 return self.name 

471 

472 

473class ForeignObject(RelatedField): 

474 """ 

475 Abstraction of the ForeignKey relation to support multi-column relations. 

476 """ 

477 

478 # Field flags 

479 many_to_many = False 

480 many_to_one = True 

481 one_to_many = False 

482 one_to_one = False 

483 

484 requires_unique_target = True 

485 related_accessor_class = ReverseManyToOneDescriptor 

486 forward_related_accessor_class = ForwardManyToOneDescriptor 

487 rel_class = ForeignObjectRel 

488 

489 def __init__( 

490 self, 

491 to, 

492 on_delete, 

493 from_fields, 

494 to_fields, 

495 rel=None, 

496 related_name=None, 

497 related_query_name=None, 

498 limit_choices_to=None, 

499 parent_link=False, 

500 swappable=True, 

501 **kwargs, 

502 ): 

503 if rel is None: 

504 rel = self.rel_class( 

505 self, 

506 to, 

507 related_name=related_name, 

508 related_query_name=related_query_name, 

509 limit_choices_to=limit_choices_to, 

510 parent_link=parent_link, 

511 on_delete=on_delete, 

512 ) 

513 

514 super().__init__( 

515 rel=rel, 

516 related_name=related_name, 

517 related_query_name=related_query_name, 

518 limit_choices_to=limit_choices_to, 

519 **kwargs, 

520 ) 

521 

522 self.from_fields = from_fields 

523 self.to_fields = to_fields 

524 self.swappable = swappable 

525 

526 def __copy__(self): 

527 obj = super().__copy__() 

528 # Remove any cached PathInfo values. 

529 obj.__dict__.pop("path_infos", None) 

530 obj.__dict__.pop("reverse_path_infos", None) 

531 return obj 

532 

533 def check(self, **kwargs): 

534 return [ 

535 *super().check(**kwargs), 

536 *self._check_to_fields_exist(), 

537 *self._check_unique_target(), 

538 ] 

539 

540 def _check_to_fields_exist(self): 

541 # Skip nonexistent models. 

542 if isinstance(self.remote_field.model, str): 

543 return [] 

544 

545 errors = [] 

546 for to_field in self.to_fields: 

547 if to_field: 

548 try: 

549 self.remote_field.model._meta.get_field(to_field) 

550 except exceptions.FieldDoesNotExist: 

551 errors.append( 

552 preflight.Error( 

553 f"The to_field '{to_field}' doesn't exist on the related " 

554 f"model '{self.remote_field.model._meta.label}'.", 

555 obj=self, 

556 id="fields.E312", 

557 ) 

558 ) 

559 return errors 

560 

561 def _check_unique_target(self): 

562 rel_is_string = isinstance(self.remote_field.model, str) 

563 if rel_is_string or not self.requires_unique_target: 

564 return [] 

565 

566 try: 

567 self.foreign_related_fields 

568 except exceptions.FieldDoesNotExist: 

569 return [] 

570 

571 if not self.foreign_related_fields: 

572 return [] 

573 

574 unique_foreign_fields = { 

575 frozenset([f.name]) 

576 for f in self.remote_field.model._meta.get_fields() 

577 if getattr(f, "unique", False) 

578 } 

579 unique_foreign_fields.update( 

580 { 

581 frozenset(uc.fields) 

582 for uc in self.remote_field.model._meta.total_unique_constraints 

583 } 

584 ) 

585 foreign_fields = {f.name for f in self.foreign_related_fields} 

586 has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields) 

587 

588 if not has_unique_constraint and len(self.foreign_related_fields) > 1: 

589 field_combination = ", ".join( 

590 f"'{rel_field.name}'" for rel_field in self.foreign_related_fields 

591 ) 

592 model_name = self.remote_field.model.__name__ 

593 return [ 

594 preflight.Error( 

595 f"No subset of the fields {field_combination} on model '{model_name}' is unique.", 

596 hint=( 

597 "Mark a single field as unique=True or add a set of " 

598 "fields to a unique constraint (via " 

599 "a UniqueConstraint (without condition) in the " 

600 "model Meta.constraints)." 

601 ), 

602 obj=self, 

603 id="fields.E310", 

604 ) 

605 ] 

606 elif not has_unique_constraint: 

607 field_name = self.foreign_related_fields[0].name 

608 model_name = self.remote_field.model.__name__ 

609 return [ 

610 preflight.Error( 

611 f"'{model_name}.{field_name}' must be unique because it is referenced by " 

612 "a foreign key.", 

613 hint=( 

614 "Add unique=True to this field or add a " 

615 "UniqueConstraint (without condition) in the model " 

616 "Meta.constraints." 

617 ), 

618 obj=self, 

619 id="fields.E311", 

620 ) 

621 ] 

622 else: 

623 return [] 

624 

625 def deconstruct(self): 

626 name, path, args, kwargs = super().deconstruct() 

627 kwargs["on_delete"] = self.remote_field.on_delete 

628 kwargs["from_fields"] = self.from_fields 

629 kwargs["to_fields"] = self.to_fields 

630 

631 if self.remote_field.parent_link: 

632 kwargs["parent_link"] = self.remote_field.parent_link 

633 if isinstance(self.remote_field.model, str): 

634 if "." in self.remote_field.model: 

635 package_label, model_name = self.remote_field.model.split(".") 

636 kwargs["to"] = f"{package_label}.{model_name.lower()}" 

637 else: 

638 kwargs["to"] = self.remote_field.model.lower() 

639 else: 

640 kwargs["to"] = self.remote_field.model._meta.label_lower 

641 # If swappable is True, then see if we're actually pointing to the target 

642 # of a swap. 

643 swappable_setting = self.swappable_setting 

644 if swappable_setting is not None: 

645 # If it's already a settings reference, error 

646 if hasattr(kwargs["to"], "setting_name"): 

647 if kwargs["to"].setting_name != swappable_setting: 

648 raise ValueError( 

649 "Cannot deconstruct a ForeignKey pointing to a model " 

650 "that is swapped in place of more than one model ({} and {})".format( 

651 kwargs["to"].setting_name, swappable_setting 

652 ) 

653 ) 

654 # Set it 

655 kwargs["to"] = SettingsReference( 

656 kwargs["to"], 

657 swappable_setting, 

658 ) 

659 return name, path, args, kwargs 

660 

661 def resolve_related_fields(self): 

662 if not self.from_fields or len(self.from_fields) != len(self.to_fields): 

663 raise ValueError( 

664 "Foreign Object from and to fields must be the same non-zero length" 

665 ) 

666 if isinstance(self.remote_field.model, str): 

667 raise ValueError( 

668 f"Related model {self.remote_field.model!r} cannot be resolved" 

669 ) 

670 related_fields = [] 

671 for index in range(len(self.from_fields)): 

672 from_field_name = self.from_fields[index] 

673 to_field_name = self.to_fields[index] 

674 from_field = ( 

675 self 

676 if from_field_name == RECURSIVE_RELATIONSHIP_CONSTANT 

677 else self.opts.get_field(from_field_name) 

678 ) 

679 to_field = ( 

680 self.remote_field.model._meta.pk 

681 if to_field_name is None 

682 else self.remote_field.model._meta.get_field(to_field_name) 

683 ) 

684 related_fields.append((from_field, to_field)) 

685 return related_fields 

686 

687 @cached_property 

688 def related_fields(self): 

689 return self.resolve_related_fields() 

690 

691 @cached_property 

692 def reverse_related_fields(self): 

693 return [(rhs_field, lhs_field) for lhs_field, rhs_field in self.related_fields] 

694 

695 @cached_property 

696 def local_related_fields(self): 

697 return tuple(lhs_field for lhs_field, rhs_field in self.related_fields) 

698 

699 @cached_property 

700 def foreign_related_fields(self): 

701 return tuple( 

702 rhs_field for lhs_field, rhs_field in self.related_fields if rhs_field 

703 ) 

704 

705 def get_local_related_value(self, instance): 

706 return self.get_instance_value_for_fields(instance, self.local_related_fields) 

707 

708 def get_foreign_related_value(self, instance): 

709 return self.get_instance_value_for_fields(instance, self.foreign_related_fields) 

710 

711 @staticmethod 

712 def get_instance_value_for_fields(instance, fields): 

713 ret = [] 

714 opts = instance._meta 

715 for field in fields: 

716 # Gotcha: in some cases (like fixture loading) a model can have 

717 # different values in parent_ptr_id and parent's id. So, use 

718 # instance.pk (that is, parent_ptr_id) when asked for instance.id. 

719 if field.primary_key: 

720 possible_parent_link = opts.get_ancestor_link(field.model) 

721 if ( 

722 not possible_parent_link 

723 or possible_parent_link.primary_key 

724 or possible_parent_link.model._meta.abstract 

725 ): 

726 ret.append(instance.pk) 

727 continue 

728 ret.append(getattr(instance, field.attname)) 

729 return tuple(ret) 

730 

731 def get_attname_column(self): 

732 attname, column = super().get_attname_column() 

733 return attname, None 

734 

735 def get_joining_columns(self, reverse_join=False): 

736 source = self.reverse_related_fields if reverse_join else self.related_fields 

737 return tuple( 

738 (lhs_field.column, rhs_field.column) for lhs_field, rhs_field in source 

739 ) 

740 

741 def get_reverse_joining_columns(self): 

742 return self.get_joining_columns(reverse_join=True) 

743 

744 def get_extra_descriptor_filter(self, instance): 

745 """ 

746 Return an extra filter condition for related object fetching when 

747 user does 'instance.fieldname', that is the extra filter is used in 

748 the descriptor of the field. 

749 

750 The filter should be either a dict usable in .filter(**kwargs) call or 

751 a Q-object. The condition will be ANDed together with the relation's 

752 joining columns. 

753 

754 A parallel method is get_extra_restriction() which is used in 

755 JOIN and subquery conditions. 

756 """ 

757 return {} 

758 

759 def get_extra_restriction(self, alias, related_alias): 

760 """ 

761 Return a pair condition used for joining and subquery pushdown. The 

762 condition is something that responds to as_sql(compiler, connection) 

763 method. 

764 

765 Note that currently referring both the 'alias' and 'related_alias' 

766 will not work in some conditions, like subquery pushdown. 

767 

768 A parallel method is get_extra_descriptor_filter() which is used in 

769 instance.fieldname related object fetching. 

770 """ 

771 return None 

772 

773 def get_path_info(self, filtered_relation=None): 

774 """Get path from this field to the related model.""" 

775 opts = self.remote_field.model._meta 

776 from_opts = self.model._meta 

777 return [ 

778 PathInfo( 

779 from_opts=from_opts, 

780 to_opts=opts, 

781 target_fields=self.foreign_related_fields, 

782 join_field=self, 

783 m2m=False, 

784 direct=True, 

785 filtered_relation=filtered_relation, 

786 ) 

787 ] 

788 

789 @cached_property 

790 def path_infos(self): 

791 return self.get_path_info() 

792 

793 def get_reverse_path_info(self, filtered_relation=None): 

794 """Get path from the related model to this field's model.""" 

795 opts = self.model._meta 

796 from_opts = self.remote_field.model._meta 

797 return [ 

798 PathInfo( 

799 from_opts=from_opts, 

800 to_opts=opts, 

801 target_fields=(opts.pk,), 

802 join_field=self.remote_field, 

803 m2m=not self.unique, 

804 direct=False, 

805 filtered_relation=filtered_relation, 

806 ) 

807 ] 

808 

809 @cached_property 

810 def reverse_path_infos(self): 

811 return self.get_reverse_path_info() 

812 

813 @classmethod 

814 @functools.cache 

815 def get_class_lookups(cls): 

816 bases = inspect.getmro(cls) 

817 bases = bases[: bases.index(ForeignObject) + 1] 

818 class_lookups = [parent.__dict__.get("class_lookups", {}) for parent in bases] 

819 return cls.merge_dicts(class_lookups) 

820 

821 def contribute_to_class(self, cls, name, private_only=False, **kwargs): 

822 super().contribute_to_class(cls, name, private_only=private_only, **kwargs) 

823 setattr(cls, self.name, self.forward_related_accessor_class(self)) 

824 

825 def contribute_to_related_class(self, cls, related): 

826 # Internal FK's - i.e., those with a related name ending with '+' - 

827 # and swapped models don't get a related descriptor. 

828 if ( 

829 not self.remote_field.is_hidden() 

830 and not related.related_model._meta.swapped 

831 ): 

832 setattr( 

833 cls._meta.concrete_model, 

834 related.get_accessor_name(), 

835 self.related_accessor_class(related), 

836 ) 

837 # While 'limit_choices_to' might be a callable, simply pass 

838 # it along for later - this is too early because it's still 

839 # model load time. 

840 if self.remote_field.limit_choices_to: 

841 cls._meta.related_fkey_lookups.append( 

842 self.remote_field.limit_choices_to 

843 ) 

844 

845 

846ForeignObject.register_lookup(RelatedIn) 

847ForeignObject.register_lookup(RelatedExact) 

848ForeignObject.register_lookup(RelatedLessThan) 

849ForeignObject.register_lookup(RelatedGreaterThan) 

850ForeignObject.register_lookup(RelatedGreaterThanOrEqual) 

851ForeignObject.register_lookup(RelatedLessThanOrEqual) 

852ForeignObject.register_lookup(RelatedIsNull) 

853 

854 

855class ForeignKey(ForeignObject): 

856 """ 

857 Provide a many-to-one relation by adding a column to the local model 

858 to hold the remote value. 

859 

860 By default ForeignKey will target the pk of the remote model but this 

861 behavior can be changed by using the ``to_field`` argument. 

862 """ 

863 

864 descriptor_class = ForeignKeyDeferredAttribute 

865 # Field flags 

866 many_to_many = False 

867 many_to_one = True 

868 one_to_many = False 

869 one_to_one = False 

870 

871 rel_class = ManyToOneRel 

872 

873 empty_strings_allowed = False 

874 default_error_messages = { 

875 "invalid": "%(model)s instance with %(field)s %(value)r does not exist." 

876 } 

877 description = "Foreign Key (type determined by related field)" 

878 

879 def __init__( 

880 self, 

881 to, 

882 on_delete, 

883 related_name=None, 

884 related_query_name=None, 

885 limit_choices_to=None, 

886 parent_link=False, 

887 to_field=None, 

888 db_constraint=True, 

889 **kwargs, 

890 ): 

891 try: 

892 to._meta.model_name 

893 except AttributeError: 

894 if not isinstance(to, str): 

895 raise TypeError( 

896 f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ForeignKey must be " 

897 f"either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}" 

898 ) 

899 else: 

900 # For backwards compatibility purposes, we need to *try* and set 

901 # the to_field during FK construction. It won't be guaranteed to 

902 # be correct until contribute_to_class is called. Refs #12190. 

903 to_field = to_field or (to._meta.pk and to._meta.pk.name) 

904 if not callable(on_delete): 

905 raise TypeError("on_delete must be callable.") 

906 

907 kwargs["rel"] = self.rel_class( 

908 self, 

909 to, 

910 to_field, 

911 related_name=related_name, 

912 related_query_name=related_query_name, 

913 limit_choices_to=limit_choices_to, 

914 parent_link=parent_link, 

915 on_delete=on_delete, 

916 ) 

917 kwargs.setdefault("db_index", True) 

918 

919 super().__init__( 

920 to, 

921 on_delete, 

922 related_name=related_name, 

923 related_query_name=related_query_name, 

924 limit_choices_to=limit_choices_to, 

925 from_fields=[RECURSIVE_RELATIONSHIP_CONSTANT], 

926 to_fields=[to_field], 

927 **kwargs, 

928 ) 

929 self.db_constraint = db_constraint 

930 

931 def __class_getitem__(cls, *args, **kwargs): 

932 return cls 

933 

934 def check(self, **kwargs): 

935 return [ 

936 *super().check(**kwargs), 

937 *self._check_on_delete(), 

938 *self._check_unique(), 

939 ] 

940 

941 def _check_on_delete(self): 

942 on_delete = getattr(self.remote_field, "on_delete", None) 

943 if on_delete == SET_NULL and not self.null: 

944 return [ 

945 preflight.Error( 

946 "Field specifies on_delete=SET_NULL, but cannot be null.", 

947 hint=( 

948 "Set null=True argument on the field, or change the on_delete " 

949 "rule." 

950 ), 

951 obj=self, 

952 id="fields.E320", 

953 ) 

954 ] 

955 elif on_delete == SET_DEFAULT and not self.has_default(): 

956 return [ 

957 preflight.Error( 

958 "Field specifies on_delete=SET_DEFAULT, but has no default value.", 

959 hint="Set a default value, or change the on_delete rule.", 

960 obj=self, 

961 id="fields.E321", 

962 ) 

963 ] 

964 else: 

965 return [] 

966 

967 def _check_unique(self, **kwargs): 

968 return ( 

969 [ 

970 preflight.Warning( 

971 "Setting unique=True on a ForeignKey has the same effect as using " 

972 "a OneToOneField.", 

973 hint=( 

974 "ForeignKey(unique=True) is usually better served by a " 

975 "OneToOneField." 

976 ), 

977 obj=self, 

978 id="fields.W342", 

979 ) 

980 ] 

981 if self.unique 

982 else [] 

983 ) 

984 

985 def deconstruct(self): 

986 name, path, args, kwargs = super().deconstruct() 

987 del kwargs["to_fields"] 

988 del kwargs["from_fields"] 

989 # Handle the simpler arguments 

990 if self.db_index: 

991 del kwargs["db_index"] 

992 else: 

993 kwargs["db_index"] = False 

994 if self.db_constraint is not True: 

995 kwargs["db_constraint"] = self.db_constraint 

996 # Rel needs more work. 

997 to_meta = getattr(self.remote_field.model, "_meta", None) 

998 if self.remote_field.field_name and ( 

999 not to_meta 

1000 or (to_meta.pk and self.remote_field.field_name != to_meta.pk.name) 

1001 ): 

1002 kwargs["to_field"] = self.remote_field.field_name 

1003 return name, path, args, kwargs 

1004 

1005 def to_python(self, value): 

1006 return self.target_field.to_python(value) 

1007 

1008 @property 

1009 def target_field(self): 

1010 return self.foreign_related_fields[0] 

1011 

1012 def validate(self, value, model_instance): 

1013 if self.remote_field.parent_link: 

1014 return 

1015 super().validate(value, model_instance) 

1016 if value is None: 

1017 return 

1018 

1019 using = router.db_for_read(self.remote_field.model, instance=model_instance) 

1020 qs = self.remote_field.model._base_manager.using(using).filter( 

1021 **{self.remote_field.field_name: value} 

1022 ) 

1023 qs = qs.complex_filter(self.get_limit_choices_to()) 

1024 if not qs.exists(): 

1025 raise exceptions.ValidationError( 

1026 self.error_messages["invalid"], 

1027 code="invalid", 

1028 params={ 

1029 "model": self.remote_field.model._meta.model_name, 

1030 "pk": value, 

1031 "field": self.remote_field.field_name, 

1032 "value": value, 

1033 }, # 'pk' is included for backwards compatibility 

1034 ) 

1035 

1036 def resolve_related_fields(self): 

1037 related_fields = super().resolve_related_fields() 

1038 for from_field, to_field in related_fields: 

1039 if ( 

1040 to_field 

1041 and to_field.model != self.remote_field.model._meta.concrete_model 

1042 ): 

1043 raise exceptions.FieldError( 

1044 f"'{self.model._meta.label}.{self.name}' refers to field '{to_field.name}' which is not local to model " 

1045 f"'{self.remote_field.model._meta.concrete_model._meta.label}'." 

1046 ) 

1047 return related_fields 

1048 

1049 def get_attname(self): 

1050 return f"{self.name}_id" 

1051 

1052 def get_attname_column(self): 

1053 attname = self.get_attname() 

1054 column = self.db_column or attname 

1055 return attname, column 

1056 

1057 def get_default(self): 

1058 """Return the to_field if the default value is an object.""" 

1059 field_default = super().get_default() 

1060 if isinstance(field_default, self.remote_field.model): 

1061 return getattr(field_default, self.target_field.attname) 

1062 return field_default 

1063 

1064 def get_db_prep_save(self, value, connection): 

1065 if value is None or ( 

1066 value == "" 

1067 and ( 

1068 not self.target_field.empty_strings_allowed 

1069 or connection.features.interprets_empty_strings_as_nulls 

1070 ) 

1071 ): 

1072 return None 

1073 else: 

1074 return self.target_field.get_db_prep_save(value, connection=connection) 

1075 

1076 def get_db_prep_value(self, value, connection, prepared=False): 

1077 return self.target_field.get_db_prep_value(value, connection, prepared) 

1078 

1079 def get_prep_value(self, value): 

1080 return self.target_field.get_prep_value(value) 

1081 

1082 def contribute_to_related_class(self, cls, related): 

1083 super().contribute_to_related_class(cls, related) 

1084 if self.remote_field.field_name is None: 

1085 self.remote_field.field_name = cls._meta.pk.name 

1086 

1087 def db_check(self, connection): 

1088 return None 

1089 

1090 def db_type(self, connection): 

1091 return self.target_field.rel_db_type(connection=connection) 

1092 

1093 def cast_db_type(self, connection): 

1094 return self.target_field.cast_db_type(connection=connection) 

1095 

1096 def db_parameters(self, connection): 

1097 target_db_parameters = self.target_field.db_parameters(connection) 

1098 return { 

1099 "type": self.db_type(connection), 

1100 "check": self.db_check(connection), 

1101 "collation": target_db_parameters.get("collation"), 

1102 } 

1103 

1104 def convert_empty_strings(self, value, expression, connection): 

1105 if (not value) and isinstance(value, str): 

1106 return None 

1107 return value 

1108 

1109 def get_db_converters(self, connection): 

1110 converters = super().get_db_converters(connection) 

1111 if connection.features.interprets_empty_strings_as_nulls: 

1112 converters += [self.convert_empty_strings] 

1113 return converters 

1114 

1115 def get_col(self, alias, output_field=None): 

1116 if output_field is None: 

1117 output_field = self.target_field 

1118 while isinstance(output_field, ForeignKey): 

1119 output_field = output_field.target_field 

1120 if output_field is self: 

1121 raise ValueError("Cannot resolve output_field.") 

1122 return super().get_col(alias, output_field) 

1123 

1124 

1125class OneToOneField(ForeignKey): 

1126 """ 

1127 A OneToOneField is essentially the same as a ForeignKey, with the exception 

1128 that it always carries a "unique" constraint with it and the reverse 

1129 relation always returns the object pointed to (since there will only ever 

1130 be one), rather than returning a list. 

1131 """ 

1132 

1133 # Field flags 

1134 many_to_many = False 

1135 many_to_one = False 

1136 one_to_many = False 

1137 one_to_one = True 

1138 

1139 related_accessor_class = ReverseOneToOneDescriptor 

1140 forward_related_accessor_class = ForwardOneToOneDescriptor 

1141 rel_class = OneToOneRel 

1142 

1143 description = "One-to-one relationship" 

1144 

1145 def __init__(self, to, on_delete, to_field=None, **kwargs): 

1146 kwargs["unique"] = True 

1147 super().__init__(to, on_delete, to_field=to_field, **kwargs) 

1148 

1149 def deconstruct(self): 

1150 name, path, args, kwargs = super().deconstruct() 

1151 if "unique" in kwargs: 

1152 del kwargs["unique"] 

1153 return name, path, args, kwargs 

1154 

1155 def save_form_data(self, instance, data): 

1156 if isinstance(data, self.remote_field.model): 

1157 setattr(instance, self.name, data) 

1158 else: 

1159 setattr(instance, self.attname, data) 

1160 # Remote field object must be cleared otherwise Model.save() 

1161 # will reassign attname using the related object pk. 

1162 if data is None: 

1163 setattr(instance, self.name, data) 

1164 

1165 def _check_unique(self, **kwargs): 

1166 # Override ForeignKey since check isn't applicable here. 

1167 return [] 

1168 

1169 

1170def create_many_to_many_intermediary_model(field, klass): 

1171 from plain import models 

1172 

1173 def set_managed(model, related, through): 

1174 through._meta.managed = model._meta.managed or related._meta.managed 

1175 

1176 to_model = resolve_relation(klass, field.remote_field.model) 

1177 name = f"{klass._meta.object_name}_{field.name}" 

1178 lazy_related_operation(set_managed, klass, to_model, name) 

1179 

1180 to = make_model_tuple(to_model)[1] 

1181 from_ = klass._meta.model_name 

1182 if to == from_: 

1183 to = f"to_{to}" 

1184 from_ = f"from_{from_}" 

1185 

1186 meta = type( 

1187 "Meta", 

1188 (), 

1189 { 

1190 "db_table": field._get_m2m_db_table(klass._meta), 

1191 "auto_created": klass, 

1192 "package_label": klass._meta.package_label, 

1193 "db_tablespace": klass._meta.db_tablespace, 

1194 "constraints": [ 

1195 models.UniqueConstraint( 

1196 fields=[from_, to], 

1197 name=f"{klass._meta.package_label}_{name.lower()}_unique", 

1198 ) 

1199 ], 

1200 "packages": field.model._meta.packages, 

1201 }, 

1202 ) 

1203 # Construct and return the new class. 

1204 return type( 

1205 name, 

1206 (models.Model,), 

1207 { 

1208 "Meta": meta, 

1209 "__module__": klass.__module__, 

1210 from_: models.ForeignKey( 

1211 klass, 

1212 related_name=f"{name}+", 

1213 db_tablespace=field.db_tablespace, 

1214 db_constraint=field.remote_field.db_constraint, 

1215 on_delete=CASCADE, 

1216 ), 

1217 to: models.ForeignKey( 

1218 to_model, 

1219 related_name=f"{name}+", 

1220 db_tablespace=field.db_tablespace, 

1221 db_constraint=field.remote_field.db_constraint, 

1222 on_delete=CASCADE, 

1223 ), 

1224 }, 

1225 ) 

1226 

1227 

1228class ManyToManyField(RelatedField): 

1229 """ 

1230 Provide a many-to-many relation by using an intermediary model that 

1231 holds two ForeignKey fields pointed at the two sides of the relation. 

1232 

1233 Unless a ``through`` model was provided, ManyToManyField will use the 

1234 create_many_to_many_intermediary_model factory to automatically generate 

1235 the intermediary model. 

1236 """ 

1237 

1238 # Field flags 

1239 many_to_many = True 

1240 many_to_one = False 

1241 one_to_many = False 

1242 one_to_one = False 

1243 

1244 rel_class = ManyToManyRel 

1245 

1246 description = "Many-to-many relationship" 

1247 

1248 def __init__( 

1249 self, 

1250 to, 

1251 related_name=None, 

1252 related_query_name=None, 

1253 limit_choices_to=None, 

1254 symmetrical=None, 

1255 through=None, 

1256 through_fields=None, 

1257 db_constraint=True, 

1258 db_table=None, 

1259 swappable=True, 

1260 **kwargs, 

1261 ): 

1262 try: 

1263 to._meta 

1264 except AttributeError: 

1265 if not isinstance(to, str): 

1266 raise TypeError( 

1267 f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ManyToManyField " 

1268 f"must be either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}" 

1269 ) 

1270 

1271 if symmetrical is None: 

1272 symmetrical = to == RECURSIVE_RELATIONSHIP_CONSTANT 

1273 

1274 if through is not None and db_table is not None: 

1275 raise ValueError( 

1276 "Cannot specify a db_table if an intermediary model is used." 

1277 ) 

1278 

1279 kwargs["rel"] = self.rel_class( 

1280 self, 

1281 to, 

1282 related_name=related_name, 

1283 related_query_name=related_query_name, 

1284 limit_choices_to=limit_choices_to, 

1285 symmetrical=symmetrical, 

1286 through=through, 

1287 through_fields=through_fields, 

1288 db_constraint=db_constraint, 

1289 ) 

1290 self.has_null_arg = "null" in kwargs 

1291 

1292 super().__init__( 

1293 related_name=related_name, 

1294 related_query_name=related_query_name, 

1295 limit_choices_to=limit_choices_to, 

1296 **kwargs, 

1297 ) 

1298 

1299 self.db_table = db_table 

1300 self.swappable = swappable 

1301 

1302 def check(self, **kwargs): 

1303 return [ 

1304 *super().check(**kwargs), 

1305 *self._check_unique(**kwargs), 

1306 *self._check_relationship_model(**kwargs), 

1307 *self._check_ignored_options(**kwargs), 

1308 *self._check_table_uniqueness(**kwargs), 

1309 ] 

1310 

1311 def _check_unique(self, **kwargs): 

1312 if self.unique: 

1313 return [ 

1314 preflight.Error( 

1315 "ManyToManyFields cannot be unique.", 

1316 obj=self, 

1317 id="fields.E330", 

1318 ) 

1319 ] 

1320 return [] 

1321 

1322 def _check_ignored_options(self, **kwargs): 

1323 warnings = [] 

1324 

1325 if self.has_null_arg: 

1326 warnings.append( 

1327 preflight.Warning( 

1328 "null has no effect on ManyToManyField.", 

1329 obj=self, 

1330 id="fields.W340", 

1331 ) 

1332 ) 

1333 

1334 if self._validators: 

1335 warnings.append( 

1336 preflight.Warning( 

1337 "ManyToManyField does not support validators.", 

1338 obj=self, 

1339 id="fields.W341", 

1340 ) 

1341 ) 

1342 if self.remote_field.symmetrical and self._related_name: 

1343 warnings.append( 

1344 preflight.Warning( 

1345 "related_name has no effect on ManyToManyField " 

1346 'with a symmetrical relationship, e.g. to "self".', 

1347 obj=self, 

1348 id="fields.W345", 

1349 ) 

1350 ) 

1351 if self.db_comment: 

1352 warnings.append( 

1353 preflight.Warning( 

1354 "db_comment has no effect on ManyToManyField.", 

1355 obj=self, 

1356 id="fields.W346", 

1357 ) 

1358 ) 

1359 

1360 return warnings 

1361 

1362 def _check_relationship_model(self, from_model=None, **kwargs): 

1363 if hasattr(self.remote_field.through, "_meta"): 

1364 qualified_model_name = f"{self.remote_field.through._meta.package_label}.{self.remote_field.through.__name__}" 

1365 else: 

1366 qualified_model_name = self.remote_field.through 

1367 

1368 errors = [] 

1369 

1370 if self.remote_field.through not in self.opts.packages.get_models( 

1371 include_auto_created=True 

1372 ): 

1373 # The relationship model is not installed. 

1374 errors.append( 

1375 preflight.Error( 

1376 "Field specifies a many-to-many relation through model " 

1377 f"'{qualified_model_name}', which has not been installed.", 

1378 obj=self, 

1379 id="fields.E331", 

1380 ) 

1381 ) 

1382 

1383 else: 

1384 assert from_model is not None, ( 

1385 "ManyToManyField with intermediate " 

1386 "tables cannot be checked if you don't pass the model " 

1387 "where the field is attached to." 

1388 ) 

1389 # Set some useful local variables 

1390 to_model = resolve_relation(from_model, self.remote_field.model) 

1391 from_model_name = from_model._meta.object_name 

1392 if isinstance(to_model, str): 

1393 to_model_name = to_model 

1394 else: 

1395 to_model_name = to_model._meta.object_name 

1396 relationship_model_name = self.remote_field.through._meta.object_name 

1397 self_referential = from_model == to_model 

1398 # Count foreign keys in intermediate model 

1399 if self_referential: 

1400 seen_self = sum( 

1401 from_model == getattr(field.remote_field, "model", None) 

1402 for field in self.remote_field.through._meta.fields 

1403 ) 

1404 

1405 if seen_self > 2 and not self.remote_field.through_fields: 

1406 errors.append( 

1407 preflight.Error( 

1408 "The model is used as an intermediate model by " 

1409 f"'{self}', but it has more than two foreign keys " 

1410 f"to '{from_model_name}', which is ambiguous. You must specify " 

1411 "which two foreign keys Plain should use via the " 

1412 "through_fields keyword argument.", 

1413 hint=( 

1414 "Use through_fields to specify which two foreign keys " 

1415 "Plain should use." 

1416 ), 

1417 obj=self.remote_field.through, 

1418 id="fields.E333", 

1419 ) 

1420 ) 

1421 

1422 else: 

1423 # Count foreign keys in relationship model 

1424 seen_from = sum( 

1425 from_model == getattr(field.remote_field, "model", None) 

1426 for field in self.remote_field.through._meta.fields 

1427 ) 

1428 seen_to = sum( 

1429 to_model == getattr(field.remote_field, "model", None) 

1430 for field in self.remote_field.through._meta.fields 

1431 ) 

1432 

1433 if seen_from > 1 and not self.remote_field.through_fields: 

1434 errors.append( 

1435 preflight.Error( 

1436 ( 

1437 "The model is used as an intermediate model by " 

1438 f"'{self}', but it has more than one foreign key " 

1439 f"from '{from_model_name}', which is ambiguous. You must specify " 

1440 "which foreign key Plain should use via the " 

1441 "through_fields keyword argument." 

1442 ), 

1443 hint=( 

1444 "If you want to create a recursive relationship, " 

1445 f'use ManyToManyField("{RECURSIVE_RELATIONSHIP_CONSTANT}", through="{relationship_model_name}").' 

1446 ), 

1447 obj=self, 

1448 id="fields.E334", 

1449 ) 

1450 ) 

1451 

1452 if seen_to > 1 and not self.remote_field.through_fields: 

1453 errors.append( 

1454 preflight.Error( 

1455 "The model is used as an intermediate model by " 

1456 f"'{self}', but it has more than one foreign key " 

1457 f"to '{to_model_name}', which is ambiguous. You must specify " 

1458 "which foreign key Plain should use via the " 

1459 "through_fields keyword argument.", 

1460 hint=( 

1461 "If you want to create a recursive relationship, " 

1462 f'use ManyToManyField("{RECURSIVE_RELATIONSHIP_CONSTANT}", through="{relationship_model_name}").' 

1463 ), 

1464 obj=self, 

1465 id="fields.E335", 

1466 ) 

1467 ) 

1468 

1469 if seen_from == 0 or seen_to == 0: 

1470 errors.append( 

1471 preflight.Error( 

1472 "The model is used as an intermediate model by " 

1473 f"'{self}', but it does not have a foreign key to '{from_model_name}' or '{to_model_name}'.", 

1474 obj=self.remote_field.through, 

1475 id="fields.E336", 

1476 ) 

1477 ) 

1478 

1479 # Validate `through_fields`. 

1480 if self.remote_field.through_fields is not None: 

1481 # Validate that we're given an iterable of at least two items 

1482 # and that none of them is "falsy". 

1483 if not ( 

1484 len(self.remote_field.through_fields) >= 2 

1485 and self.remote_field.through_fields[0] 

1486 and self.remote_field.through_fields[1] 

1487 ): 

1488 errors.append( 

1489 preflight.Error( 

1490 "Field specifies 'through_fields' but does not provide " 

1491 "the names of the two link fields that should be used " 

1492 f"for the relation through model '{qualified_model_name}'.", 

1493 hint=( 

1494 "Make sure you specify 'through_fields' as " 

1495 "through_fields=('field1', 'field2')" 

1496 ), 

1497 obj=self, 

1498 id="fields.E337", 

1499 ) 

1500 ) 

1501 

1502 # Validate the given through fields -- they should be actual 

1503 # fields on the through model, and also be foreign keys to the 

1504 # expected models. 

1505 else: 

1506 assert from_model is not None, ( 

1507 "ManyToManyField with intermediate " 

1508 "tables cannot be checked if you don't pass the model " 

1509 "where the field is attached to." 

1510 ) 

1511 

1512 source, through, target = ( 

1513 from_model, 

1514 self.remote_field.through, 

1515 self.remote_field.model, 

1516 ) 

1517 source_field_name, target_field_name = self.remote_field.through_fields[ 

1518 :2 

1519 ] 

1520 

1521 for field_name, related_model in ( 

1522 (source_field_name, source), 

1523 (target_field_name, target), 

1524 ): 

1525 possible_field_names = [] 

1526 for f in through._meta.fields: 

1527 if ( 

1528 hasattr(f, "remote_field") 

1529 and getattr(f.remote_field, "model", None) == related_model 

1530 ): 

1531 possible_field_names.append(f.name) 

1532 if possible_field_names: 

1533 hint = ( 

1534 "Did you mean one of the following foreign keys to '{}': " 

1535 "{}?".format( 

1536 related_model._meta.object_name, 

1537 ", ".join(possible_field_names), 

1538 ) 

1539 ) 

1540 else: 

1541 hint = None 

1542 

1543 try: 

1544 field = through._meta.get_field(field_name) 

1545 except exceptions.FieldDoesNotExist: 

1546 errors.append( 

1547 preflight.Error( 

1548 f"The intermediary model '{qualified_model_name}' has no field '{field_name}'.", 

1549 hint=hint, 

1550 obj=self, 

1551 id="fields.E338", 

1552 ) 

1553 ) 

1554 else: 

1555 if not ( 

1556 hasattr(field, "remote_field") 

1557 and getattr(field.remote_field, "model", None) 

1558 == related_model 

1559 ): 

1560 errors.append( 

1561 preflight.Error( 

1562 f"'{through._meta.object_name}.{field_name}' is not a foreign key to '{related_model._meta.object_name}'.", 

1563 hint=hint, 

1564 obj=self, 

1565 id="fields.E339", 

1566 ) 

1567 ) 

1568 

1569 return errors 

1570 

1571 def _check_table_uniqueness(self, **kwargs): 

1572 if ( 

1573 isinstance(self.remote_field.through, str) 

1574 or not self.remote_field.through._meta.managed 

1575 ): 

1576 return [] 

1577 registered_tables = { 

1578 model._meta.db_table: model 

1579 for model in self.opts.packages.get_models(include_auto_created=True) 

1580 if model != self.remote_field.through and model._meta.managed 

1581 } 

1582 m2m_db_table = self.m2m_db_table() 

1583 model = registered_tables.get(m2m_db_table) 

1584 # The second condition allows multiple m2m relations on a model if 

1585 # some point to a through model that proxies another through model. 

1586 if ( 

1587 model 

1588 and model._meta.concrete_model 

1589 != self.remote_field.through._meta.concrete_model 

1590 ): 

1591 if model._meta.auto_created: 

1592 

1593 def _get_field_name(model): 

1594 for field in model._meta.auto_created._meta.many_to_many: 

1595 if field.remote_field.through is model: 

1596 return field.name 

1597 

1598 opts = model._meta.auto_created._meta 

1599 clashing_obj = f"{opts.label}.{_get_field_name(model)}" 

1600 else: 

1601 clashing_obj = model._meta.label 

1602 if settings.DATABASE_ROUTERS: 

1603 error_class, error_id = preflight.Warning, "fields.W344" 

1604 error_hint = ( 

1605 "You have configured settings.DATABASE_ROUTERS. Verify " 

1606 f"that the table of {clashing_obj!r} is correctly routed to a separate " 

1607 "database." 

1608 ) 

1609 else: 

1610 error_class, error_id = preflight.Error, "fields.E340" 

1611 error_hint = None 

1612 return [ 

1613 error_class( 

1614 f"The field's intermediary table '{m2m_db_table}' clashes with the " 

1615 f"table name of '{clashing_obj}'.", 

1616 obj=self, 

1617 hint=error_hint, 

1618 id=error_id, 

1619 ) 

1620 ] 

1621 return [] 

1622 

1623 def deconstruct(self): 

1624 name, path, args, kwargs = super().deconstruct() 

1625 # Handle the simpler arguments. 

1626 if self.db_table is not None: 

1627 kwargs["db_table"] = self.db_table 

1628 if self.remote_field.db_constraint is not True: 

1629 kwargs["db_constraint"] = self.remote_field.db_constraint 

1630 # Lowercase model names as they should be treated as case-insensitive. 

1631 if isinstance(self.remote_field.model, str): 

1632 if "." in self.remote_field.model: 

1633 package_label, model_name = self.remote_field.model.split(".") 

1634 kwargs["to"] = f"{package_label}.{model_name.lower()}" 

1635 else: 

1636 kwargs["to"] = self.remote_field.model.lower() 

1637 else: 

1638 kwargs["to"] = self.remote_field.model._meta.label_lower 

1639 if getattr(self.remote_field, "through", None) is not None: 

1640 if isinstance(self.remote_field.through, str): 

1641 kwargs["through"] = self.remote_field.through 

1642 elif not self.remote_field.through._meta.auto_created: 

1643 kwargs["through"] = self.remote_field.through._meta.label 

1644 # If swappable is True, then see if we're actually pointing to the target 

1645 # of a swap. 

1646 swappable_setting = self.swappable_setting 

1647 if swappable_setting is not None: 

1648 # If it's already a settings reference, error. 

1649 if hasattr(kwargs["to"], "setting_name"): 

1650 if kwargs["to"].setting_name != swappable_setting: 

1651 raise ValueError( 

1652 "Cannot deconstruct a ManyToManyField pointing to a " 

1653 "model that is swapped in place of more than one model " 

1654 "({} and {})".format( 

1655 kwargs["to"].setting_name, swappable_setting 

1656 ) 

1657 ) 

1658 

1659 kwargs["to"] = SettingsReference( 

1660 kwargs["to"], 

1661 swappable_setting, 

1662 ) 

1663 return name, path, args, kwargs 

1664 

1665 def _get_path_info(self, direct=False, filtered_relation=None): 

1666 """Called by both direct and indirect m2m traversal.""" 

1667 int_model = self.remote_field.through 

1668 linkfield1 = int_model._meta.get_field(self.m2m_field_name()) 

1669 linkfield2 = int_model._meta.get_field(self.m2m_reverse_field_name()) 

1670 if direct: 

1671 join1infos = linkfield1.reverse_path_infos 

1672 if filtered_relation: 

1673 join2infos = linkfield2.get_path_info(filtered_relation) 

1674 else: 

1675 join2infos = linkfield2.path_infos 

1676 else: 

1677 join1infos = linkfield2.reverse_path_infos 

1678 if filtered_relation: 

1679 join2infos = linkfield1.get_path_info(filtered_relation) 

1680 else: 

1681 join2infos = linkfield1.path_infos 

1682 # Get join infos between the last model of join 1 and the first model 

1683 # of join 2. Assume the only reason these may differ is due to model 

1684 # inheritance. 

1685 join1_final = join1infos[-1].to_opts 

1686 join2_initial = join2infos[0].from_opts 

1687 if join1_final is join2_initial: 

1688 intermediate_infos = [] 

1689 elif issubclass(join1_final.model, join2_initial.model): 

1690 intermediate_infos = join1_final.get_path_to_parent(join2_initial.model) 

1691 else: 

1692 intermediate_infos = join2_initial.get_path_from_parent(join1_final.model) 

1693 

1694 return [*join1infos, *intermediate_infos, *join2infos] 

1695 

1696 def get_path_info(self, filtered_relation=None): 

1697 return self._get_path_info(direct=True, filtered_relation=filtered_relation) 

1698 

1699 @cached_property 

1700 def path_infos(self): 

1701 return self.get_path_info() 

1702 

1703 def get_reverse_path_info(self, filtered_relation=None): 

1704 return self._get_path_info(direct=False, filtered_relation=filtered_relation) 

1705 

1706 @cached_property 

1707 def reverse_path_infos(self): 

1708 return self.get_reverse_path_info() 

1709 

1710 def _get_m2m_db_table(self, opts): 

1711 """ 

1712 Function that can be curried to provide the m2m table name for this 

1713 relation. 

1714 """ 

1715 if self.remote_field.through is not None: 

1716 return self.remote_field.through._meta.db_table 

1717 elif self.db_table: 

1718 return self.db_table 

1719 else: 

1720 m2m_table_name = f"{utils.strip_quotes(opts.db_table)}_{self.name}" 

1721 return utils.truncate_name(m2m_table_name, connection.ops.max_name_length()) 

1722 

1723 def _get_m2m_attr(self, related, attr): 

1724 """ 

1725 Function that can be curried to provide the source accessor or DB 

1726 column name for the m2m table. 

1727 """ 

1728 cache_attr = f"_m2m_{attr}_cache" 

1729 if hasattr(self, cache_attr): 

1730 return getattr(self, cache_attr) 

1731 if self.remote_field.through_fields is not None: 

1732 link_field_name = self.remote_field.through_fields[0] 

1733 else: 

1734 link_field_name = None 

1735 for f in self.remote_field.through._meta.fields: 

1736 if ( 

1737 f.is_relation 

1738 and f.remote_field.model == related.related_model 

1739 and (link_field_name is None or link_field_name == f.name) 

1740 ): 

1741 setattr(self, cache_attr, getattr(f, attr)) 

1742 return getattr(self, cache_attr) 

1743 

1744 def _get_m2m_reverse_attr(self, related, attr): 

1745 """ 

1746 Function that can be curried to provide the related accessor or DB 

1747 column name for the m2m table. 

1748 """ 

1749 cache_attr = f"_m2m_reverse_{attr}_cache" 

1750 if hasattr(self, cache_attr): 

1751 return getattr(self, cache_attr) 

1752 found = False 

1753 if self.remote_field.through_fields is not None: 

1754 link_field_name = self.remote_field.through_fields[1] 

1755 else: 

1756 link_field_name = None 

1757 for f in self.remote_field.through._meta.fields: 

1758 if f.is_relation and f.remote_field.model == related.model: 

1759 if link_field_name is None and related.related_model == related.model: 

1760 # If this is an m2m-intermediate to self, 

1761 # the first foreign key you find will be 

1762 # the source column. Keep searching for 

1763 # the second foreign key. 

1764 if found: 

1765 setattr(self, cache_attr, getattr(f, attr)) 

1766 break 

1767 else: 

1768 found = True 

1769 elif link_field_name is None or link_field_name == f.name: 

1770 setattr(self, cache_attr, getattr(f, attr)) 

1771 break 

1772 return getattr(self, cache_attr) 

1773 

1774 def contribute_to_class(self, cls, name, **kwargs): 

1775 # To support multiple relations to self, it's useful to have a non-None 

1776 # related name on symmetrical relations for internal reasons. The 

1777 # concept doesn't make a lot of sense externally ("you want me to 

1778 # specify *what* on my non-reversible relation?!"), so we set it up 

1779 # automatically. The funky name reduces the chance of an accidental 

1780 # clash. 

1781 if self.remote_field.symmetrical and ( 

1782 self.remote_field.model == RECURSIVE_RELATIONSHIP_CONSTANT 

1783 or self.remote_field.model == cls._meta.object_name 

1784 ): 

1785 self.remote_field.related_name = f"{name}_rel_+" 

1786 elif self.remote_field.is_hidden(): 

1787 # If the backwards relation is disabled, replace the original 

1788 # related_name with one generated from the m2m field name. Django 

1789 # still uses backwards relations internally and we need to avoid 

1790 # clashes between multiple m2m fields with related_name == '+'. 

1791 self.remote_field.related_name = ( 

1792 f"_{cls._meta.package_label}_{cls.__name__.lower()}_{name}_+" 

1793 ) 

1794 

1795 super().contribute_to_class(cls, name, **kwargs) 

1796 

1797 # The intermediate m2m model is not auto created if: 

1798 # 1) There is a manually specified intermediate, or 

1799 # 2) The class owning the m2m field is abstract. 

1800 # 3) The class owning the m2m field has been swapped out. 

1801 if not cls._meta.abstract: 

1802 if self.remote_field.through: 

1803 

1804 def resolve_through_model(_, model, field): 

1805 field.remote_field.through = model 

1806 

1807 lazy_related_operation( 

1808 resolve_through_model, cls, self.remote_field.through, field=self 

1809 ) 

1810 elif not cls._meta.swapped: 

1811 self.remote_field.through = create_many_to_many_intermediary_model( 

1812 self, cls 

1813 ) 

1814 

1815 # Add the descriptor for the m2m relation. 

1816 setattr(cls, self.name, ManyToManyDescriptor(self.remote_field, reverse=False)) 

1817 

1818 # Set up the accessor for the m2m table name for the relation. 

1819 self.m2m_db_table = partial(self._get_m2m_db_table, cls._meta) 

1820 

1821 def contribute_to_related_class(self, cls, related): 

1822 # Internal M2Ms (i.e., those with a related name ending with '+') 

1823 # and swapped models don't get a related descriptor. 

1824 if ( 

1825 not self.remote_field.is_hidden() 

1826 and not related.related_model._meta.swapped 

1827 ): 

1828 setattr( 

1829 cls, 

1830 related.get_accessor_name(), 

1831 ManyToManyDescriptor(self.remote_field, reverse=True), 

1832 ) 

1833 

1834 # Set up the accessors for the column names on the m2m table. 

1835 self.m2m_column_name = partial(self._get_m2m_attr, related, "column") 

1836 self.m2m_reverse_name = partial(self._get_m2m_reverse_attr, related, "column") 

1837 

1838 self.m2m_field_name = partial(self._get_m2m_attr, related, "name") 

1839 self.m2m_reverse_field_name = partial( 

1840 self._get_m2m_reverse_attr, related, "name" 

1841 ) 

1842 

1843 get_m2m_rel = partial(self._get_m2m_attr, related, "remote_field") 

1844 self.m2m_target_field_name = lambda: get_m2m_rel().field_name 

1845 get_m2m_reverse_rel = partial( 

1846 self._get_m2m_reverse_attr, related, "remote_field" 

1847 ) 

1848 self.m2m_reverse_target_field_name = lambda: get_m2m_reverse_rel().field_name 

1849 

1850 def set_attributes_from_rel(self): 

1851 pass 

1852 

1853 def value_from_object(self, obj): 

1854 return [] if obj.pk is None else list(getattr(obj, self.attname).all()) 

1855 

1856 def save_form_data(self, instance, data): 

1857 getattr(instance, self.attname).set(data) 

1858 

1859 def db_check(self, connection): 

1860 return None 

1861 

1862 def db_type(self, connection): 

1863 # A ManyToManyField is not represented by a single column, 

1864 # so return None. 

1865 return None 

1866 

1867 def db_parameters(self, connection): 

1868 return {"type": None, "check": None}