django-ninja-aio-crud 2.8.0__tar.gz → 2.10.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.
Potentially problematic release.
This version of django-ninja-aio-crud might be problematic. Click here for more details.
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/.github/workflows/docs.yml +6 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/PKG-INFO +1 -1
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/models/model_serializer.md +4 -2
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/models/model_util.md +44 -16
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/models/serializers.md +13 -1
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/views/mixins.md +43 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/helpers/query.py +3 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/models/serializers.py +19 -9
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/models/utils.py +117 -66
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/schemas/__init__.py +2 -0
- django_ninja_aio_crud-2.10.0/ninja_aio/schemas/api.py +43 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/schemas/helpers.py +2 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/views/api.py +5 -2
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/views/mixins.py +63 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/core/test_renderer_parser.py +25 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/generics/models.py +7 -7
- django_ninja_aio_crud-2.10.0/tests/models/test_model_util.py +152 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/models/test_models_extra.py +164 -6
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_app/serializers.py +16 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_app/views.py +25 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_auth.py +51 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_query_util.py +22 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_serializers.py +10 -4
- django_ninja_aio_crud-2.10.0/tests/views/test_views.py +301 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/views/test_viewset.py +174 -5
- django_ninja_aio_crud-2.8.0/ninja_aio/schemas/api.py +0 -24
- django_ninja_aio_crud-2.8.0/tests/models/test_model_util.py +0 -68
- django_ninja_aio_crud-2.8.0/tests/views/test_views.py +0 -144
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/README.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/views/api_view.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/views/api_view_set.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/views/decorators.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/extra.css +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/index.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/main.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/mkdocs.yml +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/helpers/api.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/helpers/test_many_to_many_api.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_app/models.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/test_settings.py +0 -0
- {django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/tests/views/__init__.py +0 -0
|
@@ -20,6 +20,9 @@ on:
|
|
|
20
20
|
- "2.6"
|
|
21
21
|
- "2.6.1"
|
|
22
22
|
- "2.7.0"
|
|
23
|
+
- "2.8"
|
|
24
|
+
- "2.9"
|
|
25
|
+
- "2.10"
|
|
23
26
|
make_latest:
|
|
24
27
|
description: 'Set as "latest" and default?'
|
|
25
28
|
type: boolean
|
|
@@ -43,6 +46,9 @@ on:
|
|
|
43
46
|
- "2.6"
|
|
44
47
|
- "2.6.1"
|
|
45
48
|
- "2.7.0"
|
|
49
|
+
- "2.8"
|
|
50
|
+
- "2.9"
|
|
51
|
+
- "2.10"
|
|
46
52
|
delete_confirm:
|
|
47
53
|
description: 'Confirm deletion of the selected version'
|
|
48
54
|
type: boolean
|
{django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/docs/api/models/model_serializer.md
RENAMED
|
@@ -149,11 +149,13 @@ class User(ModelSerializer):
|
|
|
149
149
|
|
|
150
150
|
Describes how to build a detail (single object) output schema. Use this when you want the retrieve endpoint to return more fields than the list endpoint.
|
|
151
151
|
|
|
152
|
+
**Fallback Behavior:** If `DetailSerializer` is not defined, `generate_detail_s()` automatically falls back to the read schema (same as `generate_read_s()`). This means you only need to define `DetailSerializer` when you want different fields for single-object retrieval vs list views.
|
|
153
|
+
|
|
152
154
|
**Attributes:**
|
|
153
155
|
|
|
154
156
|
| Attribute | Type | Description |
|
|
155
157
|
| ----------- | ------------------------ | ----------------------------------------------------------------------------- |
|
|
156
|
-
| `fields` | `list[str]` | Model fields to include in detail view
|
|
158
|
+
| `fields` | `list[str]` | Model fields to include in detail view (falls back to ReadSerializer fields if not defined) |
|
|
157
159
|
| `excludes` | `list[str]` | Fields to exclude from detail view |
|
|
158
160
|
| `customs` | `list[tuple]` | Computed fields: `(name, type)` required; `(name, type, default)` optional |
|
|
159
161
|
| `optionals` | `list[tuple[str, type]]` | Optional output fields |
|
|
@@ -280,7 +282,7 @@ class User(ModelSerializer):
|
|
|
280
282
|
# Auto-generate schemas
|
|
281
283
|
UserCreateSchema = User.generate_create_s()
|
|
282
284
|
UserReadSchema = User.generate_read_s()
|
|
283
|
-
UserDetailSchema = User.generate_detail_s() #
|
|
285
|
+
UserDetailSchema = User.generate_detail_s() # Falls back to read schema if DetailSerializer not defined
|
|
284
286
|
UserUpdateSchema = User.generate_update_s()
|
|
285
287
|
UserRelatedSchema = User.generate_related_s()
|
|
286
288
|
```
|
|
@@ -68,7 +68,7 @@ print(util.model_fields)
|
|
|
68
68
|
|
|
69
69
|
### `serializable_fields`
|
|
70
70
|
|
|
71
|
-
Returns serializable fields (ReadSerializer fields or all model fields).
|
|
71
|
+
Returns serializable fields for read operations (ReadSerializer fields or all model fields).
|
|
72
72
|
|
|
73
73
|
```python
|
|
74
74
|
class User(ModelSerializer):
|
|
@@ -84,6 +84,27 @@ print(util.serializable_fields)
|
|
|
84
84
|
# ["id", "username", "email"] (password excluded)
|
|
85
85
|
```
|
|
86
86
|
|
|
87
|
+
### `serializable_detail_fields`
|
|
88
|
+
|
|
89
|
+
Returns serializable fields for detail operations (DetailSerializer fields, or falls back to ReadSerializer fields).
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
class Article(ModelSerializer):
|
|
93
|
+
title = models.CharField(max_length=200)
|
|
94
|
+
summary = models.TextField()
|
|
95
|
+
content = models.TextField()
|
|
96
|
+
|
|
97
|
+
class ReadSerializer:
|
|
98
|
+
fields = ["id", "title", "summary"]
|
|
99
|
+
|
|
100
|
+
class DetailSerializer:
|
|
101
|
+
fields = ["id", "title", "summary", "content"]
|
|
102
|
+
|
|
103
|
+
util = ModelUtil(Article)
|
|
104
|
+
print(util.serializable_fields) # ["id", "title", "summary"]
|
|
105
|
+
print(util.serializable_detail_fields) # ["id", "title", "summary", "content"]
|
|
106
|
+
```
|
|
107
|
+
|
|
87
108
|
### `model_name`
|
|
88
109
|
|
|
89
110
|
Returns the Django internal model name.
|
|
@@ -118,6 +139,10 @@ class Book(ModelSerializer):
|
|
|
118
139
|
select_related=["author", "category"],
|
|
119
140
|
prefetch_related=["tags"],
|
|
120
141
|
)
|
|
142
|
+
detail = ModelQuerySetSchema(
|
|
143
|
+
select_related=["author", "category", "publisher"],
|
|
144
|
+
prefetch_related=["tags", "reviews"],
|
|
145
|
+
)
|
|
121
146
|
queryset_request = ModelQuerySetSchema(
|
|
122
147
|
select_related=[],
|
|
123
148
|
prefetch_related=["related_items"],
|
|
@@ -131,9 +156,10 @@ class Book(ModelSerializer):
|
|
|
131
156
|
]
|
|
132
157
|
```
|
|
133
158
|
|
|
134
|
-
- read
|
|
135
|
-
-
|
|
136
|
-
-
|
|
159
|
+
- **read**: applied to list operations (`is_for="read"`).
|
|
160
|
+
- **detail**: applied to retrieve operations (`is_for="detail"`). Falls back to `read` if not defined.
|
|
161
|
+
- **queryset_request**: applied inside queryset_request hook.
|
|
162
|
+
- **extras**: named configurations available via QueryUtil.SCOPES.
|
|
137
163
|
|
|
138
164
|
## QueryUtil
|
|
139
165
|
|
|
@@ -178,7 +204,7 @@ qs = await ModelUtil(Book).get_objects(
|
|
|
178
204
|
prefetch_related=["tags"],
|
|
179
205
|
),
|
|
180
206
|
with_qs_request=True, # Apply queryset_request hook
|
|
181
|
-
|
|
207
|
+
is_for="read", # union with auto-discovered relations for read
|
|
182
208
|
)
|
|
183
209
|
```
|
|
184
210
|
|
|
@@ -187,7 +213,7 @@ qs = await ModelUtil(Book).get_objects(
|
|
|
187
213
|
- `request` (`HttpRequest`): Current HTTP request
|
|
188
214
|
- `query_data` (`ObjectsQuerySchema | None`): Query configuration (filters, select_related, prefetch_related)
|
|
189
215
|
- `with_qs_request` (`bool`): Apply queryset_request hook if available (default: True)
|
|
190
|
-
- `
|
|
216
|
+
- `is_for` (`Literal["read", "detail"] | None`): Purpose of the query, determines which serializable fields to use for optimization. Use `"read"` for list operations, `"detail"` for retrieve operations. If `None`, only query_data optimizations are applied. (default: None)
|
|
191
217
|
|
|
192
218
|
**Returns:** Optimized `QuerySet`
|
|
193
219
|
|
|
@@ -198,13 +224,13 @@ Fetch a single object by pk or getters with optimizations:
|
|
|
198
224
|
```python
|
|
199
225
|
from ninja_aio.schemas.helpers import ObjectQuerySchema, QuerySchema
|
|
200
226
|
|
|
201
|
-
# by pk + select/prefetch
|
|
227
|
+
# by pk + select/prefetch for detail view
|
|
202
228
|
obj = await ModelUtil(Book).get_object(
|
|
203
229
|
request,
|
|
204
230
|
pk=42,
|
|
205
231
|
query_data=ObjectQuerySchema(select_related=["author"]),
|
|
206
232
|
with_qs_request=True,
|
|
207
|
-
|
|
233
|
+
is_for="detail",
|
|
208
234
|
)
|
|
209
235
|
|
|
210
236
|
# by getters (required if pk omitted)
|
|
@@ -220,7 +246,7 @@ obj = await ModelUtil(Book).get_object(
|
|
|
220
246
|
- `pk` (`int | str | None`): Primary key value (optional if getters provided)
|
|
221
247
|
- `query_data` (`ObjectQuerySchema | QuerySchema | None`): Query configuration
|
|
222
248
|
- `with_qs_request` (`bool`): Apply queryset_request hook if available (default: True)
|
|
223
|
-
- `
|
|
249
|
+
- `is_for` (`Literal["read", "detail"] | None`): Purpose of the query. Use `"detail"` for single object retrieval, `"read"` for list operations. (default: None)
|
|
224
250
|
|
|
225
251
|
**Returns:** Model instance
|
|
226
252
|
|
|
@@ -235,16 +261,17 @@ Uniform serialization methods that accept either instances or query data:
|
|
|
235
261
|
|
|
236
262
|
```python
|
|
237
263
|
schema = Book.generate_read_s()
|
|
264
|
+
detail_schema = Book.generate_detail_s()
|
|
238
265
|
|
|
239
266
|
# single instance
|
|
240
267
|
data = await ModelUtil(Book).read_s(schema, request, instance=obj)
|
|
241
268
|
|
|
242
|
-
# single via getters
|
|
269
|
+
# single via getters (detail view)
|
|
243
270
|
data = await ModelUtil(Book).read_s(
|
|
244
|
-
|
|
271
|
+
detail_schema,
|
|
245
272
|
request,
|
|
246
273
|
query_data=ObjectQuerySchema(getters={"pk": 42}),
|
|
247
|
-
|
|
274
|
+
is_for="detail",
|
|
248
275
|
)
|
|
249
276
|
|
|
250
277
|
# list from queryset
|
|
@@ -255,7 +282,7 @@ items = await ModelUtil(Book).list_read_s(
|
|
|
255
282
|
schema,
|
|
256
283
|
request,
|
|
257
284
|
query_data=ObjectsQuerySchema(filters={"is_published": True}),
|
|
258
|
-
|
|
285
|
+
is_for="read",
|
|
259
286
|
)
|
|
260
287
|
```
|
|
261
288
|
|
|
@@ -265,7 +292,7 @@ items = await ModelUtil(Book).list_read_s(
|
|
|
265
292
|
- `request` (`HttpRequest`): Current HTTP request
|
|
266
293
|
- `instance` (`Model | None`): Model instance to serialize (optional)
|
|
267
294
|
- `query_data` (`ObjectQuerySchema | QuerySchema | None`): Query configuration for fetching (optional)
|
|
268
|
-
- `
|
|
295
|
+
- `is_for` (`Literal["read", "detail"] | None`): Purpose of the query. Use `"detail"` for single object views, `"read"` for list views. (default: None)
|
|
269
296
|
|
|
270
297
|
**Parameters (list_read_s):**
|
|
271
298
|
|
|
@@ -273,11 +300,12 @@ items = await ModelUtil(Book).list_read_s(
|
|
|
273
300
|
- `request` (`HttpRequest`): Current HTTP request
|
|
274
301
|
- `instances` (`QuerySet | list[Model] | None`): Instances to serialize (optional)
|
|
275
302
|
- `query_data` (`ObjectsQuerySchema | None`): Query configuration for fetching (optional)
|
|
276
|
-
- `
|
|
303
|
+
- `is_for` (`Literal["read", "detail"] | None`): Purpose of the query. Typically `"read"` for list views. (default: None)
|
|
277
304
|
|
|
278
305
|
**Behavior:**
|
|
279
306
|
|
|
280
|
-
- When `
|
|
307
|
+
- When `is_for` is specified, select_related and prefetch_related are merged with model-discovered relations based on the operation type
|
|
308
|
+
- When `is_for="detail"` but no `QuerySet.detail` is configured, falls back to `QuerySet.read` optimizations
|
|
281
309
|
- Passing `instance`/`instances` skips fetching; passing `query_data` fetches automatically
|
|
282
310
|
- Either `instance`/`instances` OR `query_data` must be provided, not both
|
|
283
311
|
|
|
@@ -475,12 +475,19 @@ class ArticleSerializer(serializers.Serializer):
|
|
|
475
475
|
schema_out = serializers.SchemaModelConfig(
|
|
476
476
|
fields=["id", "title", "content", "author", "category"]
|
|
477
477
|
)
|
|
478
|
+
schema_detail = serializers.SchemaModelConfig(
|
|
479
|
+
fields=["id", "title", "content", "author", "category", "tags", "comments"]
|
|
480
|
+
)
|
|
478
481
|
|
|
479
482
|
class QuerySet:
|
|
480
483
|
read = ModelQuerySetSchema(
|
|
481
484
|
select_related=["author", "category"],
|
|
482
485
|
prefetch_related=["tags"],
|
|
483
486
|
)
|
|
487
|
+
detail = ModelQuerySetSchema(
|
|
488
|
+
select_related=["author", "category", "author__profile"],
|
|
489
|
+
prefetch_related=["tags", "comments", "comments__author"],
|
|
490
|
+
)
|
|
484
491
|
queryset_request = ModelQuerySetSchema(
|
|
485
492
|
select_related=[],
|
|
486
493
|
prefetch_related=["comments"],
|
|
@@ -494,7 +501,12 @@ class ArticleSerializer(serializers.Serializer):
|
|
|
494
501
|
]
|
|
495
502
|
```
|
|
496
503
|
|
|
497
|
-
The QuerySet configuration is used by ModelUtil to automatically optimize database queries
|
|
504
|
+
The QuerySet configuration is used by ModelUtil to automatically optimize database queries:
|
|
505
|
+
|
|
506
|
+
- **read**: Applied to list operations (`is_for="read"`)
|
|
507
|
+
- **detail**: Applied to retrieve/detail operations (`is_for="detail"`). Falls back to `read` if not defined.
|
|
508
|
+
- **queryset_request**: Applied inside the `queryset_request` hook
|
|
509
|
+
- **extras**: Named configurations available via `QueryUtil.SCOPES`
|
|
498
510
|
|
|
499
511
|
## Complete Example
|
|
500
512
|
|
|
@@ -140,6 +140,49 @@ class EventViewSet(LessEqualDateFilterViewSetMixin, APIViewSet):
|
|
|
140
140
|
query_params = {"created_at": (datetime, None)}
|
|
141
141
|
```
|
|
142
142
|
|
|
143
|
+
## RelationFilterViewSetMixin
|
|
144
|
+
|
|
145
|
+
Filters by related model fields using configurable `RelationFilterSchema` entries.
|
|
146
|
+
|
|
147
|
+
- Behavior: Maps query parameters to Django ORM lookups on related models.
|
|
148
|
+
- Configuration: Define `relations_filters` as a list of `RelationFilterSchema` objects.
|
|
149
|
+
- Query params are automatically registered from `relations_filters`.
|
|
150
|
+
|
|
151
|
+
Each `RelationFilterSchema` requires:
|
|
152
|
+
|
|
153
|
+
- `query_param`: The API query parameter name exposed to clients.
|
|
154
|
+
- `query_filter`: The Django ORM lookup path (e.g., `author__id`, `category__name__icontains`).
|
|
155
|
+
- `filter_type`: Tuple of `(type, default)` for schema generation (e.g., `(int, None)`).
|
|
156
|
+
|
|
157
|
+
Example:
|
|
158
|
+
|
|
159
|
+
```python
|
|
160
|
+
from ninja_aio.views.mixins import RelationFilterViewSetMixin
|
|
161
|
+
from ninja_aio.views.api import APIViewSet
|
|
162
|
+
from ninja_aio.schemas import RelationFilterSchema
|
|
163
|
+
|
|
164
|
+
class BookViewSet(RelationFilterViewSetMixin, APIViewSet):
|
|
165
|
+
model = models.Book
|
|
166
|
+
api = api
|
|
167
|
+
relations_filters = [
|
|
168
|
+
RelationFilterSchema(
|
|
169
|
+
query_param="author",
|
|
170
|
+
query_filter="author__id",
|
|
171
|
+
filter_type=(int, None),
|
|
172
|
+
),
|
|
173
|
+
RelationFilterSchema(
|
|
174
|
+
query_param="category_name",
|
|
175
|
+
query_filter="category__name__icontains",
|
|
176
|
+
filter_type=(str, None),
|
|
177
|
+
),
|
|
178
|
+
]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
This enables:
|
|
182
|
+
|
|
183
|
+
- `GET /books?author=5` → `queryset.filter(author__id=5)`
|
|
184
|
+
- `GET /books?category_name=fiction` → `queryset.filter(category__name__icontains="fiction")`
|
|
185
|
+
|
|
143
186
|
## Tips
|
|
144
187
|
|
|
145
188
|
- Align `query_params` types with expected filter values; prefer Pydantic `date`/`datetime` for date filters so values implement `isoformat`.
|
|
@@ -75,6 +75,9 @@ class QueryUtil:
|
|
|
75
75
|
self.queryset_request_config: ModelQuerySetSchema = self._configs.get(
|
|
76
76
|
self.SCOPES.QUERYSET_REQUEST, ModelQuerySetSchema()
|
|
77
77
|
)
|
|
78
|
+
self.detail_config: ModelQuerySetSchema = self._configs.get(
|
|
79
|
+
self.SCOPES.DETAIL, ModelQuerySetSchema()
|
|
80
|
+
)
|
|
78
81
|
|
|
79
82
|
def _get_config(self, conf_name: str) -> ModelQuerySetSchema:
|
|
80
83
|
"""Helper method to retrieve configuration attributes."""
|
{django_ninja_aio_crud-2.8.0 → django_ninja_aio_crud-2.10.0}/ninja_aio/models/serializers.py
RENAMED
|
@@ -61,6 +61,7 @@ class BaseSerializer:
|
|
|
61
61
|
"""
|
|
62
62
|
|
|
63
63
|
read = ModelQuerySetSchema()
|
|
64
|
+
detail = ModelQuerySetSchema()
|
|
64
65
|
queryset_request = ModelQuerySetSchema()
|
|
65
66
|
extras: list[ModelQuerySetExtraSchema] = []
|
|
66
67
|
|
|
@@ -325,14 +326,18 @@ class BaseSerializer:
|
|
|
325
326
|
]
|
|
326
327
|
|
|
327
328
|
@classmethod
|
|
328
|
-
def get_excluded_fields(cls, s_type:
|
|
329
|
+
def get_excluded_fields(cls, s_type: S_TYPES):
|
|
329
330
|
"""Return excluded field names for the serializer type."""
|
|
330
331
|
return cls._get_fields(s_type, "excludes")
|
|
331
332
|
|
|
332
333
|
@classmethod
|
|
333
|
-
def get_fields(cls, s_type:
|
|
334
|
+
def get_fields(cls, s_type: S_TYPES):
|
|
334
335
|
"""Return explicit declared fields for the serializer type."""
|
|
335
|
-
|
|
336
|
+
fields = cls._get_fields(s_type, "fields")
|
|
337
|
+
# Detail schema falls back to read fields if none declared
|
|
338
|
+
if not fields and s_type == "detail":
|
|
339
|
+
return cls._get_fields("read", "fields")
|
|
340
|
+
return fields
|
|
336
341
|
|
|
337
342
|
@classmethod
|
|
338
343
|
def is_custom(cls, field: str) -> bool:
|
|
@@ -406,7 +411,11 @@ class BaseSerializer:
|
|
|
406
411
|
"""Check if field is a reverse relation (M2M, reverse FK, reverse O2O)."""
|
|
407
412
|
return isinstance(
|
|
408
413
|
field_obj,
|
|
409
|
-
(
|
|
414
|
+
(
|
|
415
|
+
ManyToManyDescriptor,
|
|
416
|
+
ReverseManyToOneDescriptor,
|
|
417
|
+
ReverseOneToOneDescriptor,
|
|
418
|
+
),
|
|
410
419
|
)
|
|
411
420
|
|
|
412
421
|
@classmethod
|
|
@@ -419,9 +428,8 @@ class BaseSerializer:
|
|
|
419
428
|
@classmethod
|
|
420
429
|
def _warn_missing_relation_serializer(cls, field_name: str, model) -> None:
|
|
421
430
|
"""Emit warning for reverse relations without explicit serializer mapping."""
|
|
422
|
-
if (
|
|
423
|
-
|
|
424
|
-
and not getattr(settings, "NINJA_AIO_TESTING", False)
|
|
431
|
+
if not isinstance(model, ModelSerializerMeta) and not getattr(
|
|
432
|
+
settings, "NINJA_AIO_TESTING", False
|
|
425
433
|
):
|
|
426
434
|
warnings.warn(
|
|
427
435
|
f"{cls.__name__}: reverse relation '{field_name}' is listed in read fields "
|
|
@@ -469,7 +477,9 @@ class BaseSerializer:
|
|
|
469
477
|
tuple: (fields, reverse_rels, excludes, customs_with_forward_rels, optionals)
|
|
470
478
|
"""
|
|
471
479
|
if schema_type not in ("Out", "Detail"):
|
|
472
|
-
raise ValueError(
|
|
480
|
+
raise ValueError(
|
|
481
|
+
"get_schema_out_data only supports 'Out' or 'Detail' types"
|
|
482
|
+
)
|
|
473
483
|
|
|
474
484
|
fields_type = "read" if schema_type == "Out" else "detail"
|
|
475
485
|
model = cls._get_model()
|
|
@@ -602,7 +612,7 @@ class BaseSerializer:
|
|
|
602
612
|
@classmethod
|
|
603
613
|
def generate_detail_s(cls, depth: int = 1) -> Schema:
|
|
604
614
|
"""Generate detail (single object Out) schema."""
|
|
605
|
-
return cls._generate_model_schema("Detail", depth)
|
|
615
|
+
return cls._generate_model_schema("Detail", depth) or cls.generate_read_s(depth)
|
|
606
616
|
|
|
607
617
|
@classmethod
|
|
608
618
|
def generate_create_s(cls) -> Schema:
|