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.
Files changed (40) hide show
  1. plain/models/CHANGELOG.md +19 -0
  2. plain/models/README.md +3 -0
  3. plain/models/__init__.py +2 -2
  4. plain/models/backends/base/creation.py +1 -1
  5. plain/models/backends/base/operations.py +1 -3
  6. plain/models/backends/base/schema.py +4 -8
  7. plain/models/backends/mysql/base.py +1 -3
  8. plain/models/backends/mysql/introspection.py +2 -6
  9. plain/models/backends/mysql/operations.py +2 -4
  10. plain/models/backends/postgresql/base.py +2 -6
  11. plain/models/backends/postgresql/introspection.py +2 -6
  12. plain/models/backends/postgresql/operations.py +1 -3
  13. plain/models/backends/postgresql/schema.py +2 -10
  14. plain/models/backends/sqlite3/base.py +2 -6
  15. plain/models/backends/sqlite3/introspection.py +2 -8
  16. plain/models/base.py +46 -74
  17. plain/models/constraints.py +3 -3
  18. plain/models/deletion.py +9 -9
  19. plain/models/fields/__init__.py +30 -104
  20. plain/models/fields/related.py +90 -343
  21. plain/models/fields/related_descriptors.py +14 -14
  22. plain/models/fields/related_lookups.py +2 -2
  23. plain/models/fields/reverse_related.py +6 -14
  24. plain/models/forms.py +14 -76
  25. plain/models/lookups.py +2 -2
  26. plain/models/migrations/autodetector.py +2 -25
  27. plain/models/migrations/operations/fields.py +0 -6
  28. plain/models/migrations/state.py +2 -26
  29. plain/models/migrations/utils.py +4 -14
  30. plain/models/options.py +4 -12
  31. plain/models/query.py +46 -54
  32. plain/models/query_utils.py +3 -5
  33. plain/models/sql/compiler.py +16 -18
  34. plain/models/sql/query.py +12 -11
  35. plain/models/sql/subqueries.py +10 -10
  36. {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/METADATA +4 -1
  37. {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/RECORD +40 -40
  38. {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/WHEEL +0 -0
  39. {plain_models-0.38.0.dist-info → plain_models-0.39.0.dist-info}/entry_points.txt +0 -0
  40. {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 ForeignObject to store information about the relation.
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.pk, str(x)) for x in qs]
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 = 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(self.field_name)
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
- self.field_name = self.field_name or self.model._meta.pk.name
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.AutoField) or f.name not in cleaned_data:
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
- if self.to_field_name:
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 = self.to_field_name or "pk"
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
- "invalid_pk_value": "“%(pk)s is not a valid value.",
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 "pk"
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 pk in value:
587
+ for id_val in value:
647
588
  try:
648
- self.queryset.filter(**{key: pk})
589
+ self.queryset.filter(**{key: id_val})
649
590
  except (ValueError, TypeError):
650
591
  raise ValidationError(
651
- self.error_messages["invalid_pk_value"],
652
- code="invalid_pk_value",
653
- params={"pk": pk},
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
- pks = {str(getattr(o, key)) for o in qs}
597
+ ids = {str(getattr(o, key)) for o in qs}
657
598
  for val in value:
658
- if str(val) not in pks:
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
- # Auto fields aren't rendered by default
737
- if isinstance(modelfield, models.AutoField) or issubclass(
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(["pk"])
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(["pk"])
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
- elif not field.remote_field.parent_link:
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 both model and field name because to_field
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
@@ -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
- for field in fields.values():
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
- for *_, field, reference in references:
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()
@@ -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
- to_fields = getattr(field, "to_fields", None)
75
+ # ForeignObject always references 'id'
76
76
  if (
77
77
  reference_field_name is None
78
- or
79
- # Unspecified to_field(s).
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, to_fields)
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 BigAutoField
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 self.pk is None:
174
- auto = BigAutoField(primary_key=True, auto_created=True)
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-pk concrete field names defined on the model.
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: