plain.models 0.38.0__py3-none-any.whl → 0.39.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- plain/models/CHANGELOG.md +31 -0
- plain/models/README.md +31 -0
- plain/models/__init__.py +2 -2
- plain/models/backends/base/creation.py +1 -1
- plain/models/backends/base/operations.py +1 -3
- plain/models/backends/base/schema.py +4 -8
- plain/models/backends/mysql/base.py +1 -3
- plain/models/backends/mysql/introspection.py +2 -6
- plain/models/backends/mysql/operations.py +2 -4
- plain/models/backends/postgresql/base.py +2 -6
- plain/models/backends/postgresql/introspection.py +2 -6
- plain/models/backends/postgresql/operations.py +1 -3
- plain/models/backends/postgresql/schema.py +2 -10
- plain/models/backends/sqlite3/base.py +2 -6
- plain/models/backends/sqlite3/introspection.py +2 -8
- plain/models/base.py +46 -74
- plain/models/constraints.py +3 -3
- plain/models/deletion.py +9 -9
- plain/models/fields/__init__.py +30 -104
- plain/models/fields/related.py +90 -343
- plain/models/fields/related_descriptors.py +14 -14
- plain/models/fields/related_lookups.py +2 -2
- plain/models/fields/reverse_related.py +6 -14
- plain/models/forms.py +14 -76
- plain/models/lookups.py +2 -2
- plain/models/migrations/autodetector.py +2 -25
- plain/models/migrations/operations/fields.py +0 -6
- plain/models/migrations/state.py +2 -26
- plain/models/migrations/utils.py +4 -14
- plain/models/options.py +4 -12
- plain/models/query.py +46 -54
- plain/models/query_utils.py +3 -5
- plain/models/sql/compiler.py +16 -18
- plain/models/sql/query.py +12 -11
- plain/models/sql/subqueries.py +10 -10
- {plain_models-0.38.0.dist-info → plain_models-0.39.1.dist-info}/METADATA +32 -1
- {plain_models-0.38.0.dist-info → plain_models-0.39.1.dist-info}/RECORD +40 -40
- {plain_models-0.38.0.dist-info → plain_models-0.39.1.dist-info}/WHEEL +0 -0
- {plain_models-0.38.0.dist-info → plain_models-0.39.1.dist-info}/entry_points.txt +0 -0
- {plain_models-0.38.0.dist-info → plain_models-0.39.1.dist-info}/licenses/LICENSE +0 -0
plain/models/fields/related.py
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
import functools
|
2
|
-
import inspect
|
3
1
|
from functools import cached_property, partial
|
4
2
|
|
5
3
|
from plain import exceptions, preflight
|
@@ -27,7 +25,7 @@ from .related_lookups import (
|
|
27
25
|
RelatedLessThan,
|
28
26
|
RelatedLessThanOrEqual,
|
29
27
|
)
|
30
|
-
from .reverse_related import
|
28
|
+
from .reverse_related import ManyToManyRel, ManyToOneRel
|
31
29
|
|
32
30
|
RECURSIVE_RELATIONSHIP_CONSTANT = "self"
|
33
31
|
|
@@ -367,25 +365,15 @@ class RelatedField(FieldCacheMixin, Field):
|
|
367
365
|
select all instances of self.related_field.model related through
|
368
366
|
this field to obj. obj is an instance of self.model.
|
369
367
|
"""
|
370
|
-
|
368
|
+
return Q.create(
|
371
369
|
[
|
372
370
|
(rh_field.attname, getattr(obj, lh_field.attname))
|
373
371
|
for lh_field, rh_field in self.related_fields
|
374
372
|
]
|
375
373
|
)
|
376
|
-
descriptor_filter = self.get_extra_descriptor_filter(obj)
|
377
|
-
if isinstance(descriptor_filter, dict):
|
378
|
-
return base_q & Q(**descriptor_filter)
|
379
|
-
elif descriptor_filter:
|
380
|
-
return base_q & descriptor_filter
|
381
|
-
return base_q
|
382
374
|
|
383
375
|
def set_attributes_from_rel(self):
|
384
|
-
self.name = self.name or (
|
385
|
-
self.remote_field.model._meta.model_name
|
386
|
-
+ "_"
|
387
|
-
+ self.remote_field.model._meta.pk.name
|
388
|
-
)
|
376
|
+
self.name = self.name or (self.remote_field.model._meta.model_name + "_" + "id")
|
389
377
|
self.remote_field.set_field_name()
|
390
378
|
|
391
379
|
def do_related_class(self, other, cls):
|
@@ -432,44 +420,58 @@ class RelatedField(FieldCacheMixin, Field):
|
|
432
420
|
return self.name
|
433
421
|
|
434
422
|
|
435
|
-
class
|
436
|
-
"""
|
437
|
-
Abstraction of the ForeignKey relation to support multi-column relations.
|
423
|
+
class ForeignKey(RelatedField):
|
438
424
|
"""
|
425
|
+
Provide a many-to-one relation by adding a column to the local model
|
426
|
+
to hold the remote value.
|
439
427
|
|
440
|
-
|
441
|
-
|
442
|
-
many_to_one = True
|
443
|
-
one_to_many = False
|
428
|
+
ForeignKey targets the primary key (id) of the remote model.
|
429
|
+
"""
|
444
430
|
|
445
|
-
|
431
|
+
descriptor_class = ForeignKeyDeferredAttribute
|
446
432
|
related_accessor_class = ReverseManyToOneDescriptor
|
447
433
|
forward_related_accessor_class = ForwardManyToOneDescriptor
|
448
|
-
rel_class =
|
434
|
+
rel_class = ManyToOneRel
|
435
|
+
|
436
|
+
# Field flags - ForeignKey is many-to-one
|
437
|
+
many_to_one = True
|
438
|
+
|
439
|
+
empty_strings_allowed = False
|
440
|
+
default_error_messages = {
|
441
|
+
"invalid": "%(model)s instance with %(field)s %(value)r does not exist."
|
442
|
+
}
|
443
|
+
description = "Foreign Key (type determined by related field)"
|
449
444
|
|
450
445
|
def __init__(
|
451
446
|
self,
|
452
447
|
to,
|
453
448
|
on_delete,
|
454
|
-
from_fields,
|
455
|
-
to_fields,
|
456
|
-
rel=None,
|
457
449
|
related_name=None,
|
458
450
|
related_query_name=None,
|
459
451
|
limit_choices_to=None,
|
460
|
-
|
452
|
+
db_index=True,
|
453
|
+
db_constraint=True,
|
461
454
|
**kwargs,
|
462
455
|
):
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
)
|
456
|
+
try:
|
457
|
+
to._meta.model_name
|
458
|
+
except AttributeError:
|
459
|
+
if not isinstance(to, str):
|
460
|
+
raise TypeError(
|
461
|
+
f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ForeignKey must be "
|
462
|
+
f"either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
|
463
|
+
)
|
464
|
+
if not callable(on_delete):
|
465
|
+
raise TypeError("on_delete must be callable.")
|
466
|
+
|
467
|
+
rel = self.rel_class(
|
468
|
+
self,
|
469
|
+
to,
|
470
|
+
related_name=related_name,
|
471
|
+
related_query_name=related_query_name,
|
472
|
+
limit_choices_to=limit_choices_to,
|
473
|
+
on_delete=on_delete,
|
474
|
+
)
|
473
475
|
|
474
476
|
super().__init__(
|
475
477
|
rel=rel,
|
@@ -478,9 +480,8 @@ class ForeignObject(RelatedField):
|
|
478
480
|
limit_choices_to=limit_choices_to,
|
479
481
|
**kwargs,
|
480
482
|
)
|
481
|
-
|
482
|
-
self.
|
483
|
-
self.to_fields = to_fields
|
483
|
+
self.db_index = db_index
|
484
|
+
self.db_constraint = db_constraint
|
484
485
|
|
485
486
|
def __copy__(self):
|
486
487
|
obj = super().__copy__()
|
@@ -489,143 +490,6 @@ class ForeignObject(RelatedField):
|
|
489
490
|
obj.__dict__.pop("reverse_path_infos", None)
|
490
491
|
return obj
|
491
492
|
|
492
|
-
def check(self, **kwargs):
|
493
|
-
return [
|
494
|
-
*super().check(**kwargs),
|
495
|
-
*self._check_to_fields_exist(),
|
496
|
-
*self._check_unique_target(),
|
497
|
-
]
|
498
|
-
|
499
|
-
def _check_to_fields_exist(self):
|
500
|
-
# Skip nonexistent models.
|
501
|
-
if isinstance(self.remote_field.model, str):
|
502
|
-
return []
|
503
|
-
|
504
|
-
errors = []
|
505
|
-
for to_field in self.to_fields:
|
506
|
-
if to_field:
|
507
|
-
try:
|
508
|
-
self.remote_field.model._meta.get_field(to_field)
|
509
|
-
except exceptions.FieldDoesNotExist:
|
510
|
-
errors.append(
|
511
|
-
preflight.Error(
|
512
|
-
f"The to_field '{to_field}' doesn't exist on the related "
|
513
|
-
f"model '{self.remote_field.model._meta.label}'.",
|
514
|
-
obj=self,
|
515
|
-
id="fields.E312",
|
516
|
-
)
|
517
|
-
)
|
518
|
-
return errors
|
519
|
-
|
520
|
-
def _check_unique_target(self):
|
521
|
-
rel_is_string = isinstance(self.remote_field.model, str)
|
522
|
-
if rel_is_string or not self.requires_unique_target:
|
523
|
-
return []
|
524
|
-
|
525
|
-
try:
|
526
|
-
self.foreign_related_fields
|
527
|
-
except exceptions.FieldDoesNotExist:
|
528
|
-
return []
|
529
|
-
|
530
|
-
if not self.foreign_related_fields:
|
531
|
-
return []
|
532
|
-
|
533
|
-
unique_foreign_fields = {
|
534
|
-
frozenset([f.name])
|
535
|
-
for f in self.remote_field.model._meta.get_fields()
|
536
|
-
if getattr(f, "primary_key", False)
|
537
|
-
}
|
538
|
-
unique_foreign_fields.update(
|
539
|
-
{
|
540
|
-
frozenset(uc.fields)
|
541
|
-
for uc in self.remote_field.model._meta.total_unique_constraints
|
542
|
-
}
|
543
|
-
)
|
544
|
-
foreign_fields = {f.name for f in self.foreign_related_fields}
|
545
|
-
has_unique_constraint = any(u <= foreign_fields for u in unique_foreign_fields)
|
546
|
-
|
547
|
-
if not has_unique_constraint and len(self.foreign_related_fields) > 1:
|
548
|
-
field_combination = ", ".join(
|
549
|
-
f"'{rel_field.name}'" for rel_field in self.foreign_related_fields
|
550
|
-
)
|
551
|
-
model_name = self.remote_field.model.__name__
|
552
|
-
return [
|
553
|
-
preflight.Error(
|
554
|
-
f"No subset of the fields {field_combination} on model '{model_name}' is unique.",
|
555
|
-
hint=(
|
556
|
-
"Add a set of "
|
557
|
-
"fields to a unique constraint (via "
|
558
|
-
"a UniqueConstraint (without condition) in the "
|
559
|
-
"model Meta.constraints)."
|
560
|
-
),
|
561
|
-
obj=self,
|
562
|
-
id="fields.E310",
|
563
|
-
)
|
564
|
-
]
|
565
|
-
elif not has_unique_constraint:
|
566
|
-
field_name = self.foreign_related_fields[0].name
|
567
|
-
model_name = self.remote_field.model.__name__
|
568
|
-
return [
|
569
|
-
preflight.Error(
|
570
|
-
f"'{model_name}.{field_name}' must be unique because it is referenced by "
|
571
|
-
"a foreign key.",
|
572
|
-
hint=(
|
573
|
-
"Add a UniqueConstraint (without condition) in the model "
|
574
|
-
"Meta.constraints."
|
575
|
-
),
|
576
|
-
obj=self,
|
577
|
-
id="fields.E311",
|
578
|
-
)
|
579
|
-
]
|
580
|
-
else:
|
581
|
-
return []
|
582
|
-
|
583
|
-
def deconstruct(self):
|
584
|
-
name, path, args, kwargs = super().deconstruct()
|
585
|
-
kwargs["on_delete"] = self.remote_field.on_delete
|
586
|
-
kwargs["from_fields"] = self.from_fields
|
587
|
-
kwargs["to_fields"] = self.to_fields
|
588
|
-
|
589
|
-
if self.remote_field.parent_link:
|
590
|
-
kwargs["parent_link"] = self.remote_field.parent_link
|
591
|
-
if isinstance(self.remote_field.model, SettingsReference):
|
592
|
-
kwargs["to"] = self.remote_field.model
|
593
|
-
elif isinstance(self.remote_field.model, str):
|
594
|
-
if "." in self.remote_field.model:
|
595
|
-
package_label, model_name = self.remote_field.model.split(".")
|
596
|
-
kwargs["to"] = f"{package_label}.{model_name.lower()}"
|
597
|
-
else:
|
598
|
-
kwargs["to"] = self.remote_field.model.lower()
|
599
|
-
else:
|
600
|
-
kwargs["to"] = self.remote_field.model._meta.label_lower
|
601
|
-
return name, path, args, kwargs
|
602
|
-
|
603
|
-
def resolve_related_fields(self):
|
604
|
-
if not self.from_fields or len(self.from_fields) != len(self.to_fields):
|
605
|
-
raise ValueError(
|
606
|
-
"Foreign Object from and to fields must be the same non-zero length"
|
607
|
-
)
|
608
|
-
if isinstance(self.remote_field.model, str):
|
609
|
-
raise ValueError(
|
610
|
-
f"Related model {self.remote_field.model!r} cannot be resolved"
|
611
|
-
)
|
612
|
-
related_fields = []
|
613
|
-
for index in range(len(self.from_fields)):
|
614
|
-
from_field_name = self.from_fields[index]
|
615
|
-
to_field_name = self.to_fields[index]
|
616
|
-
from_field = (
|
617
|
-
self
|
618
|
-
if from_field_name == RECURSIVE_RELATIONSHIP_CONSTANT
|
619
|
-
else self.opts.get_field(from_field_name)
|
620
|
-
)
|
621
|
-
to_field = (
|
622
|
-
self.remote_field.model._meta.pk
|
623
|
-
if to_field_name is None
|
624
|
-
else self.remote_field.model._meta.get_field(to_field_name)
|
625
|
-
)
|
626
|
-
related_fields.append((from_field, to_field))
|
627
|
-
return related_fields
|
628
|
-
|
629
493
|
@cached_property
|
630
494
|
def related_fields(self):
|
631
495
|
return self.resolve_related_fields()
|
@@ -645,52 +509,28 @@ class ForeignObject(RelatedField):
|
|
645
509
|
)
|
646
510
|
|
647
511
|
def get_local_related_value(self, instance):
|
648
|
-
|
512
|
+
# Always returns the value of the single local field
|
513
|
+
field = self.local_related_fields[0]
|
514
|
+
if field.primary_key:
|
515
|
+
return (instance.id,)
|
516
|
+
return (getattr(instance, field.attname),)
|
649
517
|
|
650
518
|
def get_foreign_related_value(self, instance):
|
651
|
-
|
652
|
-
|
653
|
-
@staticmethod
|
654
|
-
def get_instance_value_for_fields(instance, fields):
|
655
|
-
ret = []
|
656
|
-
for field in fields:
|
657
|
-
# Gotcha: in some cases (like fixture loading) a model can have
|
658
|
-
# different values in parent_ptr_id and parent's id. So, use
|
659
|
-
# instance.pk (that is, parent_ptr_id) when asked for instance.id.
|
660
|
-
if field.primary_key:
|
661
|
-
ret.append(instance.pk)
|
662
|
-
continue
|
663
|
-
ret.append(getattr(instance, field.attname))
|
664
|
-
return tuple(ret)
|
665
|
-
|
666
|
-
def get_attname_column(self):
|
667
|
-
attname, column = super().get_attname_column()
|
668
|
-
return attname, None
|
519
|
+
# Always returns the id of the foreign instance
|
520
|
+
return (instance.id,)
|
669
521
|
|
670
522
|
def get_joining_columns(self, reverse_join=False):
|
671
|
-
|
672
|
-
|
673
|
-
|
674
|
-
|
523
|
+
# Always returns a single column pair
|
524
|
+
if reverse_join:
|
525
|
+
from_field, to_field = self.related_fields[0]
|
526
|
+
return ((to_field.column, from_field.column),)
|
527
|
+
else:
|
528
|
+
from_field, to_field = self.related_fields[0]
|
529
|
+
return ((from_field.column, to_field.column),)
|
675
530
|
|
676
531
|
def get_reverse_joining_columns(self):
|
677
532
|
return self.get_joining_columns(reverse_join=True)
|
678
533
|
|
679
|
-
def get_extra_descriptor_filter(self, instance):
|
680
|
-
"""
|
681
|
-
Return an extra filter condition for related object fetching when
|
682
|
-
user does 'instance.fieldname', that is the extra filter is used in
|
683
|
-
the descriptor of the field.
|
684
|
-
|
685
|
-
The filter should be either a dict usable in .filter(**kwargs) call or
|
686
|
-
a Q-object. The condition will be ANDed together with the relation's
|
687
|
-
joining columns.
|
688
|
-
|
689
|
-
A parallel method is get_extra_restriction() which is used in
|
690
|
-
JOIN and subquery conditions.
|
691
|
-
"""
|
692
|
-
return {}
|
693
|
-
|
694
534
|
def get_extra_restriction(self, alias, related_alias):
|
695
535
|
"""
|
696
536
|
Return a pair condition used for joining and subquery pushdown. The
|
@@ -733,7 +573,7 @@ class ForeignObject(RelatedField):
|
|
733
573
|
PathInfo(
|
734
574
|
from_opts=from_opts,
|
735
575
|
to_opts=opts,
|
736
|
-
target_fields=(opts.
|
576
|
+
target_fields=(opts.get_field("id"),),
|
737
577
|
join_field=self.remote_field,
|
738
578
|
m2m=not self.primary_key,
|
739
579
|
direct=False,
|
@@ -745,14 +585,6 @@ class ForeignObject(RelatedField):
|
|
745
585
|
def reverse_path_infos(self):
|
746
586
|
return self.get_reverse_path_info()
|
747
587
|
|
748
|
-
@classmethod
|
749
|
-
@functools.cache
|
750
|
-
def get_class_lookups(cls):
|
751
|
-
bases = inspect.getmro(cls)
|
752
|
-
bases = bases[: bases.index(ForeignObject) + 1]
|
753
|
-
class_lookups = [parent.__dict__.get("class_lookups", {}) for parent in bases]
|
754
|
-
return cls.merge_dicts(class_lookups)
|
755
|
-
|
756
588
|
def contribute_to_class(self, cls, name, private_only=False, **kwargs):
|
757
589
|
super().contribute_to_class(cls, name, private_only=private_only, **kwargs)
|
758
590
|
setattr(cls, self.name, self.forward_related_accessor_class(self))
|
@@ -773,95 +605,6 @@ class ForeignObject(RelatedField):
|
|
773
605
|
self.remote_field.limit_choices_to
|
774
606
|
)
|
775
607
|
|
776
|
-
|
777
|
-
ForeignObject.register_lookup(RelatedIn)
|
778
|
-
ForeignObject.register_lookup(RelatedExact)
|
779
|
-
ForeignObject.register_lookup(RelatedLessThan)
|
780
|
-
ForeignObject.register_lookup(RelatedGreaterThan)
|
781
|
-
ForeignObject.register_lookup(RelatedGreaterThanOrEqual)
|
782
|
-
ForeignObject.register_lookup(RelatedLessThanOrEqual)
|
783
|
-
ForeignObject.register_lookup(RelatedIsNull)
|
784
|
-
|
785
|
-
|
786
|
-
class ForeignKey(ForeignObject):
|
787
|
-
"""
|
788
|
-
Provide a many-to-one relation by adding a column to the local model
|
789
|
-
to hold the remote value.
|
790
|
-
|
791
|
-
By default ForeignKey will target the pk of the remote model but this
|
792
|
-
behavior can be changed by using the ``to_field`` argument.
|
793
|
-
"""
|
794
|
-
|
795
|
-
descriptor_class = ForeignKeyDeferredAttribute
|
796
|
-
# Field flags
|
797
|
-
many_to_many = False
|
798
|
-
many_to_one = True
|
799
|
-
one_to_many = False
|
800
|
-
|
801
|
-
rel_class = ManyToOneRel
|
802
|
-
|
803
|
-
empty_strings_allowed = False
|
804
|
-
default_error_messages = {
|
805
|
-
"invalid": "%(model)s instance with %(field)s %(value)r does not exist."
|
806
|
-
}
|
807
|
-
description = "Foreign Key (type determined by related field)"
|
808
|
-
|
809
|
-
def __init__(
|
810
|
-
self,
|
811
|
-
to,
|
812
|
-
on_delete,
|
813
|
-
related_name=None,
|
814
|
-
related_query_name=None,
|
815
|
-
limit_choices_to=None,
|
816
|
-
parent_link=False,
|
817
|
-
to_field=None,
|
818
|
-
db_index=True,
|
819
|
-
db_constraint=True,
|
820
|
-
**kwargs,
|
821
|
-
):
|
822
|
-
try:
|
823
|
-
to._meta.model_name
|
824
|
-
except AttributeError:
|
825
|
-
if not isinstance(to, str):
|
826
|
-
raise TypeError(
|
827
|
-
f"{self.__class__.__name__}({to!r}) is invalid. First parameter to ForeignKey must be "
|
828
|
-
f"either a model, a model name, or the string {RECURSIVE_RELATIONSHIP_CONSTANT!r}"
|
829
|
-
)
|
830
|
-
else:
|
831
|
-
# For backwards compatibility purposes, we need to *try* and set
|
832
|
-
# the to_field during FK construction. It won't be guaranteed to
|
833
|
-
# be correct until contribute_to_class is called. Refs #12190.
|
834
|
-
to_field = to_field or (to._meta.pk and to._meta.pk.name)
|
835
|
-
if not callable(on_delete):
|
836
|
-
raise TypeError("on_delete must be callable.")
|
837
|
-
|
838
|
-
kwargs["rel"] = self.rel_class(
|
839
|
-
self,
|
840
|
-
to,
|
841
|
-
to_field,
|
842
|
-
related_name=related_name,
|
843
|
-
related_query_name=related_query_name,
|
844
|
-
limit_choices_to=limit_choices_to,
|
845
|
-
parent_link=parent_link,
|
846
|
-
on_delete=on_delete,
|
847
|
-
)
|
848
|
-
|
849
|
-
super().__init__(
|
850
|
-
to,
|
851
|
-
on_delete,
|
852
|
-
related_name=related_name,
|
853
|
-
related_query_name=related_query_name,
|
854
|
-
limit_choices_to=limit_choices_to,
|
855
|
-
from_fields=[RECURSIVE_RELATIONSHIP_CONSTANT],
|
856
|
-
to_fields=[to_field],
|
857
|
-
**kwargs,
|
858
|
-
)
|
859
|
-
self.db_index = db_index
|
860
|
-
self.db_constraint = db_constraint
|
861
|
-
|
862
|
-
def __class_getitem__(cls, *args, **kwargs):
|
863
|
-
return cls
|
864
|
-
|
865
608
|
def check(self, **kwargs):
|
866
609
|
return [
|
867
610
|
*super().check(**kwargs),
|
@@ -896,8 +639,18 @@ class ForeignKey(ForeignObject):
|
|
896
639
|
|
897
640
|
def deconstruct(self):
|
898
641
|
name, path, args, kwargs = super().deconstruct()
|
899
|
-
|
900
|
-
|
642
|
+
kwargs["on_delete"] = self.remote_field.on_delete
|
643
|
+
|
644
|
+
if isinstance(self.remote_field.model, SettingsReference):
|
645
|
+
kwargs["to"] = self.remote_field.model
|
646
|
+
elif isinstance(self.remote_field.model, str):
|
647
|
+
if "." in self.remote_field.model:
|
648
|
+
package_label, model_name = self.remote_field.model.split(".")
|
649
|
+
kwargs["to"] = f"{package_label}.{model_name.lower()}"
|
650
|
+
else:
|
651
|
+
kwargs["to"] = self.remote_field.model.lower()
|
652
|
+
else:
|
653
|
+
kwargs["to"] = self.remote_field.model._meta.label_lower
|
901
654
|
|
902
655
|
if self.db_index is not True:
|
903
656
|
kwargs["db_index"] = self.db_index
|
@@ -905,13 +658,6 @@ class ForeignKey(ForeignObject):
|
|
905
658
|
if self.db_constraint is not True:
|
906
659
|
kwargs["db_constraint"] = self.db_constraint
|
907
660
|
|
908
|
-
# Rel needs more work.
|
909
|
-
to_meta = getattr(self.remote_field.model, "_meta", None)
|
910
|
-
if self.remote_field.field_name and (
|
911
|
-
not to_meta
|
912
|
-
or (to_meta.pk and self.remote_field.field_name != to_meta.pk.name)
|
913
|
-
):
|
914
|
-
kwargs["to_field"] = self.remote_field.field_name
|
915
661
|
return name, path, args, kwargs
|
916
662
|
|
917
663
|
def to_python(self, value):
|
@@ -922,8 +668,6 @@ class ForeignKey(ForeignObject):
|
|
922
668
|
return self.foreign_related_fields[0]
|
923
669
|
|
924
670
|
def validate(self, value, model_instance):
|
925
|
-
if self.remote_field.parent_link:
|
926
|
-
return
|
927
671
|
super().validate(value, model_instance)
|
928
672
|
if value is None:
|
929
673
|
return
|
@@ -938,14 +682,21 @@ class ForeignKey(ForeignObject):
|
|
938
682
|
code="invalid",
|
939
683
|
params={
|
940
684
|
"model": self.remote_field.model._meta.model_name,
|
941
|
-
"
|
685
|
+
"id": value,
|
942
686
|
"field": self.remote_field.field_name,
|
943
687
|
"value": value,
|
944
|
-
},
|
688
|
+
},
|
945
689
|
)
|
946
690
|
|
947
691
|
def resolve_related_fields(self):
|
948
|
-
|
692
|
+
if isinstance(self.remote_field.model, str):
|
693
|
+
raise ValueError(
|
694
|
+
f"Related model {self.remote_field.model!r} cannot be resolved"
|
695
|
+
)
|
696
|
+
from_field = self
|
697
|
+
to_field = self.remote_field.model._meta.get_field("id")
|
698
|
+
related_fields = [(from_field, to_field)]
|
699
|
+
|
949
700
|
for from_field, to_field in related_fields:
|
950
701
|
if (
|
951
702
|
to_field
|
@@ -986,11 +737,6 @@ class ForeignKey(ForeignObject):
|
|
986
737
|
def get_prep_value(self, value):
|
987
738
|
return self.target_field.get_prep_value(value)
|
988
739
|
|
989
|
-
def contribute_to_related_class(self, cls, related):
|
990
|
-
super().contribute_to_related_class(cls, related)
|
991
|
-
if self.remote_field.field_name is None:
|
992
|
-
self.remote_field.field_name = cls._meta.pk.name
|
993
|
-
|
994
740
|
def db_check(self, connection):
|
995
741
|
return None
|
996
742
|
|
@@ -1008,15 +754,6 @@ class ForeignKey(ForeignObject):
|
|
1008
754
|
"collation": target_db_parameters.get("collation"),
|
1009
755
|
}
|
1010
756
|
|
1011
|
-
def convert_empty_strings(self, value, expression, connection):
|
1012
|
-
if (not value) and isinstance(value, str):
|
1013
|
-
return None
|
1014
|
-
return value
|
1015
|
-
|
1016
|
-
def get_db_converters(self, connection):
|
1017
|
-
converters = super().get_db_converters(connection)
|
1018
|
-
return converters
|
1019
|
-
|
1020
757
|
def get_col(self, alias, output_field=None):
|
1021
758
|
if output_field is None:
|
1022
759
|
output_field = self.target_field
|
@@ -1027,6 +764,16 @@ class ForeignKey(ForeignObject):
|
|
1027
764
|
return super().get_col(alias, output_field)
|
1028
765
|
|
1029
766
|
|
767
|
+
# Register lookups for ForeignKey
|
768
|
+
ForeignKey.register_lookup(RelatedIn)
|
769
|
+
ForeignKey.register_lookup(RelatedExact)
|
770
|
+
ForeignKey.register_lookup(RelatedLessThan)
|
771
|
+
ForeignKey.register_lookup(RelatedGreaterThan)
|
772
|
+
ForeignKey.register_lookup(RelatedGreaterThanOrEqual)
|
773
|
+
ForeignKey.register_lookup(RelatedLessThanOrEqual)
|
774
|
+
ForeignKey.register_lookup(RelatedIsNull)
|
775
|
+
|
776
|
+
|
1030
777
|
class ManyToManyField(RelatedField):
|
1031
778
|
"""
|
1032
779
|
Provide a many-to-many relation by using an intermediary model that
|
@@ -1557,7 +1304,7 @@ class ManyToManyField(RelatedField):
|
|
1557
1304
|
pass
|
1558
1305
|
|
1559
1306
|
def value_from_object(self, obj):
|
1560
|
-
return [] if obj.
|
1307
|
+
return [] if obj.id is None else list(getattr(obj, self.attname).all())
|
1561
1308
|
|
1562
1309
|
def save_form_data(self, instance, data):
|
1563
1310
|
getattr(instance, self.attname).set(data)
|
@@ -408,10 +408,10 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
|
408
408
|
pass # nothing to clear from cache
|
409
409
|
|
410
410
|
def get_queryset(self):
|
411
|
-
# Even if this relation is not to
|
411
|
+
# Even if this relation is not to primary key, we require still primary key value.
|
412
412
|
# The wish is that the instance has been already saved to DB,
|
413
|
-
# although having a
|
414
|
-
if self.instance.
|
413
|
+
# although having a primary key value isn't a guarantee of that.
|
414
|
+
if self.instance.id is None:
|
415
415
|
raise ValueError(
|
416
416
|
f"{self.instance.__class__.__name__!r} instance needs to have a "
|
417
417
|
f"primary key value before this relationship can be used."
|
@@ -456,7 +456,7 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
|
456
456
|
setattr(obj, self.field.name, self.instance)
|
457
457
|
|
458
458
|
if bulk:
|
459
|
-
|
459
|
+
ids = []
|
460
460
|
for obj in objs:
|
461
461
|
check_and_update_obj(obj)
|
462
462
|
if obj._state.adding:
|
@@ -464,8 +464,8 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
|
464
464
|
f"{obj!r} instance isn't saved. Use bulk=False or save "
|
465
465
|
"the object first."
|
466
466
|
)
|
467
|
-
|
468
|
-
self.model._base_manager.filter(
|
467
|
+
ids.append(obj.id)
|
468
|
+
self.model._base_manager.filter(id__in=ids).update(
|
469
469
|
**{
|
470
470
|
self.field.name: self.instance,
|
471
471
|
}
|
@@ -508,12 +508,12 @@ def create_reverse_many_to_one_manager(superclass, rel):
|
|
508
508
|
)
|
509
509
|
# Is obj actually part of this descriptor set?
|
510
510
|
if self.field.get_local_related_value(obj) == val:
|
511
|
-
old_ids.add(obj.
|
511
|
+
old_ids.add(obj.id)
|
512
512
|
else:
|
513
513
|
raise self.field.remote_field.model.DoesNotExist(
|
514
514
|
f"{obj!r} is not related to {self.instance!r}."
|
515
515
|
)
|
516
|
-
self._clear(self.filter(
|
516
|
+
self._clear(self.filter(id__in=old_ids), bulk)
|
517
517
|
|
518
518
|
def clear(self, *, bulk=True):
|
519
519
|
self._check_fk_val()
|
@@ -641,22 +641,22 @@ def create_forward_many_to_many_manager(superclass, rel, reverse):
|
|
641
641
|
self.target_field = self.through._meta.get_field(self.target_field_name)
|
642
642
|
|
643
643
|
self.core_filters = {}
|
644
|
-
self.
|
644
|
+
self.id_field_names = {}
|
645
645
|
for lh_field, rh_field in self.source_field.related_fields:
|
646
646
|
core_filter_key = f"{self.query_field_name}__{rh_field.name}"
|
647
647
|
self.core_filters[core_filter_key] = getattr(instance, rh_field.attname)
|
648
|
-
self.
|
648
|
+
self.id_field_names[lh_field.name] = rh_field.name
|
649
649
|
|
650
650
|
self.related_val = self.source_field.get_foreign_related_value(instance)
|
651
651
|
if None in self.related_val:
|
652
652
|
raise ValueError(
|
653
|
-
f'"{instance!r}" needs to have a value for field "{self.
|
653
|
+
f'"{instance!r}" needs to have a value for field "{self.id_field_names[self.source_field_name]}" before '
|
654
654
|
"this many-to-many relationship can be used."
|
655
655
|
)
|
656
|
-
# Even if this relation is not to
|
656
|
+
# Even if this relation is not to primary key, we require still primary key value.
|
657
657
|
# The wish is that the instance has been already saved to DB,
|
658
|
-
# although having a
|
659
|
-
if instance.
|
658
|
+
# although having a primary key value isn't a guarantee of that.
|
659
|
+
if instance.id is None:
|
660
660
|
raise ValueError(
|
661
661
|
f"{instance.__class__.__name__!r} instance needs to have a primary key value before "
|
662
662
|
"a many-to-many relationship can be used."
|
@@ -41,7 +41,7 @@ def get_normalized_value(value, lhs):
|
|
41
41
|
from plain.models import Model
|
42
42
|
|
43
43
|
if isinstance(value, Model):
|
44
|
-
if value.
|
44
|
+
if value.id is None:
|
45
45
|
raise ValueError("Model instances passed to related filters must be saved.")
|
46
46
|
value_list = []
|
47
47
|
sources = lhs.output_field.path_infos[-1].target_fields
|
@@ -55,7 +55,7 @@ def get_normalized_value(value, lhs):
|
|
55
55
|
except AttributeError:
|
56
56
|
# A case like Restaurant.objects.filter(place=restaurant_instance),
|
57
57
|
# where place is a OneToOneField and the primary key of Restaurant.
|
58
|
-
return (value.
|
58
|
+
return (value.id,)
|
59
59
|
return tuple(value_list)
|
60
60
|
if not isinstance(value, tuple):
|
61
61
|
return (value,)
|