plain.models 0.46.0__tar.gz → 0.46.1__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 (136) hide show
  1. {plain_models-0.46.0 → plain_models-0.46.1}/PKG-INFO +1 -1
  2. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/CHANGELOG.md +10 -0
  3. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/query.py +9 -1
  4. {plain_models-0.46.0 → plain_models-0.46.1}/pyproject.toml +1 -1
  5. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_related_descriptors.py +81 -2
  6. {plain_models-0.46.0 → plain_models-0.46.1}/.gitignore +0 -0
  7. {plain_models-0.46.0 → plain_models-0.46.1}/LICENSE +0 -0
  8. {plain_models-0.46.0 → plain_models-0.46.1}/README.md +0 -0
  9. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/AGENTS.md +0 -0
  10. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/README.md +0 -0
  11. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/__init__.py +0 -0
  12. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/aggregates.py +0 -0
  13. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/__init__.py +0 -0
  14. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/__init__.py +0 -0
  15. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/base.py +0 -0
  16. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/client.py +0 -0
  17. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/creation.py +0 -0
  18. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/features.py +0 -0
  19. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/introspection.py +0 -0
  20. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/operations.py +0 -0
  21. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/schema.py +0 -0
  22. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/validation.py +0 -0
  23. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/ddl_references.py +0 -0
  24. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/__init__.py +0 -0
  25. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/base.py +0 -0
  26. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/client.py +0 -0
  27. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/compiler.py +0 -0
  28. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/creation.py +0 -0
  29. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/features.py +0 -0
  30. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/introspection.py +0 -0
  31. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/operations.py +0 -0
  32. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/schema.py +0 -0
  33. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/validation.py +0 -0
  34. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/__init__.py +0 -0
  35. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/base.py +0 -0
  36. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/client.py +0 -0
  37. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/creation.py +0 -0
  38. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/features.py +0 -0
  39. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/introspection.py +0 -0
  40. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/operations.py +0 -0
  41. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/schema.py +0 -0
  42. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/__init__.py +0 -0
  43. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/_functions.py +0 -0
  44. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/base.py +0 -0
  45. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/client.py +0 -0
  46. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/creation.py +0 -0
  47. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/features.py +0 -0
  48. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/introspection.py +0 -0
  49. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/operations.py +0 -0
  50. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/schema.py +0 -0
  51. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/utils.py +0 -0
  52. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/__init__.py +0 -0
  53. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/cli.py +0 -0
  54. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/clients.py +0 -0
  55. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/core.py +0 -0
  56. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/base.py +0 -0
  57. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/cli.py +0 -0
  58. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/config.py +0 -0
  59. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/connections.py +0 -0
  60. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/constants.py +0 -0
  61. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/constraints.py +0 -0
  62. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/database_url.py +0 -0
  63. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/db.py +0 -0
  64. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/default_settings.py +0 -0
  65. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/deletion.py +0 -0
  66. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/entrypoints.py +0 -0
  67. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/enums.py +0 -0
  68. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/exceptions.py +0 -0
  69. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/expressions.py +0 -0
  70. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/__init__.py +0 -0
  71. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/json.py +0 -0
  72. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/mixins.py +0 -0
  73. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related.py +0 -0
  74. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related_descriptors.py +0 -0
  75. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related_lookups.py +0 -0
  76. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related_managers.py +0 -0
  77. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/reverse_related.py +0 -0
  78. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/forms.py +0 -0
  79. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/__init__.py +0 -0
  80. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/comparison.py +0 -0
  81. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/datetime.py +0 -0
  82. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/math.py +0 -0
  83. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/mixins.py +0 -0
  84. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/text.py +0 -0
  85. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/window.py +0 -0
  86. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/indexes.py +0 -0
  87. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/lookups.py +0 -0
  88. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/__init__.py +0 -0
  89. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/autodetector.py +0 -0
  90. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/exceptions.py +0 -0
  91. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/executor.py +0 -0
  92. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/graph.py +0 -0
  93. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/loader.py +0 -0
  94. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/migration.py +0 -0
  95. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/__init__.py +0 -0
  96. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/base.py +0 -0
  97. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/fields.py +0 -0
  98. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/models.py +0 -0
  99. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/special.py +0 -0
  100. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/optimizer.py +0 -0
  101. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/questioner.py +0 -0
  102. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/recorder.py +0 -0
  103. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/serializer.py +0 -0
  104. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/state.py +0 -0
  105. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/utils.py +0 -0
  106. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/writer.py +0 -0
  107. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/options.py +0 -0
  108. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/otel.py +0 -0
  109. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/preflight.py +0 -0
  110. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/query_utils.py +0 -0
  111. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/registry.py +0 -0
  112. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/__init__.py +0 -0
  113. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/compiler.py +0 -0
  114. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/constants.py +0 -0
  115. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/datastructures.py +0 -0
  116. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/query.py +0 -0
  117. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/subqueries.py +0 -0
  118. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/where.py +0 -0
  119. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/test/__init__.py +0 -0
  120. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/test/pytest.py +0 -0
  121. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/test/utils.py +0 -0
  122. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/transaction.py +0 -0
  123. {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/utils.py +0 -0
  124. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0001_initial.py +0 -0
  125. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
  126. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
  127. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/__init__.py +0 -0
  128. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/models.py +0 -0
  129. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/settings.py +0 -0
  130. {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/urls.py +0 -0
  131. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_database_url.py +0 -0
  132. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_delete_behaviors.py +0 -0
  133. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_exceptions.py +0 -0
  134. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_manager_assignment.py +0 -0
  135. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_models.py +0 -0
  136. {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_related_manager_api.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plain.models
3
- Version: 0.46.0
3
+ Version: 0.46.1
4
4
  Summary: Model your data and store it in a database.
5
5
  Author-email: Dave Gaeddert <dave.gaeddert@dropseed.dev>
6
6
  License-File: LICENSE
@@ -1,5 +1,15 @@
1
1
  # plain-models changelog
2
2
 
3
+ ## [0.46.1](https://github.com/dropseed/plain/releases/plain-models@0.46.1) (2025-09-25)
4
+
5
+ ### What's changed
6
+
7
+ - Fixed `prefetch_related` for reverse foreign key relationships by correctly handling related managers in the prefetch query process ([2c04e80](https://github.com/dropseed/plain/commit/2c04e80dcd))
8
+
9
+ ### Upgrade instructions
10
+
11
+ - No changes required
12
+
3
13
  ## [0.46.0](https://github.com/dropseed/plain/releases/plain-models@0.46.0) (2025-09-25)
4
14
 
5
15
  ### What's changed
@@ -2204,7 +2204,15 @@ def prefetch_one_level(instances, prefetcher, lookup, level):
2204
2204
  if leaf and lookup.queryset is not None:
2205
2205
  qs = queryset._apply_rel_filters(lookup.queryset)
2206
2206
  else:
2207
- qs = queryset.__class__(model=queryset.model)
2207
+ # Check if queryset is a QuerySet or a related manager
2208
+ # We need a QuerySet instance to cache the prefetched values
2209
+ if isinstance(queryset, QuerySet):
2210
+ # It's already a QuerySet, create a new instance
2211
+ qs = queryset.__class__(model=queryset.model)
2212
+ else:
2213
+ # It's a related manager, get its QuerySet
2214
+ # The manager's query property returns a properly filtered QuerySet
2215
+ qs = queryset.query
2208
2216
  qs._result_cache = vals
2209
2217
  # We don't want the individual qs doing prefetch_related now,
2210
2218
  # since we have merged this into the current work.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "plain.models"
3
- version = "0.46.0"
3
+ version = "0.46.1"
4
4
  description = "Model your data and store it in a database."
5
5
  authors = [{name = "Dave Gaeddert", email = "dave.gaeddert@dropseed.dev"}]
6
6
  readme = "README.md"
@@ -117,8 +117,87 @@ class TestReverseManyToOneDescriptor:
117
117
  class TestPrefetchRelated:
118
118
  """Test prefetch_related functionality"""
119
119
 
120
- # Note: prefetch_related testing requires more complex setup
121
- # Core functionality works as demonstrated by basic relationship access
120
+ def test_prefetch_related_basic(self, db):
121
+ """Test basic prefetch_related functionality with reverse FK"""
122
+ # Create test data
123
+ parent1 = DeleteParent.query.create(name="Parent 1")
124
+ parent2 = DeleteParent.query.create(name="Parent 2")
125
+ DeleteParent.query.create(name="Parent 3")
126
+
127
+ child1 = ChildCascade.query.create(parent=parent1)
128
+ child2 = ChildCascade.query.create(parent=parent1)
129
+ child3 = ChildCascade.query.create(parent=parent2)
130
+
131
+ # Test prefetch_related on reverse FK
132
+ parents = DeleteParent.query.prefetch_related("childcascade_set").all()
133
+
134
+ # Should be able to access related objects without additional queries
135
+ assert len(parents) == 3
136
+
137
+ # Check if prefetch cache exists
138
+ parent1_from_query = next(p for p in parents if p.name == "Parent 1")
139
+
140
+ # Check if _prefetched_objects_cache is set
141
+ assert hasattr(parent1_from_query, "_prefetched_objects_cache"), (
142
+ "Prefetch cache not set"
143
+ )
144
+
145
+ # If prefetch worked, this should come from cache
146
+ parent1_children = list(parent1_from_query.childcascade_set.query.all())
147
+ assert len(parent1_children) == 2
148
+ assert child1 in parent1_children
149
+ assert child2 in parent1_children
150
+
151
+ parent2_from_query = next(p for p in parents if p.name == "Parent 2")
152
+ parent2_children = list(parent2_from_query.childcascade_set.query.all())
153
+ assert len(parent2_children) == 1
154
+ assert child3 in parent2_children
155
+
156
+ parent3_from_query = next(p for p in parents if p.name == "Parent 3")
157
+ parent3_children = list(parent3_from_query.childcascade_set.query.all())
158
+ assert len(parent3_children) == 0
159
+
160
+ def test_prefetch_related_forward_fk(self, db):
161
+ """Test prefetch_related functionality with forward FK"""
162
+ # Create test data
163
+ parent1 = DeleteParent.query.create(name="Parent 1")
164
+ parent2 = DeleteParent.query.create(name="Parent 2")
165
+
166
+ child1 = ChildCascade.query.create(parent=parent1)
167
+ child2 = ChildCascade.query.create(parent=parent1)
168
+ child3 = ChildCascade.query.create(parent=parent2)
169
+
170
+ # Test prefetch_related on forward FK
171
+ children = ChildCascade.query.prefetch_related("parent").all()
172
+
173
+ assert len(children) == 3
174
+
175
+ # Access related parent objects through prefetched relation
176
+ for child in children:
177
+ assert child.parent is not None
178
+ if child in [child1, child2]:
179
+ assert child.parent.name == "Parent 1"
180
+ elif child == child3:
181
+ assert child.parent.name == "Parent 2"
182
+
183
+ def test_prefetch_related_empty_result(self, db):
184
+ """Test prefetch_related works correctly with empty results"""
185
+ # Create parent with no children
186
+ DeleteParent.query.create(name="Lonely Parent")
187
+
188
+ parents = DeleteParent.query.prefetch_related("childcascade_set").all()
189
+ assert len(parents) == 1
190
+
191
+ parent_children = list(parents[0].childcascade_set.query.all())
192
+ assert len(parent_children) == 0
193
+
194
+ def test_prefetch_related_nonexistent_relation(self, db):
195
+ """Test that prefetch_related raises appropriate error for nonexistent relations"""
196
+ # Create at least one parent so we have something to prefetch on
197
+ DeleteParent.query.create(name="Test Parent")
198
+
199
+ with pytest.raises((AttributeError, ValueError)):
200
+ list(DeleteParent.query.prefetch_related("nonexistent_relation").all())
122
201
 
123
202
 
124
203
  class TestCustomQuerySetInheritance:
File without changes
File without changes
File without changes