plain.models 0.38.0__py3-none-any.whl → 0.39.0__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 +19 -0
- plain/models/README.md +3 -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.0.dist-info}/METADATA +4 -1
- {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/RECORD +40 -40
- {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/WHEEL +0 -0
- {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/entry_points.txt +0 -0
- {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/licenses/LICENSE +0 -0
@@ -20,7 +20,7 @@ from .mixins import FieldCacheMixin
|
|
20
20
|
|
21
21
|
class ForeignObjectRel(FieldCacheMixin):
|
22
22
|
"""
|
23
|
-
Used by
|
23
|
+
Used by ForeignKey to store information about the relation.
|
24
24
|
|
25
25
|
``_meta.get_fields()`` returns this class to provide access to the field
|
26
26
|
flags for the reverse relation.
|
@@ -43,7 +43,6 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
43
43
|
related_name=None,
|
44
44
|
related_query_name=None,
|
45
45
|
limit_choices_to=None,
|
46
|
-
parent_link=False,
|
47
46
|
on_delete=None,
|
48
47
|
):
|
49
48
|
self.field = field
|
@@ -51,7 +50,6 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
51
50
|
self.related_name = related_name
|
52
51
|
self.related_query_name = related_query_name
|
53
52
|
self.limit_choices_to = {} if limit_choices_to is None else limit_choices_to
|
54
|
-
self.parent_link = parent_link
|
55
53
|
self.on_delete = on_delete
|
56
54
|
|
57
55
|
self.symmetrical = False
|
@@ -128,7 +126,6 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
128
126
|
self.related_name,
|
129
127
|
self.related_query_name,
|
130
128
|
make_hashable(self.limit_choices_to),
|
131
|
-
self.parent_link,
|
132
129
|
self.on_delete,
|
133
130
|
self.symmetrical,
|
134
131
|
self.multiple,
|
@@ -172,7 +169,7 @@ class ForeignObjectRel(FieldCacheMixin):
|
|
172
169
|
qs = self.related_model._default_manager.complex_filter(limit_choices_to)
|
173
170
|
if ordering:
|
174
171
|
qs = qs.order_by(*ordering)
|
175
|
-
return (blank_choice if include_blank else []) + [(x.
|
172
|
+
return (blank_choice if include_blank else []) + [(x.id, str(x)) for x in qs]
|
176
173
|
|
177
174
|
def is_hidden(self):
|
178
175
|
"""Should the related object be hidden?"""
|
@@ -249,11 +246,9 @@ class ManyToOneRel(ForeignObjectRel):
|
|
249
246
|
self,
|
250
247
|
field,
|
251
248
|
to,
|
252
|
-
field_name,
|
253
249
|
related_name=None,
|
254
250
|
related_query_name=None,
|
255
251
|
limit_choices_to=None,
|
256
|
-
parent_link=False,
|
257
252
|
on_delete=None,
|
258
253
|
):
|
259
254
|
super().__init__(
|
@@ -262,11 +257,10 @@ class ManyToOneRel(ForeignObjectRel):
|
|
262
257
|
related_name=related_name,
|
263
258
|
related_query_name=related_query_name,
|
264
259
|
limit_choices_to=limit_choices_to,
|
265
|
-
parent_link=parent_link,
|
266
260
|
on_delete=on_delete,
|
267
261
|
)
|
268
262
|
|
269
|
-
self.field_name =
|
263
|
+
self.field_name = "id"
|
270
264
|
|
271
265
|
def __getstate__(self):
|
272
266
|
state = super().__getstate__()
|
@@ -281,15 +275,13 @@ class ManyToOneRel(ForeignObjectRel):
|
|
281
275
|
"""
|
282
276
|
Return the Field in the 'to' object to which this relationship is tied.
|
283
277
|
"""
|
284
|
-
field = self.model._meta.get_field(
|
278
|
+
field = self.model._meta.get_field("id")
|
285
279
|
if not field.concrete:
|
286
|
-
raise exceptions.FieldDoesNotExist(
|
287
|
-
f"No related field named '{self.field_name}'"
|
288
|
-
)
|
280
|
+
raise exceptions.FieldDoesNotExist("No related field named 'id'")
|
289
281
|
return field
|
290
282
|
|
291
283
|
def set_field_name(self):
|
292
|
-
|
284
|
+
pass
|
293
285
|
|
294
286
|
|
295
287
|
class ManyToManyRel(ForeignObjectRel):
|
plain/models/forms.py
CHANGED
@@ -37,7 +37,7 @@ def construct_instance(form, instance, fields=None):
|
|
37
37
|
cleaned_data = form.cleaned_data
|
38
38
|
file_field_list = []
|
39
39
|
for f in opts.fields:
|
40
|
-
if isinstance(f, models.
|
40
|
+
if isinstance(f, models.PrimaryKeyField) or f.name not in cleaned_data:
|
41
41
|
continue
|
42
42
|
if fields is not None and f.name not in fields:
|
43
43
|
continue
|
@@ -318,17 +318,6 @@ class BaseModelForm(BaseForm):
|
|
318
318
|
|
319
319
|
exclude = self._get_validation_exclusions()
|
320
320
|
|
321
|
-
# Foreign Keys being used to represent inline relationships
|
322
|
-
# are excluded from basic field value validation. This is for two
|
323
|
-
# reasons: firstly, the value may not be supplied (#12507; the
|
324
|
-
# case of providing new values to the admin); secondly the
|
325
|
-
# object being referred to may not yet fully exist (#12749).
|
326
|
-
# However, these fields *must* be included in uniqueness checks,
|
327
|
-
# so this can't be part of _get_validation_exclusions().
|
328
|
-
for name, field in self.fields.items():
|
329
|
-
if isinstance(field, InlineForeignKeyField):
|
330
|
-
exclude.add(name)
|
331
|
-
|
332
321
|
try:
|
333
322
|
self.instance = construct_instance(self, self.instance, opts.fields)
|
334
323
|
except ValidationError as e:
|
@@ -401,49 +390,6 @@ class ModelForm(BaseModelForm, metaclass=ModelFormMetaclass):
|
|
401
390
|
# Fields #####################################################################
|
402
391
|
|
403
392
|
|
404
|
-
class InlineForeignKeyField(Field):
|
405
|
-
"""
|
406
|
-
A basic integer field that deals with validating the given value to a
|
407
|
-
given parent instance in an inline.
|
408
|
-
"""
|
409
|
-
|
410
|
-
default_error_messages = {
|
411
|
-
"invalid_choice": "The inline value did not match the parent instance.",
|
412
|
-
}
|
413
|
-
|
414
|
-
def __init__(self, parent_instance, *args, pk_field=False, to_field=None, **kwargs):
|
415
|
-
self.parent_instance = parent_instance
|
416
|
-
self.pk_field = pk_field
|
417
|
-
self.to_field = to_field
|
418
|
-
if self.parent_instance is not None:
|
419
|
-
if self.to_field:
|
420
|
-
kwargs["initial"] = getattr(self.parent_instance, self.to_field)
|
421
|
-
else:
|
422
|
-
kwargs["initial"] = self.parent_instance.pk
|
423
|
-
kwargs["required"] = False
|
424
|
-
super().__init__(*args, **kwargs)
|
425
|
-
|
426
|
-
def clean(self, value):
|
427
|
-
if value in self.empty_values:
|
428
|
-
if self.pk_field:
|
429
|
-
return None
|
430
|
-
# if there is no value act as we did before.
|
431
|
-
return self.parent_instance
|
432
|
-
# ensure the we compare the values as equal types.
|
433
|
-
if self.to_field:
|
434
|
-
orig = getattr(self.parent_instance, self.to_field)
|
435
|
-
else:
|
436
|
-
orig = self.parent_instance.pk
|
437
|
-
if str(value) != str(orig):
|
438
|
-
raise ValidationError(
|
439
|
-
self.error_messages["invalid_choice"], code="invalid_choice"
|
440
|
-
)
|
441
|
-
return self.parent_instance
|
442
|
-
|
443
|
-
def has_changed(self, initial, data):
|
444
|
-
return False
|
445
|
-
|
446
|
-
|
447
393
|
class ModelChoiceIteratorValue:
|
448
394
|
def __init__(self, value, instance):
|
449
395
|
self.value = value
|
@@ -509,7 +455,6 @@ class ModelChoiceField(ChoiceField):
|
|
509
455
|
empty_label="---------",
|
510
456
|
required=True,
|
511
457
|
initial=None,
|
512
|
-
to_field_name=None,
|
513
458
|
**kwargs,
|
514
459
|
):
|
515
460
|
# Call Field instead of ChoiceField __init__() because we don't need
|
@@ -525,7 +470,6 @@ class ModelChoiceField(ChoiceField):
|
|
525
470
|
else:
|
526
471
|
self.empty_label = empty_label
|
527
472
|
self.queryset = queryset
|
528
|
-
self.to_field_name = to_field_name
|
529
473
|
|
530
474
|
def __deepcopy__(self, memo):
|
531
475
|
result = super(ChoiceField, self).__deepcopy__(memo)
|
@@ -561,17 +505,14 @@ class ModelChoiceField(ChoiceField):
|
|
561
505
|
|
562
506
|
def prepare_value(self, value):
|
563
507
|
if hasattr(value, "_meta"):
|
564
|
-
|
565
|
-
return value.serializable_value(self.to_field_name)
|
566
|
-
else:
|
567
|
-
return value.pk
|
508
|
+
return value.id
|
568
509
|
return super().prepare_value(value)
|
569
510
|
|
570
511
|
def to_python(self, value):
|
571
512
|
if value in self.empty_values:
|
572
513
|
return None
|
573
514
|
try:
|
574
|
-
key =
|
515
|
+
key = "id"
|
575
516
|
if isinstance(value, self.queryset.model):
|
576
517
|
value = getattr(value, key)
|
577
518
|
value = self.queryset.get(**{key: value})
|
@@ -598,7 +539,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
598
539
|
default_error_messages = {
|
599
540
|
"invalid_list": "Enter a list of values.",
|
600
541
|
"invalid_choice": "Select a valid choice. %(value)s is not one of the available choices.",
|
601
|
-
"
|
542
|
+
"invalid_id_value": "'%(id)s' is not a valid value.",
|
602
543
|
}
|
603
544
|
|
604
545
|
def __init__(self, queryset, **kwargs):
|
@@ -632,7 +573,7 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
632
573
|
corresponding objects. Raise a ValidationError if a given value is
|
633
574
|
invalid (not a valid PK, not in the queryset, etc.)
|
634
575
|
"""
|
635
|
-
key = self.to_field_name or "
|
576
|
+
key = self.to_field_name or "id"
|
636
577
|
# deduplicate given values to avoid creating many querysets or
|
637
578
|
# requiring the database backend deduplicate efficiently.
|
638
579
|
try:
|
@@ -643,19 +584,19 @@ class ModelMultipleChoiceField(ModelChoiceField):
|
|
643
584
|
self.error_messages["invalid_list"],
|
644
585
|
code="invalid_list",
|
645
586
|
)
|
646
|
-
for
|
587
|
+
for id_val in value:
|
647
588
|
try:
|
648
|
-
self.queryset.filter(**{key:
|
589
|
+
self.queryset.filter(**{key: id_val})
|
649
590
|
except (ValueError, TypeError):
|
650
591
|
raise ValidationError(
|
651
|
-
self.error_messages["
|
652
|
-
code="
|
653
|
-
params={"
|
592
|
+
self.error_messages["invalid_id_value"],
|
593
|
+
code="invalid_id_value",
|
594
|
+
params={"id": id_val},
|
654
595
|
)
|
655
596
|
qs = self.queryset.filter(**{f"{key}__in": value})
|
656
|
-
|
597
|
+
ids = {str(getattr(o, key)) for o in qs}
|
657
598
|
for val in value:
|
658
|
-
if str(val) not in
|
599
|
+
if str(val) not in ids:
|
659
600
|
raise ValidationError(
|
660
601
|
self.error_messages["invalid_choice"],
|
661
602
|
code="invalid_choice",
|
@@ -733,10 +674,8 @@ def modelfield_to_formfield(
|
|
733
674
|
# Avoid a circular import
|
734
675
|
from plain import models
|
735
676
|
|
736
|
-
#
|
737
|
-
if isinstance(modelfield, models.
|
738
|
-
modelfield.__class__, models.AutoField
|
739
|
-
):
|
677
|
+
# Primary key fields aren't rendered by default
|
678
|
+
if isinstance(modelfield, models.PrimaryKeyField):
|
740
679
|
return None
|
741
680
|
|
742
681
|
if isinstance(modelfield, models.BooleanField):
|
@@ -784,7 +723,6 @@ def modelfield_to_formfield(
|
|
784
723
|
if isinstance(modelfield, models.ForeignKey):
|
785
724
|
return ModelChoiceField(
|
786
725
|
queryset=modelfield.remote_field.model._default_manager,
|
787
|
-
to_field_name=modelfield.remote_field.field_name,
|
788
726
|
**defaults,
|
789
727
|
)
|
790
728
|
|
plain/models/lookups.py
CHANGED
@@ -319,7 +319,7 @@ class Exact(FieldGetDbPrepValueMixin, BuiltinLookup):
|
|
319
319
|
if self.rhs.has_limit_one():
|
320
320
|
if not self.rhs.has_select_fields:
|
321
321
|
self.rhs.clear_select_clause()
|
322
|
-
self.rhs.add_fields(["
|
322
|
+
self.rhs.add_fields(["id"])
|
323
323
|
else:
|
324
324
|
raise ValueError(
|
325
325
|
"The QuerySet value for an exact lookup must be limited to "
|
@@ -444,7 +444,7 @@ class In(FieldGetDbPrepValueIterableMixin, BuiltinLookup):
|
|
444
444
|
self.rhs.clear_ordering(clear_default=True)
|
445
445
|
if not self.rhs.has_select_fields:
|
446
446
|
self.rhs.clear_select_clause()
|
447
|
-
self.rhs.add_fields(["
|
447
|
+
self.rhs.add_fields(["id"])
|
448
448
|
return super().get_prep_lookup()
|
449
449
|
|
450
450
|
def process_rhs(self, compiler, connection):
|
@@ -536,7 +536,7 @@ class MigrationAutodetector:
|
|
536
536
|
if field.remote_field.model:
|
537
537
|
if field.primary_key:
|
538
538
|
primary_key_rel = field.remote_field.model
|
539
|
-
|
539
|
+
else:
|
540
540
|
related_fields[field_name] = field
|
541
541
|
if getattr(field.remote_field, "through", None):
|
542
542
|
related_fields[field_name] = field
|
@@ -938,31 +938,8 @@ class MigrationAutodetector:
|
|
938
938
|
if remote_field_name:
|
939
939
|
to_field_rename_key = rename_key + (remote_field_name,)
|
940
940
|
if to_field_rename_key in self.renamed_fields:
|
941
|
-
# Repoint
|
942
|
-
# inclusion in ForeignKey.deconstruct() is based on
|
943
|
-
# both.
|
941
|
+
# Repoint model name only
|
944
942
|
new_field.remote_field.model = old_field.remote_field.model
|
945
|
-
new_field.remote_field.field_name = (
|
946
|
-
old_field.remote_field.field_name
|
947
|
-
)
|
948
|
-
# Handle ForeignObjects which can have multiple from_fields/to_fields.
|
949
|
-
from_fields = getattr(new_field, "from_fields", None)
|
950
|
-
if from_fields:
|
951
|
-
from_rename_key = (package_label, model_name)
|
952
|
-
new_field.from_fields = tuple(
|
953
|
-
[
|
954
|
-
self.renamed_fields.get(
|
955
|
-
from_rename_key + (from_field,), from_field
|
956
|
-
)
|
957
|
-
for from_field in from_fields
|
958
|
-
]
|
959
|
-
)
|
960
|
-
new_field.to_fields = tuple(
|
961
|
-
[
|
962
|
-
self.renamed_fields.get(rename_key + (to_field,), to_field)
|
963
|
-
for to_field in new_field.to_fields
|
964
|
-
]
|
965
|
-
)
|
966
943
|
dependencies.extend(
|
967
944
|
self._get_dependencies_for_foreign_key(
|
968
945
|
package_label,
|
@@ -49,12 +49,6 @@ class FieldOperation(Operation):
|
|
49
49
|
if model_name_lower == self.model_name_lower:
|
50
50
|
if name == self.name:
|
51
51
|
return True
|
52
|
-
elif (
|
53
|
-
self.field
|
54
|
-
and hasattr(self.field, "from_fields")
|
55
|
-
and name in self.field.from_fields
|
56
|
-
):
|
57
|
-
return True
|
58
52
|
# Check if this operation remotely references the field.
|
59
53
|
if self.field is None:
|
60
54
|
return False
|
plain/models/migrations/state.py
CHANGED
@@ -281,33 +281,9 @@ class ProjectState:
|
|
281
281
|
f"{package_label}.{model_name} has no field named '{old_name}'"
|
282
282
|
)
|
283
283
|
fields[new_name] = found
|
284
|
-
|
285
|
-
# Fix from_fields to refer to the new field.
|
286
|
-
from_fields = getattr(field, "from_fields", None)
|
287
|
-
if from_fields:
|
288
|
-
field.from_fields = tuple(
|
289
|
-
[
|
290
|
-
new_name if from_field_name == old_name else from_field_name
|
291
|
-
for from_field_name in from_fields
|
292
|
-
]
|
293
|
-
)
|
294
|
-
|
295
|
-
# Fix to_fields to refer to the new field.
|
296
|
-
delay = True
|
284
|
+
# Check if there are any references to this field
|
297
285
|
references = get_references(self, model_key, (old_name, found))
|
298
|
-
|
299
|
-
delay = False
|
300
|
-
if reference.to:
|
301
|
-
remote_field, to_fields = reference.to
|
302
|
-
if getattr(remote_field, "field_name", None) == old_name:
|
303
|
-
remote_field.field_name = new_name
|
304
|
-
if to_fields:
|
305
|
-
field.to_fields = tuple(
|
306
|
-
[
|
307
|
-
new_name if to_field_name == old_name else to_field_name
|
308
|
-
for to_field_name in to_fields
|
309
|
-
]
|
310
|
-
)
|
286
|
+
delay = not bool(references)
|
311
287
|
if self._relations is not None:
|
312
288
|
old_name_lower = old_name.lower()
|
313
289
|
new_name_lower = new_name.lower()
|
plain/models/migrations/utils.py
CHANGED
@@ -72,23 +72,13 @@ def field_references(
|
|
72
72
|
references_to = None
|
73
73
|
references_through = None
|
74
74
|
if resolve_relation(remote_field.model, *model_tuple) == reference_model_tuple:
|
75
|
-
|
75
|
+
# ForeignObject always references 'id'
|
76
76
|
if (
|
77
77
|
reference_field_name is None
|
78
|
-
or
|
79
|
-
|
80
|
-
to_fields is None
|
81
|
-
or
|
82
|
-
# Reference to primary key.
|
83
|
-
(
|
84
|
-
None in to_fields
|
85
|
-
and (reference_field is None or reference_field.primary_key)
|
86
|
-
)
|
87
|
-
or
|
88
|
-
# Reference to field.
|
89
|
-
reference_field_name in to_fields
|
78
|
+
or reference_field_name == "id"
|
79
|
+
or (reference_field is None or reference_field.primary_key)
|
90
80
|
):
|
91
|
-
references_to = (remote_field,
|
81
|
+
references_to = (remote_field, ["id"])
|
92
82
|
through = getattr(remote_field, "through", None)
|
93
83
|
if through and resolve_relation(through, *model_tuple) == reference_model_tuple:
|
94
84
|
through_fields = remote_field.through_fields
|
plain/models/options.py
CHANGED
@@ -8,7 +8,7 @@ from plain.exceptions import FieldDoesNotExist
|
|
8
8
|
from plain.models import models_registry
|
9
9
|
from plain.models.constraints import UniqueConstraint
|
10
10
|
from plain.models.db import db_connection
|
11
|
-
from plain.models.fields import
|
11
|
+
from plain.models.fields import PrimaryKeyField
|
12
12
|
from plain.models.manager import Manager
|
13
13
|
from plain.utils.datastructures import ImmutableList
|
14
14
|
|
@@ -78,8 +78,6 @@ class Options:
|
|
78
78
|
self.required_db_features = []
|
79
79
|
self.required_db_vendor = None
|
80
80
|
self.meta = meta
|
81
|
-
self.pk = None
|
82
|
-
self.auto_field = None
|
83
81
|
|
84
82
|
# For any non-abstract class, the concrete class is the model
|
85
83
|
# in the end of the proxy_for_model chain. In particular, for
|
@@ -170,9 +168,8 @@ class Options:
|
|
170
168
|
return new_objs
|
171
169
|
|
172
170
|
def _prepare(self, model):
|
173
|
-
if
|
174
|
-
|
175
|
-
model.add_to_class("id", auto)
|
171
|
+
if not any(f.name == "id" for f in self.local_fields):
|
172
|
+
model.add_to_class("id", PrimaryKeyField())
|
176
173
|
|
177
174
|
def add_manager(self, manager):
|
178
175
|
self.local_managers.append(manager)
|
@@ -187,7 +184,6 @@ class Options:
|
|
187
184
|
bisect.insort(self.local_many_to_many, field)
|
188
185
|
else:
|
189
186
|
bisect.insort(self.local_fields, field)
|
190
|
-
self.setup_pk(field)
|
191
187
|
|
192
188
|
# If the field being added is a relation to another known field,
|
193
189
|
# expire the cache on this field and the forward cache on the field
|
@@ -210,10 +206,6 @@ class Options:
|
|
210
206
|
else:
|
211
207
|
self._expire_cache(reverse=False)
|
212
208
|
|
213
|
-
def setup_pk(self, field):
|
214
|
-
if not self.pk and field.primary_key:
|
215
|
-
self.pk = field
|
216
|
-
|
217
209
|
def __repr__(self):
|
218
210
|
return f"<Options for {self.object_name}>"
|
219
211
|
|
@@ -624,7 +616,7 @@ class Options:
|
|
624
616
|
@cached_property
|
625
617
|
def _non_pk_concrete_field_names(self):
|
626
618
|
"""
|
627
|
-
Return a set of the non-
|
619
|
+
Return a set of the non-primary key concrete field names defined on the model.
|
628
620
|
"""
|
629
621
|
names = []
|
630
622
|
for field in self.concrete_fields:
|