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.
- {plain_models-0.46.0 → plain_models-0.46.1}/PKG-INFO +1 -1
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/CHANGELOG.md +10 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/query.py +9 -1
- {plain_models-0.46.0 → plain_models-0.46.1}/pyproject.toml +1 -1
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_related_descriptors.py +81 -2
- {plain_models-0.46.0 → plain_models-0.46.1}/.gitignore +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/LICENSE +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/README.md +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/AGENTS.md +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/README.md +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/aggregates.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/base.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/client.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/creation.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/features.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/introspection.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/operations.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/schema.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/base/validation.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/ddl_references.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/base.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/client.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/compiler.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/creation.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/features.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/introspection.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/operations.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/schema.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/mysql/validation.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/base.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/client.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/creation.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/features.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/introspection.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/operations.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/schema.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/_functions.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/base.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/client.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/creation.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/features.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/introspection.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/operations.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/sqlite3/schema.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/utils.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/cli.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/clients.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backups/core.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/base.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/cli.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/config.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/connections.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/constants.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/constraints.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/database_url.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/db.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/default_settings.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/deletion.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/entrypoints.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/enums.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/exceptions.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/expressions.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/json.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/mixins.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related_descriptors.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related_lookups.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/related_managers.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/fields/reverse_related.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/forms.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/comparison.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/datetime.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/math.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/mixins.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/text.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/functions/window.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/indexes.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/lookups.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/autodetector.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/exceptions.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/executor.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/graph.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/loader.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/migration.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/base.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/fields.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/models.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/operations/special.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/optimizer.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/questioner.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/recorder.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/serializer.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/state.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/utils.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/migrations/writer.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/options.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/otel.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/preflight.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/query_utils.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/registry.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/compiler.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/constants.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/datastructures.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/query.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/subqueries.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/sql/where.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/test/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/test/pytest.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/test/utils.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/transaction.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/plain/models/utils.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0001_initial.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0002_test_field_removed.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0003_deleteparent_childsetnull_childsetdefault_and_more.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/__init__.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/models.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/settings.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/app/urls.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_database_url.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_delete_behaviors.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_exceptions.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_manager_assignment.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_models.py +0 -0
- {plain_models-0.46.0 → plain_models-0.46.1}/tests/test_related_manager_api.py +0 -0
@@ -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
|
-
|
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.
|
@@ -117,8 +117,87 @@ class TestReverseManyToOneDescriptor:
|
|
117
117
|
class TestPrefetchRelated:
|
118
118
|
"""Test prefetch_related functionality"""
|
119
119
|
|
120
|
-
|
121
|
-
|
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
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{plain_models-0.46.0 → plain_models-0.46.1}/plain/models/backends/postgresql/introspection.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{plain_models-0.46.0 → plain_models-0.46.1}/tests/app/examples/migrations/0002_test_field_removed.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|