plain.models 0.38.0__tar.gz → 0.39.0__tar.gz

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 (131) hide show
  1. {plain_models-0.38.0 → plain_models-0.39.0}/PKG-INFO +4 -1
  2. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/CHANGELOG.md +19 -0
  3. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/README.md +3 -0
  4. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/__init__.py +2 -2
  5. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/creation.py +1 -1
  6. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/operations.py +1 -3
  7. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/schema.py +4 -8
  8. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/base.py +1 -3
  9. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/introspection.py +2 -6
  10. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/operations.py +2 -4
  11. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/base.py +2 -6
  12. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/introspection.py +2 -6
  13. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/operations.py +1 -3
  14. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/schema.py +2 -10
  15. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/base.py +2 -6
  16. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/introspection.py +2 -8
  17. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/base.py +46 -74
  18. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/constraints.py +3 -3
  19. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/deletion.py +9 -9
  20. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/__init__.py +30 -104
  21. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/related.py +90 -343
  22. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/related_descriptors.py +14 -14
  23. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/related_lookups.py +2 -2
  24. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/reverse_related.py +6 -14
  25. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/forms.py +14 -76
  26. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/lookups.py +2 -2
  27. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/autodetector.py +2 -25
  28. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/operations/fields.py +0 -6
  29. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/state.py +2 -26
  30. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/utils.py +4 -14
  31. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/options.py +4 -12
  32. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/query.py +46 -54
  33. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/query_utils.py +3 -5
  34. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/compiler.py +16 -18
  35. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/query.py +12 -11
  36. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/subqueries.py +10 -10
  37. {plain_models-0.38.0 → plain_models-0.39.0}/pyproject.toml +1 -1
  38. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/examples/migrations/0001_initial.py +1 -1
  39. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +7 -7
  40. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/examples/models.py +3 -3
  41. {plain_models-0.38.0 → plain_models-0.39.0}/tests/test_delete_behaviors.py +3 -3
  42. {plain_models-0.38.0 → plain_models-0.39.0}/.gitignore +0 -0
  43. {plain_models-0.38.0 → plain_models-0.39.0}/LICENSE +0 -0
  44. {plain_models-0.38.0 → plain_models-0.39.0}/README.md +0 -0
  45. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/aggregates.py +0 -0
  46. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/__init__.py +0 -0
  47. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/__init__.py +0 -0
  48. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/base.py +0 -0
  49. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/client.py +0 -0
  50. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/features.py +0 -0
  51. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/introspection.py +0 -0
  52. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/base/validation.py +0 -0
  53. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/ddl_references.py +0 -0
  54. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/__init__.py +0 -0
  55. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/client.py +0 -0
  56. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/compiler.py +0 -0
  57. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/creation.py +0 -0
  58. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/features.py +0 -0
  59. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/schema.py +0 -0
  60. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/mysql/validation.py +0 -0
  61. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/__init__.py +0 -0
  62. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/client.py +0 -0
  63. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/creation.py +0 -0
  64. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/postgresql/features.py +0 -0
  65. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/__init__.py +0 -0
  66. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/_functions.py +0 -0
  67. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/client.py +0 -0
  68. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/creation.py +0 -0
  69. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/features.py +0 -0
  70. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/operations.py +0 -0
  71. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/sqlite3/schema.py +0 -0
  72. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backends/utils.py +0 -0
  73. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backups/__init__.py +0 -0
  74. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backups/cli.py +0 -0
  75. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backups/clients.py +0 -0
  76. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/backups/core.py +0 -0
  77. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/cli.py +0 -0
  78. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/config.py +0 -0
  79. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/connections.py +0 -0
  80. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/constants.py +0 -0
  81. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/database_url.py +0 -0
  82. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/db.py +0 -0
  83. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/default_settings.py +0 -0
  84. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/entrypoints.py +0 -0
  85. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/enums.py +0 -0
  86. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/exceptions.py +0 -0
  87. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/expressions.py +0 -0
  88. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/json.py +0 -0
  89. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/fields/mixins.py +0 -0
  90. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/__init__.py +0 -0
  91. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/comparison.py +0 -0
  92. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/datetime.py +0 -0
  93. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/math.py +0 -0
  94. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/mixins.py +0 -0
  95. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/text.py +0 -0
  96. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/functions/window.py +0 -0
  97. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/indexes.py +0 -0
  98. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/manager.py +0 -0
  99. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/__init__.py +0 -0
  100. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/exceptions.py +0 -0
  101. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/executor.py +0 -0
  102. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/graph.py +0 -0
  103. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/loader.py +0 -0
  104. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/migration.py +0 -0
  105. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/operations/__init__.py +0 -0
  106. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/operations/base.py +0 -0
  107. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/operations/models.py +0 -0
  108. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/operations/special.py +0 -0
  109. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/optimizer.py +0 -0
  110. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/questioner.py +0 -0
  111. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/recorder.py +0 -0
  112. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/serializer.py +0 -0
  113. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/migrations/writer.py +0 -0
  114. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/otel.py +0 -0
  115. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/preflight.py +0 -0
  116. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/registry.py +0 -0
  117. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/__init__.py +0 -0
  118. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/constants.py +0 -0
  119. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/datastructures.py +0 -0
  120. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/sql/where.py +0 -0
  121. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/test/__init__.py +0 -0
  122. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/test/pytest.py +0 -0
  123. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/test/utils.py +0 -0
  124. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/transaction.py +0 -0
  125. {plain_models-0.38.0 → plain_models-0.39.0}/plain/models/utils.py +0 -0
  126. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  127. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/examples/migrations/__init__.py +0 -0
  128. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/settings.py +0 -0
  129. {plain_models-0.38.0 → plain_models-0.39.0}/tests/app/urls.py +0 -0
  130. {plain_models-0.38.0 → plain_models-0.39.0}/tests/test_database_url.py +0 -0
  131. {plain_models-0.38.0 → plain_models-0.39.0}/tests/test_models.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.models
3
- Version: 0.38.0
3
+ Version: 0.39.0
4
4
  Summary: Database models for Plain.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -30,6 +30,9 @@ class User(models.Model):
30
30
  return self.email
31
31
  ```
32
32
 
33
+ Every model automatically includes an `id` field which serves as the primary
34
+ key. The name `id` is reserved and can't be used for other fields.
35
+
33
36
  Create, update, and delete instances of your models:
34
37
 
35
38
  ```python
@@ -1,5 +1,24 @@
1
1
  # plain-models changelog
2
2
 
3
+ ## [0.39.0](https://github.com/dropseed/plain/releases/plain-models@0.39.0) (2025-07-22)
4
+
5
+ ### What's changed
6
+
7
+ - Models now use a single automatic `id` field as the primary key, replacing the previous `pk` alias and automatic field system ([4b8fa6a](https://github.com/dropseed/plain/commit/4b8fa6a))
8
+ - Removed the `to_field` option for ForeignKey - foreign keys now always reference the primary key of the related model ([7fc3c88](https://github.com/dropseed/plain/commit/7fc3c88))
9
+ - Removed the internal `from_fields` and `to_fields` system used for multi-column foreign keys ([0e9eda3](https://github.com/dropseed/plain/commit/0e9eda3))
10
+ - Removed the `parent_link` parameter on ForeignKey and ForeignObject ([6658647](https://github.com/dropseed/plain/commit/6658647))
11
+ - Removed `InlineForeignKeyField` from forms ([ede6265](https://github.com/dropseed/plain/commit/ede6265))
12
+ - Merged ForeignObject functionality into ForeignKey, simplifying the foreign key implementation ([e6d9aaa](https://github.com/dropseed/plain/commit/e6d9aaa))
13
+ - Cleaned up unused code in ForeignKey and fixed ForeignObjectRel imports ([b656ee6](https://github.com/dropseed/plain/commit/b656ee6))
14
+
15
+ ### Upgrade instructions
16
+
17
+ - Replace any direct references to `pk` with `id` in your models and queries (e.g., `user.pk` becomes `user.id`)
18
+ - Remove any `to_field` arguments from ForeignKey definitions - they are no longer supported
19
+ - Remove any `parent_link=True` arguments from ForeignKey definitions - they are no longer supported
20
+ - Replace any usage of `InlineForeignKeyField` in forms with standard form fields
21
+
3
22
  ## [0.38.0](https://github.com/dropseed/plain/releases/plain-models@0.38.0) (2025-07-21)
4
23
 
5
24
  ### What's changed
@@ -19,6 +19,9 @@ class User(models.Model):
19
19
  return self.email
20
20
  ```
21
21
 
22
+ Every model automatically includes an `id` field which serves as the primary
23
+ key. The name `id` is reserved and can't be used for other fields.
24
+
22
25
  Create, update, and delete instances of your models:
23
26
 
24
27
  ```python
@@ -68,8 +68,9 @@ from .registry import models_registry, register_model
68
68
  from .base import DEFERRED, Model # isort:skip
69
69
  from .fields.related import ( # isort:skip
70
70
  ForeignKey,
71
- ForeignObject,
72
71
  ManyToManyField,
72
+ )
73
+ from .fields.reverse_related import ( # isort:skip
73
74
  ForeignObjectRel,
74
75
  ManyToOneRel,
75
76
  ManyToManyRel,
@@ -116,7 +117,6 @@ __all__ += [
116
117
  "Model",
117
118
  "FilteredRelation",
118
119
  "ForeignKey",
119
- "ForeignObject",
120
120
  "ManyToManyField",
121
121
  "ForeignObjectRel",
122
122
  "ManyToOneRel",
@@ -96,7 +96,7 @@ class BaseDatabaseCreation:
96
96
  # ) and router.allow_migrate_model(self.connection.alias, model):
97
97
  # queryset = model._base_manager.using(
98
98
  # self.connection.alias,
99
- # ).order_by(model._meta.pk.name)
99
+ # ).order_by("id")
100
100
  # yield from queryset.iterator()
101
101
 
102
102
  # # Serialize to a string
@@ -28,9 +28,7 @@ class BaseDatabaseOperations:
28
28
  "PositiveBigIntegerField": (0, 9223372036854775807),
29
29
  "PositiveSmallIntegerField": (0, 32767),
30
30
  "PositiveIntegerField": (0, 2147483647),
31
- "SmallAutoField": (-32768, 32767),
32
- "AutoField": (-2147483648, 2147483647),
33
- "BigAutoField": (-9223372036854775808, 9223372036854775807),
31
+ "PrimaryKeyField": (-9223372036854775808, 9223372036854775807),
34
32
  }
35
33
  set_operators = {
36
34
  "union": "UNION",
@@ -29,11 +29,11 @@ def _is_relevant_relation(relation, altered_field):
29
29
  if field.many_to_many:
30
30
  # M2M reverse field
31
31
  return False
32
- if altered_field.primary_key and field.to_fields == [None]:
32
+ if altered_field.primary_key:
33
33
  # Foreign key constraint on the primary key, which is being altered.
34
34
  return True
35
- # Is the constraint targeting the field being altered?
36
- return altered_field.name in field.to_fields
35
+ # ForeignKey always targets 'id'
36
+ return altered_field.name == "id"
37
37
 
38
38
 
39
39
  def _all_related_fields(model):
@@ -239,11 +239,7 @@ class BaseDatabaseSchemaEditor:
239
239
  column_sqls.append(f"{self.quote_name(field.column)} {definition}")
240
240
  # Autoincrement SQL (for backends with post table definition
241
241
  # variant).
242
- if field.get_internal_type() in (
243
- "AutoField",
244
- "BigAutoField",
245
- "SmallAutoField",
246
- ):
242
+ if field.get_internal_type() in ("PrimaryKeyField",):
247
243
  autoinc_sql = self.connection.ops.autoinc_sql(
248
244
  model._meta.db_table, field.column
249
245
  )
@@ -91,8 +91,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
91
91
  # be interpolated against the values of Field.__dict__ before being output.
92
92
  # If a column type is set to None, it won't be included in the output.
93
93
  data_types = {
94
- "AutoField": "integer AUTO_INCREMENT",
95
- "BigAutoField": "bigint AUTO_INCREMENT",
94
+ "PrimaryKeyField": "bigint AUTO_INCREMENT",
96
95
  "BinaryField": "longblob",
97
96
  "BooleanField": "bool",
98
97
  "CharField": "varchar(%(max_length)s)",
@@ -109,7 +108,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
109
108
  "PositiveBigIntegerField": "bigint UNSIGNED",
110
109
  "PositiveIntegerField": "integer UNSIGNED",
111
110
  "PositiveSmallIntegerField": "smallint UNSIGNED",
112
- "SmallAutoField": "smallint AUTO_INCREMENT",
113
111
  "SmallIntegerField": "smallint",
114
112
  "TextField": "longtext",
115
113
  "TimeField": "time(6)",
@@ -49,12 +49,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
49
49
  def get_field_type(self, data_type, description):
50
50
  field_type = super().get_field_type(data_type, description)
51
51
  if "auto_increment" in description.extra:
52
- if field_type == "IntegerField":
53
- return "AutoField"
54
- elif field_type == "BigIntegerField":
55
- return "BigAutoField"
56
- elif field_type == "SmallIntegerField":
57
- return "SmallAutoField"
52
+ if field_type == "BigIntegerField":
53
+ return "PrimaryKeyField"
58
54
  if description.is_unsigned:
59
55
  if field_type == "BigIntegerField":
60
56
  return "PositiveBigIntegerField"
@@ -21,9 +21,7 @@ class DatabaseOperations(BaseDatabaseOperations):
21
21
  "PositiveBigIntegerField": (0, 18446744073709551615),
22
22
  }
23
23
  cast_data_types = {
24
- "AutoField": "signed integer",
25
- "BigAutoField": "signed integer",
26
- "SmallAutoField": "signed integer",
24
+ "PrimaryKeyField": "signed integer",
27
25
  "CharField": "char(%(max_length)s)",
28
26
  "DecimalField": "decimal(%(max_digits)s, %(decimal_places)s)",
29
27
  "TextField": "char",
@@ -201,7 +199,7 @@ class DatabaseOperations(BaseDatabaseOperations):
201
199
  # NO_AUTO_VALUE_ON_ZERO SQL mode.
202
200
  if value == 0 and not self.connection.features.allows_auto_pk_0:
203
201
  raise ValueError(
204
- "The database backend does not accept 0 as a value for AutoField."
202
+ "The database backend does not accept 0 as a value for PrimaryKeyField."
205
203
  )
206
204
  return value
207
205
 
@@ -89,8 +89,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
89
89
  # be interpolated against the values of Field.__dict__ before being output.
90
90
  # If a column type is set to None, it won't be included in the output.
91
91
  data_types = {
92
- "AutoField": "integer",
93
- "BigAutoField": "bigint",
92
+ "PrimaryKeyField": "bigint",
94
93
  "BinaryField": "bytea",
95
94
  "BooleanField": "boolean",
96
95
  "CharField": _get_varchar_column,
@@ -107,7 +106,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
107
106
  "PositiveBigIntegerField": "bigint",
108
107
  "PositiveIntegerField": "integer",
109
108
  "PositiveSmallIntegerField": "smallint",
110
- "SmallAutoField": "smallint",
111
109
  "SmallIntegerField": "smallint",
112
110
  "TextField": "text",
113
111
  "TimeField": "time",
@@ -119,9 +117,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
119
117
  "PositiveSmallIntegerField": '"%(column)s" >= 0',
120
118
  }
121
119
  data_types_suffix = {
122
- "AutoField": "GENERATED BY DEFAULT AS IDENTITY",
123
- "BigAutoField": "GENERATED BY DEFAULT AS IDENTITY",
124
- "SmallAutoField": "GENERATED BY DEFAULT AS IDENTITY",
120
+ "PrimaryKeyField": "GENERATED BY DEFAULT AS IDENTITY",
125
121
  }
126
122
  operators = {
127
123
  "exact": "= %s",
@@ -44,12 +44,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
44
44
  # Required for pre-Plain 4.1 serial columns.
45
45
  description.default and "nextval" in description.default
46
46
  ):
47
- if field_type == "IntegerField":
48
- return "AutoField"
49
- elif field_type == "BigIntegerField":
50
- return "BigAutoField"
51
- elif field_type == "SmallIntegerField":
52
- return "SmallAutoField"
47
+ if field_type == "BigIntegerField":
48
+ return "PrimaryKeyField"
53
49
  return field_type
54
50
 
55
51
  def get_table_list(self, cursor):
@@ -35,9 +35,7 @@ class DatabaseOperations(BaseDatabaseOperations):
35
35
  ]
36
36
  )
37
37
  cast_data_types = {
38
- "AutoField": "integer",
39
- "BigAutoField": "bigint",
40
- "SmallAutoField": "smallint",
38
+ "PrimaryKeyField": "bigint",
41
39
  }
42
40
 
43
41
  integerfield_type_map = {
@@ -166,11 +166,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
166
166
  old_internal_type = old_field.get_internal_type()
167
167
  # Make ALTER TYPE with IDENTITY make sense.
168
168
  table = strip_quotes(model._meta.db_table)
169
- auto_field_types = {
170
- "AutoField",
171
- "BigAutoField",
172
- "SmallAutoField",
173
- }
169
+ auto_field_types = {"PrimaryKeyField"}
174
170
  old_is_auto = old_internal_type in auto_field_types
175
171
  new_is_auto = new_internal_type in auto_field_types
176
172
  if new_is_auto and not old_is_auto:
@@ -229,11 +225,7 @@ class DatabaseSchemaEditor(BaseDatabaseSchemaEditor):
229
225
  model, old_field, new_field, new_type, old_collation, new_collation
230
226
  )
231
227
  column = strip_quotes(new_field.column)
232
- db_types = {
233
- "AutoField": "integer",
234
- "BigAutoField": "bigint",
235
- "SmallAutoField": "smallint",
236
- }
228
+ db_types = {"PrimaryKeyField": "bigint"}
237
229
  # Alter the sequence type if exists (Plain 4.1+ identity columns
238
230
  # don't have it).
239
231
  other_actions = []
@@ -57,8 +57,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
57
57
  # thing" given more verbose field definitions, so leave them as is so that
58
58
  # schema inspection is more useful.
59
59
  data_types = {
60
- "AutoField": "integer",
61
- "BigAutoField": "integer",
60
+ "PrimaryKeyField": "integer",
62
61
  "BinaryField": "BLOB",
63
62
  "BooleanField": "bool",
64
63
  "CharField": "varchar(%(max_length)s)",
@@ -75,7 +74,6 @@ class DatabaseWrapper(BaseDatabaseWrapper):
75
74
  "PositiveBigIntegerField": "bigint unsigned",
76
75
  "PositiveIntegerField": "integer unsigned",
77
76
  "PositiveSmallIntegerField": "smallint unsigned",
78
- "SmallAutoField": "integer",
79
77
  "SmallIntegerField": "smallint",
80
78
  "TextField": "text",
81
79
  "TimeField": "time",
@@ -88,9 +86,7 @@ class DatabaseWrapper(BaseDatabaseWrapper):
88
86
  "PositiveSmallIntegerField": '"%(column)s" >= 0',
89
87
  }
90
88
  data_types_suffix = {
91
- "AutoField": "AUTOINCREMENT",
92
- "BigAutoField": "AUTOINCREMENT",
93
- "SmallAutoField": "AUTOINCREMENT",
89
+ "PrimaryKeyField": "AUTOINCREMENT",
94
90
  }
95
91
  # SQLite requires LIKE statements to include an ESCAPE clause if the value
96
92
  # being escaped has a percent or underscore in it.
@@ -63,14 +63,8 @@ class DatabaseIntrospection(BaseDatabaseIntrospection):
63
63
 
64
64
  def get_field_type(self, data_type, description):
65
65
  field_type = super().get_field_type(data_type, description)
66
- if description.pk and field_type in {
67
- "BigIntegerField",
68
- "IntegerField",
69
- "SmallIntegerField",
70
- }:
71
- # No support for BigAutoField or SmallAutoField as SQLite treats
72
- # all integer primary keys as signed 64-bit integers.
73
- return "AutoField"
66
+ if description.pk and field_type == "BigIntegerField":
67
+ return "PrimaryKeyField"
74
68
  if description.has_json_constraint:
75
69
  return "JSONField"
76
70
  return field_type
@@ -23,9 +23,7 @@ from plain.models.db import (
23
23
  from plain.models.deletion import Collector
24
24
  from plain.models.expressions import RawSQL, Value
25
25
  from plain.models.fields import NOT_PROVIDED
26
- from plain.models.fields.related import (
27
- ForeignObjectRel,
28
- )
26
+ from plain.models.fields.reverse_related import ForeignObjectRel
29
27
  from plain.models.manager import Manager
30
28
  from plain.models.options import Options
31
29
  from plain.models.query import F, Q
@@ -334,22 +332,22 @@ class Model(metaclass=ModelBase):
334
332
  return f"<{self.__class__.__name__}: {self}>"
335
333
 
336
334
  def __str__(self):
337
- return f"{self.__class__.__name__} object ({self.pk})"
335
+ return f"{self.__class__.__name__} object ({self.id})"
338
336
 
339
337
  def __eq__(self, other):
340
338
  if not isinstance(other, Model):
341
339
  return NotImplemented
342
340
  if self._meta.concrete_model != other._meta.concrete_model:
343
341
  return False
344
- my_pk = self.pk
345
- if my_pk is None:
342
+ my_id = self.id
343
+ if my_id is None:
346
344
  return self is other
347
- return my_pk == other.pk
345
+ return my_id == other.id
348
346
 
349
347
  def __hash__(self):
350
- if self.pk is None:
348
+ if self.id is None:
351
349
  raise TypeError("Model instances without primary key value are unhashable")
352
- return hash(self.pk)
350
+ return hash(self.id)
353
351
 
354
352
  def __reduce__(self):
355
353
  data = self.__getstate__()
@@ -395,15 +393,6 @@ class Model(metaclass=ModelBase):
395
393
  state[attr] = memoryview(value)
396
394
  self.__dict__.update(state)
397
395
 
398
- def _get_pk_val(self, meta=None):
399
- meta = meta or self._meta
400
- return getattr(self, meta.pk.attname)
401
-
402
- def _set_pk_val(self, value):
403
- return setattr(self, self._meta.pk.attname, value)
404
-
405
- pk = property(_get_pk_val, _set_pk_val)
406
-
407
396
  def get_deferred_fields(self):
408
397
  """
409
398
  Return a set containing names of deferred fields for this instance.
@@ -445,7 +434,7 @@ class Model(metaclass=ModelBase):
445
434
  "are not allowed in fields."
446
435
  )
447
436
 
448
- db_instance_qs = self.__class__._base_manager.get_queryset().filter(pk=self.pk)
437
+ db_instance_qs = self.__class__._base_manager.get_queryset().filter(id=self.id)
449
438
 
450
439
  # Use provided fields, if not set then reload all non-deferred fields.
451
440
  deferred_fields = self.get_deferred_fields()
@@ -608,12 +597,13 @@ class Model(metaclass=ModelBase):
608
597
  if f.name in update_fields or f.attname in update_fields
609
598
  ]
610
599
 
611
- pk_val = self._get_pk_val(meta)
612
- if pk_val is None:
613
- pk_val = meta.pk.get_pk_value_on_save(self)
614
- setattr(self, meta.pk.attname, pk_val)
615
- pk_set = pk_val is not None
616
- if not pk_set and (force_update or update_fields):
600
+ id_val = self.id
601
+ if id_val is None:
602
+ id_field = meta.get_field("id")
603
+ id_val = id_field.get_id_value_on_save(self)
604
+ setattr(self, id_field.attname, id_val)
605
+ id_set = id_val is not None
606
+ if not id_set and (force_update or update_fields):
617
607
  raise ValueError("Cannot force an update in save() with no primary key.")
618
608
  updated = False
619
609
  # Skip an UPDATE when adding an instance and primary key has a default.
@@ -621,12 +611,12 @@ class Model(metaclass=ModelBase):
621
611
  not raw
622
612
  and not force_insert
623
613
  and self._state.adding
624
- and meta.pk.default
625
- and meta.pk.default is not NOT_PROVIDED
614
+ and meta.get_field("id").default
615
+ and meta.get_field("id").default is not NOT_PROVIDED
626
616
  ):
627
617
  force_insert = True
628
618
  # If possible, try an UPDATE. If that doesn't update anything, do an INSERT.
629
- if pk_set and not force_insert:
619
+ if id_set and not force_insert:
630
620
  base_qs = cls._base_manager
631
621
  values = [
632
622
  (
@@ -638,7 +628,7 @@ class Model(metaclass=ModelBase):
638
628
  ]
639
629
  forced_update = update_fields or force_update
640
630
  updated = self._do_update(
641
- base_qs, pk_val, values, update_fields, forced_update
631
+ base_qs, id_val, values, update_fields, forced_update
642
632
  )
643
633
  if force_update and not updated:
644
634
  raise DatabaseError("Forced update did not affect any rows.")
@@ -646,8 +636,9 @@ class Model(metaclass=ModelBase):
646
636
  raise DatabaseError("Save with update_fields did not affect any rows.")
647
637
  if not updated:
648
638
  fields = meta.local_concrete_fields
649
- if not pk_set:
650
- fields = [f for f in fields if f is not meta.auto_field]
639
+ if not id_set:
640
+ id_field = meta.get_field("id")
641
+ fields = [f for f in fields if f is not id_field]
651
642
 
652
643
  returning_fields = meta.db_returning_fields
653
644
  results = self._do_insert(cls._base_manager, fields, returning_fields, raw)
@@ -656,12 +647,12 @@ class Model(metaclass=ModelBase):
656
647
  setattr(self, field.attname, value)
657
648
  return updated
658
649
 
659
- def _do_update(self, base_qs, pk_val, values, update_fields, forced_update):
650
+ def _do_update(self, base_qs, id_val, values, update_fields, forced_update):
660
651
  """
661
652
  Try to update the model. Return True if the model was updated (if an
662
653
  update query was done and a matching row was found in the DB).
663
654
  """
664
- filtered = base_qs.filter(pk=pk_val)
655
+ filtered = base_qs.filter(id=id_val)
665
656
  if not values:
666
657
  # We can end up here when saving a model in inheritance chain where
667
658
  # update_fields doesn't target any field in current model. In that
@@ -701,7 +692,7 @@ class Model(metaclass=ModelBase):
701
692
  # database to raise an IntegrityError if applicable. If
702
693
  # constraints aren't supported by the database, there's the
703
694
  # unavoidable risk of data corruption.
704
- if obj.pk is None:
695
+ if obj.id is None:
705
696
  # Remove the object from a related instance cache.
706
697
  if not field.remote_field.multiple:
707
698
  field.remote_field.delete_cached_value(obj)
@@ -721,9 +712,9 @@ class Model(metaclass=ModelBase):
721
712
  field.delete_cached_value(self)
722
713
 
723
714
  def delete(self):
724
- if self.pk is None:
715
+ if self.id is None:
725
716
  raise ValueError(
726
- f"{self._meta.object_name} object can't be deleted because its {self._meta.pk.attname} attribute is set "
717
+ f"{self._meta.object_name} object can't be deleted because its id attribute is set "
727
718
  "to None."
728
719
  )
729
720
  collector = Collector(origin=self)
@@ -739,17 +730,17 @@ class Model(metaclass=ModelBase):
739
730
  )
740
731
 
741
732
  def _get_next_or_previous_by_FIELD(self, field, is_next, **kwargs):
742
- if not self.pk:
733
+ if not self.id:
743
734
  raise ValueError("get_next/get_previous cannot be used on unsaved objects.")
744
735
  op = "gt" if is_next else "lt"
745
736
  order = "" if is_next else "-"
746
737
  param = getattr(self, field.attname)
747
- q = Q.create([(field.name, param), (f"pk__{op}", self.pk)], connector=Q.AND)
738
+ q = Q.create([(field.name, param), (f"id__{op}", self.id)], connector=Q.AND)
748
739
  q = Q.create([q, (f"{field.name}__{op}", param)], connector=Q.OR)
749
740
  qs = (
750
741
  self.__class__._default_manager.filter(**kwargs)
751
742
  .filter(q)
752
- .order_by(f"{order}{field.name}", f"{order}pk")
743
+ .order_by(f"{order}{field.name}", f"{order}id")
753
744
  )
754
745
  try:
755
746
  return qs[0]
@@ -769,7 +760,7 @@ class Model(metaclass=ModelBase):
769
760
  }
770
761
 
771
762
  def prepare_database_save(self, field):
772
- if self.pk is None:
763
+ if self.id is None:
773
764
  raise ValueError(
774
765
  f"Unsaved model instance {self!r} cannot be used in an ORM query."
775
766
  )
@@ -849,13 +840,11 @@ class Model(metaclass=ModelBase):
849
840
 
850
841
  # Exclude the current object from the query if we are editing an
851
842
  # instance (as opposed to creating a new one)
852
- # Note that we need to use the pk as defined by model_class, not
853
- # self.pk. These can be different fields because model inheritance
854
- # allows single model to have effectively multiple primary keys.
855
- # Refs #17615.
856
- model_class_pk = self._get_pk_val(model_class._meta)
857
- if not self._state.adding and model_class_pk is not None:
858
- qs = qs.exclude(pk=model_class_pk)
843
+ # Use the primary key defined by model_class. In previous versions
844
+ # this could differ from `self.id` due to model inheritance.
845
+ model_class_id = getattr(self, "id")
846
+ if not self._state.adding and model_class_id is not None:
847
+ qs = qs.exclude(id=model_class_id)
859
848
  if qs.exists():
860
849
  if len(unique_check) == 1:
861
850
  key = unique_check[0]
@@ -1111,22 +1100,18 @@ class Model(metaclass=ModelBase):
1111
1100
 
1112
1101
  @classmethod
1113
1102
  def _check_id_field(cls):
1114
- """Check if `id` field is a primary key."""
1115
- fields = [
1116
- f for f in cls._meta.local_fields if f.name == "id" and f != cls._meta.pk
1117
- ]
1118
- # fields is empty or consists of the invalid "id" field
1119
- if fields and not fields[0].primary_key and cls._meta.pk.name == "id":
1103
+ """Disallow user-defined fields named ``id``."""
1104
+ if any(
1105
+ f for f in cls._meta.local_fields if f.name == "id" and not f.auto_created
1106
+ ):
1120
1107
  return [
1121
1108
  preflight.Error(
1122
- "'id' can only be used as a field name if the field also "
1123
- "sets 'primary_key=True'.",
1109
+ "'id' is a reserved word that cannot be used as a field name.",
1124
1110
  obj=cls,
1125
1111
  id="models.E004",
1126
1112
  )
1127
1113
  ]
1128
- else:
1129
- return []
1114
+ return []
1130
1115
 
1131
1116
  @classmethod
1132
1117
  def _check_field_name_clashes(cls):
@@ -1428,11 +1413,7 @@ class Model(metaclass=ModelBase):
1428
1413
  fld = None
1429
1414
  for part in field.split(LOOKUP_SEP):
1430
1415
  try:
1431
- # pk is an alias that won't be found by opts.get_field.
1432
- if part == "pk":
1433
- fld = _cls._meta.pk
1434
- else:
1435
- fld = _cls._meta.get_field(part)
1416
+ fld = _cls._meta.get_field(part)
1436
1417
  if fld.is_relation:
1437
1418
  _cls = fld.path_infos[-1].to_opts.model
1438
1419
  else:
@@ -1450,10 +1431,6 @@ class Model(metaclass=ModelBase):
1450
1431
  )
1451
1432
  )
1452
1433
 
1453
- # Skip ordering on pk. This is always a valid order_by field
1454
- # but is an alias and therefore won't be found by opts.get_field.
1455
- fields = {f for f in fields if f != "pk"}
1456
-
1457
1434
  # Check for invalid or nonexistent fields in ordering.
1458
1435
  invalid_fields = []
1459
1436
 
@@ -1469,7 +1446,7 @@ class Model(metaclass=ModelBase):
1469
1446
  )
1470
1447
  )
1471
1448
 
1472
- invalid_fields.extend(fields - valid_fields)
1449
+ invalid_fields.extend(set(fields) - valid_fields)
1473
1450
 
1474
1451
  for invalid_field in invalid_fields:
1475
1452
  errors.append(
@@ -1712,17 +1689,12 @@ class Model(metaclass=ModelBase):
1712
1689
  ),
1713
1690
  )
1714
1691
  for field_name, *lookups in references:
1715
- # pk is an alias that won't be found by opts.get_field.
1716
- if field_name != "pk":
1717
- fields.add(field_name)
1692
+ fields.add(field_name)
1718
1693
  if not lookups:
1719
1694
  # If it has no lookups it cannot result in a JOIN.
1720
1695
  continue
1721
1696
  try:
1722
- if field_name == "pk":
1723
- field = cls._meta.pk
1724
- else:
1725
- field = cls._meta.get_field(field_name)
1697
+ field = cls._meta.get_field(field_name)
1726
1698
  if not field.is_relation or field.many_to_many or field.one_to_many:
1727
1699
  continue
1728
1700
  except FieldDoesNotExist:
@@ -384,9 +384,9 @@ class UniqueConstraint(BaseConstraint):
384
384
  expr = expr.expression
385
385
  expressions.append(Exact(expr, expr.replace_expressions(replacements)))
386
386
  queryset = queryset.filter(*expressions)
387
- model_class_pk = instance._get_pk_val(model._meta)
388
- if not instance._state.adding and model_class_pk is not None:
389
- queryset = queryset.exclude(pk=model_class_pk)
387
+ model_class_id = instance.id
388
+ if not instance._state.adding and model_class_id is not None:
389
+ queryset = queryset.exclude(id=model_class_id)
390
390
  if not self.condition:
391
391
  if queryset.exists():
392
392
  if self.expressions: