django-ninja-aio-crud 2.7.0__tar.gz → 2.8.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.
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/.github/workflows/docs.yml +2 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/PKG-INFO +1 -1
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/models/model_serializer.md +69 -7
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/models/serializers.md +27 -2
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/views/api_view_set.md +80 -20
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/helpers/api.py +1 -1
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/models/serializers.py +140 -67
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/schemas/helpers.py +32 -6
- django_ninja_aio_crud-2.8.0/ninja_aio/types.py +24 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/views/api.py +21 -7
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/generics/views.py +1 -1
- django_ninja_aio_crud-2.8.0/tests/helpers/test_many_to_many_api.py +282 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_app/models.py +13 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_serializers.py +121 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/views/test_viewset.py +141 -0
- django_ninja_aio_crud-2.7.0/ninja_aio/types.py +0 -19
- django_ninja_aio_crud-2.7.0/tests/helpers/test_many_to_many_api.py +0 -118
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/README.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/models/model_util.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/views/api_view.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/views/decorators.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/views/mixins.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/extra.css +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/index.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/main.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/mkdocs.yml +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/helpers/query.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/models/utils.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_app/serializers.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_app/views.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/test_settings.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/tests/views/test_views.py +0 -0
|
@@ -19,6 +19,7 @@ on:
|
|
|
19
19
|
- "2.5"
|
|
20
20
|
- "2.6"
|
|
21
21
|
- "2.6.1"
|
|
22
|
+
- "2.7.0"
|
|
22
23
|
make_latest:
|
|
23
24
|
description: 'Set as "latest" and default?'
|
|
24
25
|
type: boolean
|
|
@@ -41,6 +42,7 @@ on:
|
|
|
41
42
|
- "2.5"
|
|
42
43
|
- "2.6"
|
|
43
44
|
- "2.6.1"
|
|
45
|
+
- "2.7.0"
|
|
44
46
|
delete_confirm:
|
|
45
47
|
description: 'Confirm deletion of the selected version'
|
|
46
48
|
type: boolean
|
{django_ninja_aio_crud-2.7.0 → django_ninja_aio_crud-2.8.0}/docs/api/models/model_serializer.md
RENAMED
|
@@ -145,6 +145,66 @@ class User(ModelSerializer):
|
|
|
145
145
|
}
|
|
146
146
|
```
|
|
147
147
|
|
|
148
|
+
### DetailSerializer
|
|
149
|
+
|
|
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
|
+
|
|
152
|
+
**Attributes:**
|
|
153
|
+
|
|
154
|
+
| Attribute | Type | Description |
|
|
155
|
+
| ----------- | ------------------------ | ----------------------------------------------------------------------------- |
|
|
156
|
+
| `fields` | `list[str]` | Model fields to include in detail view |
|
|
157
|
+
| `excludes` | `list[str]` | Fields to exclude from detail view |
|
|
158
|
+
| `customs` | `list[tuple]` | Computed fields: `(name, type)` required; `(name, type, default)` optional |
|
|
159
|
+
| `optionals` | `list[tuple[str, type]]` | Optional output fields |
|
|
160
|
+
|
|
161
|
+
**Example:**
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
class Article(ModelSerializer):
|
|
165
|
+
title = models.CharField(max_length=200)
|
|
166
|
+
summary = models.TextField()
|
|
167
|
+
content = models.TextField()
|
|
168
|
+
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
169
|
+
tags = models.ManyToManyField(Tag)
|
|
170
|
+
view_count = models.IntegerField(default=0)
|
|
171
|
+
|
|
172
|
+
class ReadSerializer:
|
|
173
|
+
# List view: minimal fields for performance
|
|
174
|
+
fields = ["id", "title", "summary", "author"]
|
|
175
|
+
|
|
176
|
+
class DetailSerializer:
|
|
177
|
+
# Detail view: all fields including expensive relations
|
|
178
|
+
fields = ["id", "title", "summary", "content", "author", "tags", "view_count"]
|
|
179
|
+
customs = [
|
|
180
|
+
("reading_time", int, lambda obj: len(obj.content.split()) // 200),
|
|
181
|
+
]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Generated Output (List):**
|
|
185
|
+
|
|
186
|
+
```json
|
|
187
|
+
[
|
|
188
|
+
{"id": 1, "title": "Getting Started", "summary": "...", "author": {...}},
|
|
189
|
+
{"id": 2, "title": "Advanced Topics", "summary": "...", "author": {...}}
|
|
190
|
+
]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
**Generated Output (Detail):**
|
|
194
|
+
|
|
195
|
+
```json
|
|
196
|
+
{
|
|
197
|
+
"id": 1,
|
|
198
|
+
"title": "Getting Started",
|
|
199
|
+
"summary": "...",
|
|
200
|
+
"content": "Full article content here...",
|
|
201
|
+
"author": {...},
|
|
202
|
+
"tags": [{"id": 1, "name": "python"}, {"id": 2, "name": "django"}],
|
|
203
|
+
"view_count": 1234,
|
|
204
|
+
"reading_time": 5
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
148
208
|
### UpdateSerializer
|
|
149
209
|
|
|
150
210
|
Describes how to build an update (partial/full) input schema.
|
|
@@ -194,14 +254,15 @@ class User(ModelSerializer):
|
|
|
194
254
|
|
|
195
255
|
### Auto-Generated Schemas
|
|
196
256
|
|
|
197
|
-
ModelSerializer automatically generates
|
|
257
|
+
ModelSerializer automatically generates five schema types:
|
|
198
258
|
|
|
199
|
-
| Method
|
|
200
|
-
|
|
|
201
|
-
| `generate_create_s()`
|
|
202
|
-
| `generate_update_s()`
|
|
203
|
-
| `generate_read_s(depth=1)`
|
|
204
|
-
| `
|
|
259
|
+
| Method | Schema Type | Purpose |
|
|
260
|
+
| ---------------------------- | ------------------ | ------------------------------------ |
|
|
261
|
+
| `generate_create_s()` | Input ("In") | POST endpoint payload |
|
|
262
|
+
| `generate_update_s()` | Input ("Patch") | PATCH/PUT endpoint payload |
|
|
263
|
+
| `generate_read_s(depth=1)` | Output ("Out") | List response with nested relations |
|
|
264
|
+
| `generate_detail_s(depth=1)` | Output ("Detail") | Single object response (retrieve) |
|
|
265
|
+
| `generate_related_s()` | Output ("Related") | Compact nested representation |
|
|
205
266
|
|
|
206
267
|
**Example:**
|
|
207
268
|
|
|
@@ -219,6 +280,7 @@ class User(ModelSerializer):
|
|
|
219
280
|
# Auto-generate schemas
|
|
220
281
|
UserCreateSchema = User.generate_create_s()
|
|
221
282
|
UserReadSchema = User.generate_read_s()
|
|
283
|
+
UserDetailSchema = User.generate_detail_s() # Returns None if DetailSerializer not defined
|
|
222
284
|
UserUpdateSchema = User.generate_update_s()
|
|
223
285
|
UserRelatedSchema = User.generate_related_s()
|
|
224
286
|
```
|
|
@@ -37,7 +37,8 @@ Define a Serializer subclass with a nested Meta:
|
|
|
37
37
|
|
|
38
38
|
- **model**: Django model class
|
|
39
39
|
- **schema_in**: SchemaModelConfig for create inputs
|
|
40
|
-
- **schema_out**: SchemaModelConfig for read outputs
|
|
40
|
+
- **schema_out**: SchemaModelConfig for read outputs (list endpoint)
|
|
41
|
+
- **schema_detail**: SchemaModelConfig for detail outputs (retrieve endpoint)
|
|
41
42
|
- **schema_update**: SchemaModelConfig for patch/update inputs
|
|
42
43
|
- **relations_serializers**: Mapping of relation field name -> Serializer class, **string reference**, or **Union of serializers** (supports forward/circular dependencies and polymorphic relations)
|
|
43
44
|
|
|
@@ -55,13 +56,37 @@ Generate schemas explicitly using these methods:
|
|
|
55
56
|
```python
|
|
56
57
|
# Explicitly generate schemas when needed
|
|
57
58
|
ArticleSerializer.generate_create_s() # Returns create (In) schema
|
|
58
|
-
ArticleSerializer.generate_read_s() # Returns read (Out) schema
|
|
59
|
+
ArticleSerializer.generate_read_s() # Returns read (Out) schema for list endpoint
|
|
60
|
+
ArticleSerializer.generate_detail_s() # Returns detail (Out) schema for retrieve endpoint
|
|
59
61
|
ArticleSerializer.generate_update_s() # Returns update (Patch) schema
|
|
60
62
|
ArticleSerializer.generate_related_s() # Returns related (nested) schema
|
|
61
63
|
```
|
|
62
64
|
|
|
63
65
|
Schemas support **forward references and circular dependencies** via string references in `relations_serializers`.
|
|
64
66
|
|
|
67
|
+
### Detail Schema for Retrieve Endpoint
|
|
68
|
+
|
|
69
|
+
Use `schema_detail` when you want the retrieve endpoint to return more fields than the list endpoint:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
class ArticleSerializer(serializers.Serializer):
|
|
73
|
+
class Meta:
|
|
74
|
+
model = models.Article
|
|
75
|
+
schema_out = serializers.SchemaModelConfig(
|
|
76
|
+
# List view: minimal fields for performance
|
|
77
|
+
fields=["id", "title", "summary"]
|
|
78
|
+
)
|
|
79
|
+
schema_detail = serializers.SchemaModelConfig(
|
|
80
|
+
# Detail view: all fields including expensive relations
|
|
81
|
+
fields=["id", "title", "summary", "content", "author", "tags"],
|
|
82
|
+
customs=[("reading_time", int, lambda obj: len(obj.content.split()) // 200)]
|
|
83
|
+
)
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
When used with `APIViewSet`:
|
|
87
|
+
- **List endpoint** (`GET /articles/`) uses `schema_out`
|
|
88
|
+
- **Retrieve endpoint** (`GET /articles/{pk}`) uses `schema_detail` (falls back to `schema_out` if not defined)
|
|
89
|
+
|
|
65
90
|
## Example: simple FK
|
|
66
91
|
|
|
67
92
|
```python
|
|
@@ -4,13 +4,13 @@
|
|
|
4
4
|
|
|
5
5
|
## Generated CRUD Endpoints
|
|
6
6
|
|
|
7
|
-
| Method | Path | Summary | Response
|
|
8
|
-
| ------ | --------------- | -------------- |
|
|
9
|
-
| POST | `/{base}/` | Create Model | `201 schema_out`
|
|
10
|
-
| GET | `/{base}/` | List Models | `200 List[schema_out]` (paginated)
|
|
11
|
-
| GET | `/{base}/{pk}` | Retrieve Model | `200 schema_out`
|
|
12
|
-
| PATCH | `/{base}/{pk}/` | Update Model | `200 schema_out`
|
|
13
|
-
| DELETE | `/{base}/{pk}/` | Delete Model | `204 No Content`
|
|
7
|
+
| Method | Path | Summary | Response |
|
|
8
|
+
| ------ | --------------- | -------------- | --------------------------------------------- |
|
|
9
|
+
| POST | `/{base}/` | Create Model | `201 schema_out` |
|
|
10
|
+
| GET | `/{base}/` | List Models | `200 List[schema_out]` (paginated) |
|
|
11
|
+
| GET | `/{base}/{pk}` | Retrieve Model | `200 schema_detail` (or `schema_out` if none) |
|
|
12
|
+
| PATCH | `/{base}/{pk}/` | Update Model | `200 schema_out` |
|
|
13
|
+
| DELETE | `/{base}/{pk}/` | Delete Model | `204 No Content` |
|
|
14
14
|
|
|
15
15
|
Notes:
|
|
16
16
|
|
|
@@ -100,7 +100,8 @@ class ArticleViewSet(APIViewSet):
|
|
|
100
100
|
| `api` | `NinjaAPI` | — | API instance (required) |
|
|
101
101
|
| `serializer_class` | `Serializer \| None` | `None` | Serializer class for plain models (alternative to ModelSerializer) |
|
|
102
102
|
| `schema_in` | `Schema \| None` | `None` (auto) | Create input schema override |
|
|
103
|
-
| `schema_out` | `Schema \| None` | `None` (auto) |
|
|
103
|
+
| `schema_out` | `Schema \| None` | `None` (auto) | List/output schema override |
|
|
104
|
+
| `schema_detail` | `Schema \| None` | `None` (auto) | Retrieve/detail schema override (falls back to `schema_out`) |
|
|
104
105
|
| `schema_update` | `Schema \| None` | `None` (auto) | Update input schema override |
|
|
105
106
|
| `pagination_class` | `type[AsyncPaginationBase]` | `PageNumberPagination` | Pagination strategy |
|
|
106
107
|
| `query_params` | `dict[str, tuple[type, ...]]` | `{}` | List endpoint filters definition |
|
|
@@ -150,6 +151,7 @@ The transaction behavior is applied by default. Custom decorators can be added v
|
|
|
150
151
|
If `model` is a subclass of `ModelSerializerMeta`:
|
|
151
152
|
|
|
152
153
|
- `schema_out` is generated from `ReadSerializer`
|
|
154
|
+
- `schema_detail` is generated from `DetailSerializer` (optional, falls back to `schema_out`)
|
|
153
155
|
- `schema_in` from `CreateSerializer`
|
|
154
156
|
- `schema_update` from `UpdateSerializer`
|
|
155
157
|
|
|
@@ -173,7 +175,42 @@ class ArticleViewSet(APIViewSet):
|
|
|
173
175
|
serializer_class = ArticleSerializer
|
|
174
176
|
```
|
|
175
177
|
|
|
176
|
-
Otherwise provide schemas manually via `schema_in`, `schema_out`, and `schema_update` attributes.
|
|
178
|
+
Otherwise provide schemas manually via `schema_in`, `schema_out`, `schema_detail`, and `schema_update` attributes.
|
|
179
|
+
|
|
180
|
+
### Detail Schema for Retrieve Endpoint
|
|
181
|
+
|
|
182
|
+
Use `schema_detail` (or `DetailSerializer` on ModelSerializer) when you want the retrieve endpoint to return more fields than the list endpoint. This is useful for:
|
|
183
|
+
|
|
184
|
+
- **Performance optimization**: List endpoints return minimal fields, retrieve endpoints include expensive relations
|
|
185
|
+
- **API design**: Clients get a summary in lists and full details on individual requests
|
|
186
|
+
|
|
187
|
+
```python
|
|
188
|
+
from ninja_aio.models import ModelSerializer
|
|
189
|
+
from django.db import models
|
|
190
|
+
|
|
191
|
+
class Article(ModelSerializer):
|
|
192
|
+
title = models.CharField(max_length=200)
|
|
193
|
+
summary = models.TextField()
|
|
194
|
+
content = models.TextField()
|
|
195
|
+
author = models.ForeignKey(User, on_delete=models.CASCADE)
|
|
196
|
+
tags = models.ManyToManyField(Tag)
|
|
197
|
+
|
|
198
|
+
class ReadSerializer:
|
|
199
|
+
# List view: minimal fields
|
|
200
|
+
fields = ["id", "title", "summary"]
|
|
201
|
+
|
|
202
|
+
class DetailSerializer:
|
|
203
|
+
# Detail view: all fields
|
|
204
|
+
fields = ["id", "title", "summary", "content", "author", "tags"]
|
|
205
|
+
|
|
206
|
+
@api.viewset(model=Article)
|
|
207
|
+
class ArticleViewSet(APIViewSet):
|
|
208
|
+
pass # Schemas auto-generated from model
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
Endpoints behavior:
|
|
212
|
+
- `GET /articles/` returns `[{"id": 1, "title": "...", "summary": "..."}, ...]`
|
|
213
|
+
- `GET /articles/1` returns `{"id": 1, "title": "...", "summary": "...", "content": "...", "author": {...}, "tags": [...]}`
|
|
177
214
|
|
|
178
215
|
## List Filtering
|
|
179
216
|
|
|
@@ -254,6 +291,7 @@ Relations are declared via `M2MRelationSchema` objects (not tuples). Each schema
|
|
|
254
291
|
- `get`: enable GET listing (bool)
|
|
255
292
|
- `filters`: dict of `{param_name: (type, default)}` for relation-level filtering
|
|
256
293
|
- `related_schema`: optional pre-built schema for the related model (auto-generated if the `model` is a `ModelSerializer`)
|
|
294
|
+
- `serializer_class`: optional `Serializer` class for plain Django models. When provided, `related_schema` is auto-generated from the serializer. Cannot be used when `model` is a `ModelSerializer`.
|
|
257
295
|
- `append_slash`: bool to control trailing slash for the GET relation endpoint path. Defaults to `False` (no trailing slash) for backward compatibility. When `True`, the GET path ends with a trailing slash.
|
|
258
296
|
|
|
259
297
|
If `path` is empty it falls back to the related model verbose name (lowercase plural).
|
|
@@ -285,21 +323,43 @@ async def tags_query_params_handler(self, queryset, filters_dict):
|
|
|
285
323
|
|
|
286
324
|
Warning: Model support
|
|
287
325
|
|
|
288
|
-
- You can supply a standard Django `Model` (not a `ModelSerializer`) in `M2MRelationSchema.model`. When doing so you must provide `related_schema` manually
|
|
326
|
+
- You can supply a standard Django `Model` (not a `ModelSerializer`) in `M2MRelationSchema.model`. When doing so you must provide either `related_schema` manually or `serializer_class`:
|
|
289
327
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
328
|
+
=== "With related_schema"
|
|
329
|
+
```python
|
|
330
|
+
M2MRelationSchema(
|
|
331
|
+
model=Tag, # plain django.db.models.Model
|
|
332
|
+
related_name="tags",
|
|
333
|
+
related_schema=TagOut, # a Pydantic/Ninja Schema you define
|
|
334
|
+
add=True,
|
|
335
|
+
remove=True,
|
|
336
|
+
get=True,
|
|
337
|
+
)
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
=== "With serializer_class"
|
|
341
|
+
```python
|
|
342
|
+
from ninja_aio.models import serializers
|
|
343
|
+
|
|
344
|
+
class TagSerializer(serializers.Serializer):
|
|
345
|
+
class Meta:
|
|
346
|
+
model = Tag
|
|
347
|
+
schema_out = serializers.SchemaModelConfig(fields=["id", "name"])
|
|
348
|
+
|
|
349
|
+
M2MRelationSchema(
|
|
350
|
+
model=Tag, # plain django.db.models.Model
|
|
351
|
+
related_name="tags",
|
|
352
|
+
serializer_class=TagSerializer, # auto-generates related_schema
|
|
353
|
+
add=True,
|
|
354
|
+
remove=True,
|
|
355
|
+
get=True,
|
|
356
|
+
)
|
|
357
|
+
```
|
|
300
358
|
|
|
301
359
|
For `ModelSerializer` models, `related_schema` can be inferred automatically (via internal helpers).
|
|
302
360
|
|
|
361
|
+
Note: You cannot use `serializer_class` when `model` is already a `ModelSerializer` - this will raise a `ValueError`.
|
|
362
|
+
|
|
303
363
|
Example with filters:
|
|
304
364
|
|
|
305
365
|
```python
|
|
@@ -472,7 +472,7 @@ class ManyToManyAPI:
|
|
|
472
472
|
model = relation.model
|
|
473
473
|
related_name = relation.related_name
|
|
474
474
|
m2m_auth = relation.auth or self.default_auth
|
|
475
|
-
rel_util = ModelUtil(model)
|
|
475
|
+
rel_util = ModelUtil(model, serializer_class=relation.serializer_class)
|
|
476
476
|
rel_path = relation.path or rel_util.verbose_name_path_resolver()
|
|
477
477
|
related_schema = relation.related_schema
|
|
478
478
|
m2m_add, m2m_remove, m2m_get = relation.add, relation.remove, relation.get
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from typing import Any, List, Optional, Union, get_args, get_origin, ForwardRef
|
|
1
|
+
from typing import Any, List, Literal, Optional, Union, get_args, get_origin, ForwardRef
|
|
2
2
|
import warnings
|
|
3
3
|
import sys
|
|
4
4
|
|
|
@@ -15,7 +15,13 @@ from django.db.models.fields.related_descriptors import (
|
|
|
15
15
|
ForwardOneToOneDescriptor,
|
|
16
16
|
)
|
|
17
17
|
|
|
18
|
-
from ninja_aio.types import
|
|
18
|
+
from ninja_aio.types import (
|
|
19
|
+
S_TYPES,
|
|
20
|
+
F_TYPES,
|
|
21
|
+
SCHEMA_TYPES,
|
|
22
|
+
ModelSerializerMeta,
|
|
23
|
+
SerializerMeta,
|
|
24
|
+
)
|
|
19
25
|
from ninja_aio.schemas.helpers import (
|
|
20
26
|
ModelQuerySetSchema,
|
|
21
27
|
ModelQuerySetExtraSchema,
|
|
@@ -98,6 +104,7 @@ class BaseSerializer:
|
|
|
98
104
|
module = sys.modules.get(module_path)
|
|
99
105
|
if module is None:
|
|
100
106
|
import importlib
|
|
107
|
+
|
|
101
108
|
module = importlib.import_module(module_path)
|
|
102
109
|
|
|
103
110
|
# Get the serializer class from the module
|
|
@@ -136,7 +143,9 @@ class BaseSerializer:
|
|
|
136
143
|
return serializer_class
|
|
137
144
|
|
|
138
145
|
@classmethod
|
|
139
|
-
def _resolve_serializer_reference(
|
|
146
|
+
def _resolve_serializer_reference(
|
|
147
|
+
cls, serializer_ref: str | type | Any
|
|
148
|
+
) -> type | Any:
|
|
140
149
|
"""
|
|
141
150
|
Resolve a serializer reference that may be a string, a class, or a Union of serializers.
|
|
142
151
|
|
|
@@ -217,11 +226,11 @@ class BaseSerializer:
|
|
|
217
226
|
Schema | Union[Schema, ...] | None
|
|
218
227
|
Union of generated schemas or None if all schemas are None.
|
|
219
228
|
"""
|
|
220
|
-
# Generate schemas for each serializer in the Union
|
|
229
|
+
# Generate schemas for each serializer in the Union (single call per serializer)
|
|
221
230
|
schemas = tuple(
|
|
222
|
-
|
|
231
|
+
schema
|
|
223
232
|
for serializer_type in get_args(resolved_union)
|
|
224
|
-
if serializer_type.generate_related_s() is not None
|
|
233
|
+
if (schema := serializer_type.generate_related_s()) is not None
|
|
225
234
|
)
|
|
226
235
|
|
|
227
236
|
if not schemas:
|
|
@@ -393,72 +402,100 @@ class BaseSerializer:
|
|
|
393
402
|
return (field_name, schema | None, None)
|
|
394
403
|
|
|
395
404
|
@classmethod
|
|
396
|
-
def
|
|
405
|
+
def _is_reverse_relation(cls, field_obj) -> bool:
|
|
406
|
+
"""Check if field is a reverse relation (M2M, reverse FK, reverse O2O)."""
|
|
407
|
+
return isinstance(
|
|
408
|
+
field_obj,
|
|
409
|
+
(ManyToManyDescriptor, ReverseManyToOneDescriptor, ReverseOneToOneDescriptor),
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
@classmethod
|
|
413
|
+
def _is_forward_relation(cls, field_obj) -> bool:
|
|
414
|
+
"""Check if field is a forward relation (FK, O2O)."""
|
|
415
|
+
return isinstance(
|
|
416
|
+
field_obj, (ForwardOneToOneDescriptor, ForwardManyToOneDescriptor)
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
@classmethod
|
|
420
|
+
def _warn_missing_relation_serializer(cls, field_name: str, model) -> None:
|
|
421
|
+
"""Emit warning for reverse relations without explicit serializer mapping."""
|
|
422
|
+
if (
|
|
423
|
+
not isinstance(model, ModelSerializerMeta)
|
|
424
|
+
and not getattr(settings, "NINJA_AIO_TESTING", False)
|
|
425
|
+
):
|
|
426
|
+
warnings.warn(
|
|
427
|
+
f"{cls.__name__}: reverse relation '{field_name}' is listed in read fields "
|
|
428
|
+
"but has no entry in relations_serializers; it will be auto-resolved only "
|
|
429
|
+
"for ModelSerializer relations, otherwise skipped.",
|
|
430
|
+
UserWarning,
|
|
431
|
+
stacklevel=3,
|
|
432
|
+
)
|
|
433
|
+
|
|
434
|
+
@classmethod
|
|
435
|
+
def _process_field(
|
|
436
|
+
cls,
|
|
437
|
+
field_name: str,
|
|
438
|
+
model,
|
|
439
|
+
relations_serializers: dict,
|
|
440
|
+
) -> tuple[str | None, tuple | None, tuple | None]:
|
|
441
|
+
"""
|
|
442
|
+
Process a single field and determine its classification.
|
|
443
|
+
|
|
444
|
+
Returns:
|
|
445
|
+
(plain_field, reverse_rel, forward_rel) - only one will be non-None
|
|
446
|
+
"""
|
|
447
|
+
field_obj = getattr(model, field_name)
|
|
448
|
+
|
|
449
|
+
if cls._is_reverse_relation(field_obj):
|
|
450
|
+
if field_name not in relations_serializers:
|
|
451
|
+
cls._warn_missing_relation_serializer(field_name, model)
|
|
452
|
+
rel_tuple = cls._build_schema_reverse_rel(field_name, field_obj)
|
|
453
|
+
return (None, rel_tuple, None)
|
|
454
|
+
|
|
455
|
+
if cls._is_forward_relation(field_obj):
|
|
456
|
+
rel_tuple = cls._build_schema_forward_rel(field_name, field_obj)
|
|
457
|
+
if rel_tuple is True:
|
|
458
|
+
return (field_name, None, None)
|
|
459
|
+
return (None, None, rel_tuple)
|
|
460
|
+
|
|
461
|
+
return (field_name, None, None)
|
|
462
|
+
|
|
463
|
+
@classmethod
|
|
464
|
+
def get_schema_out_data(cls, schema_type: Literal["Out", "Detail"] = "Out"):
|
|
397
465
|
"""
|
|
398
|
-
Collect components for
|
|
399
|
-
|
|
400
|
-
|
|
466
|
+
Collect components for output schema generation (Out or Detail).
|
|
467
|
+
|
|
468
|
+
Returns:
|
|
469
|
+
tuple: (fields, reverse_rels, excludes, customs_with_forward_rels, optionals)
|
|
401
470
|
"""
|
|
471
|
+
if schema_type not in ("Out", "Detail"):
|
|
472
|
+
raise ValueError("get_schema_out_data only supports 'Out' or 'Detail' types")
|
|
473
|
+
|
|
474
|
+
fields_type = "read" if schema_type == "Out" else "detail"
|
|
475
|
+
model = cls._get_model()
|
|
476
|
+
relations_serializers = cls._get_relations_serializers() or {}
|
|
477
|
+
|
|
402
478
|
fields: list[str] = []
|
|
403
479
|
reverse_rels: list[tuple] = []
|
|
404
|
-
|
|
405
|
-
relations_serializers = cls._get_relations_serializers() or {}
|
|
406
|
-
model = cls._get_model()
|
|
480
|
+
forward_rels: list[tuple] = []
|
|
407
481
|
|
|
408
|
-
for
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
field_obj,
|
|
412
|
-
(
|
|
413
|
-
ManyToManyDescriptor,
|
|
414
|
-
ReverseManyToOneDescriptor,
|
|
415
|
-
ReverseOneToOneDescriptor,
|
|
416
|
-
),
|
|
482
|
+
for field_name in cls.get_fields(fields_type):
|
|
483
|
+
plain, reverse, forward = cls._process_field(
|
|
484
|
+
field_name, model, relations_serializers
|
|
417
485
|
)
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
is_reverse
|
|
425
|
-
and not isinstance(model, ModelSerializerMeta)
|
|
426
|
-
and f not in relations_serializers
|
|
427
|
-
and not getattr(settings, "NINJA_AIO_TESTING", False)
|
|
428
|
-
):
|
|
429
|
-
warnings.warn(
|
|
430
|
-
f"{cls.__name__}: reverse relation '{f}' is listed in read fields but has no entry in relations_serializers; "
|
|
431
|
-
"it will be auto-resolved only for ModelSerializer relations, otherwise skipped.",
|
|
432
|
-
UserWarning,
|
|
433
|
-
stacklevel=2,
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
# Reverse relations
|
|
437
|
-
if is_reverse:
|
|
438
|
-
rel_tuple = cls._build_schema_reverse_rel(f, field_obj)
|
|
439
|
-
if rel_tuple:
|
|
440
|
-
reverse_rels.append(rel_tuple)
|
|
441
|
-
continue
|
|
442
|
-
|
|
443
|
-
# Forward relations
|
|
444
|
-
if is_forward:
|
|
445
|
-
rel_tuple = cls._build_schema_forward_rel(f, field_obj)
|
|
446
|
-
if rel_tuple is True:
|
|
447
|
-
fields.append(f)
|
|
448
|
-
elif rel_tuple:
|
|
449
|
-
rels.append(rel_tuple)
|
|
450
|
-
# None -> skip entirely
|
|
451
|
-
continue
|
|
452
|
-
|
|
453
|
-
# Plain field
|
|
454
|
-
fields.append(f)
|
|
486
|
+
if plain:
|
|
487
|
+
fields.append(plain)
|
|
488
|
+
if reverse:
|
|
489
|
+
reverse_rels.append(reverse)
|
|
490
|
+
if forward:
|
|
491
|
+
forward_rels.append(forward)
|
|
455
492
|
|
|
456
493
|
return (
|
|
457
494
|
fields,
|
|
458
495
|
reverse_rels,
|
|
459
|
-
cls.get_excluded_fields(
|
|
460
|
-
cls.get_custom_fields(
|
|
461
|
-
cls.get_optional_fields(
|
|
496
|
+
cls.get_excluded_fields(fields_type),
|
|
497
|
+
cls.get_custom_fields(fields_type) + forward_rels,
|
|
498
|
+
cls.get_optional_fields(fields_type),
|
|
462
499
|
)
|
|
463
500
|
|
|
464
501
|
@classmethod
|
|
@@ -474,15 +511,16 @@ class BaseSerializer:
|
|
|
474
511
|
model = cls._get_model()
|
|
475
512
|
|
|
476
513
|
# Handle special schema types with custom logic
|
|
477
|
-
if schema_type == "Out":
|
|
514
|
+
if schema_type == "Out" or schema_type == "Detail":
|
|
478
515
|
fields, reverse_rels, excludes, customs, optionals = (
|
|
479
|
-
cls.get_schema_out_data()
|
|
516
|
+
cls.get_schema_out_data(schema_type)
|
|
480
517
|
)
|
|
481
518
|
if not any([fields, reverse_rels, excludes, customs]):
|
|
482
519
|
return None
|
|
520
|
+
schema_name = "SchemaOut" if schema_type == "Out" else "DetailSchemaOut"
|
|
483
521
|
return create_schema(
|
|
484
522
|
model=model,
|
|
485
|
-
name=f"{model._meta.model_name}
|
|
523
|
+
name=f"{model._meta.model_name}{schema_name}",
|
|
486
524
|
depth=depth,
|
|
487
525
|
fields=fields,
|
|
488
526
|
custom_fields=reverse_rels + customs + optionals,
|
|
@@ -561,6 +599,11 @@ class BaseSerializer:
|
|
|
561
599
|
"""Generate read (Out) schema."""
|
|
562
600
|
return cls._generate_model_schema("Out", depth)
|
|
563
601
|
|
|
602
|
+
@classmethod
|
|
603
|
+
def generate_detail_s(cls, depth: int = 1) -> Schema:
|
|
604
|
+
"""Generate detail (single object Out) schema."""
|
|
605
|
+
return cls._generate_model_schema("Detail", depth)
|
|
606
|
+
|
|
564
607
|
@classmethod
|
|
565
608
|
def generate_create_s(cls) -> Schema:
|
|
566
609
|
"""Generate create (In) schema."""
|
|
@@ -659,6 +702,26 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
659
702
|
optionals: list[tuple[str, type]] = []
|
|
660
703
|
excludes: list[str] = []
|
|
661
704
|
|
|
705
|
+
class DetailSerializer:
|
|
706
|
+
"""Configuration describing detail (single object) read schema.
|
|
707
|
+
|
|
708
|
+
Attributes
|
|
709
|
+
----------
|
|
710
|
+
fields : list[str]
|
|
711
|
+
Explicit model fields to include.
|
|
712
|
+
excludes : list[str]
|
|
713
|
+
Fields to force exclude (safety).
|
|
714
|
+
customs : list[tuple[str, type, Any]]
|
|
715
|
+
Computed / synthetic output attributes.
|
|
716
|
+
optionals : list[tuple[str, type]]
|
|
717
|
+
Optional output fields.
|
|
718
|
+
"""
|
|
719
|
+
|
|
720
|
+
fields: list[str] = []
|
|
721
|
+
customs: list[tuple[str, type, Any]] = []
|
|
722
|
+
optionals: list[tuple[str, type]] = []
|
|
723
|
+
excludes: list[str] = []
|
|
724
|
+
|
|
662
725
|
class UpdateSerializer:
|
|
663
726
|
"""Configuration describing update (PATCH/PUT) schema.
|
|
664
727
|
|
|
@@ -684,6 +747,7 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
684
747
|
"create": "CreateSerializer",
|
|
685
748
|
"update": "UpdateSerializer",
|
|
686
749
|
"read": "ReadSerializer",
|
|
750
|
+
"detail": "DetailSerializer",
|
|
687
751
|
}
|
|
688
752
|
|
|
689
753
|
@classmethod
|
|
@@ -852,7 +916,7 @@ class SchemaModelConfig(Schema):
|
|
|
852
916
|
customs: Optional[List[tuple[str, type, Any]]] = None
|
|
853
917
|
|
|
854
918
|
|
|
855
|
-
class Serializer(BaseSerializer):
|
|
919
|
+
class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
856
920
|
"""
|
|
857
921
|
Serializer
|
|
858
922
|
----------
|
|
@@ -867,6 +931,7 @@ class Serializer(BaseSerializer):
|
|
|
867
931
|
"create": "in",
|
|
868
932
|
"update": "update",
|
|
869
933
|
"read": "out",
|
|
934
|
+
"detail": "detail",
|
|
870
935
|
}
|
|
871
936
|
|
|
872
937
|
def __init_subclass__(cls, **kwargs):
|
|
@@ -884,6 +949,7 @@ class Serializer(BaseSerializer):
|
|
|
884
949
|
schema_in: Optional[SchemaModelConfig] = None
|
|
885
950
|
schema_out: Optional[SchemaModelConfig] = None
|
|
886
951
|
schema_update: Optional[SchemaModelConfig] = None
|
|
952
|
+
schema_detail: Optional[SchemaModelConfig] = None
|
|
887
953
|
relations_serializers: dict[str, "Serializer"] = {}
|
|
888
954
|
|
|
889
955
|
@classmethod
|
|
@@ -908,6 +974,8 @@ class Serializer(BaseSerializer):
|
|
|
908
974
|
return cls._get_meta_data("schema_out")
|
|
909
975
|
case "update":
|
|
910
976
|
return cls._get_meta_data("schema_update")
|
|
977
|
+
case "detail":
|
|
978
|
+
return cls._get_meta_data("schema_detail")
|
|
911
979
|
case _:
|
|
912
980
|
return None
|
|
913
981
|
|
|
@@ -1027,7 +1095,12 @@ class Serializer(BaseSerializer):
|
|
|
1027
1095
|
dict
|
|
1028
1096
|
Serialized data.
|
|
1029
1097
|
"""
|
|
1030
|
-
|
|
1098
|
+
schema = (
|
|
1099
|
+
self.generate_read_s()
|
|
1100
|
+
if self.generate_detail_s() is None
|
|
1101
|
+
else self.generate_detail_s()
|
|
1102
|
+
)
|
|
1103
|
+
return await self.util.read_s(schema=schema, instance=instance)
|
|
1031
1104
|
|
|
1032
1105
|
async def models_dump(
|
|
1033
1106
|
self, instances: models.QuerySet[models.Model]
|