django-ninja-aio-crud 2.11.1__tar.gz → 2.12.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.11.1 → django_ninja_aio_crud-2.12.0}/PKG-INFO +1 -1
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/models/model_serializer.md +35 -11
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/models/serializers.md +23 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/models/serializers.py +12 -9
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/models/utils.py +1 -1
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_app/models.py +36 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_serializers.py +182 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.github/workflows/docs.yml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/README.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/models/model_util.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/views/api_view.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/views/api_view_set.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/views/decorators.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/views/mixins.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/extra.css +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/index.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/main.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/mkdocs.yml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/helpers/api.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/helpers/query.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/schemas/filters.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/schemas/helpers.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/views/api.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/helpers/test_many_to_many_api.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_app/serializers.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_app/views.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/test_settings.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/views/test_views.py +0 -0
- {django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/views/test_viewset.py +0 -0
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/models/model_serializer.md
RENAMED
|
@@ -149,16 +149,23 @@ 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:**
|
|
152
|
+
**Fallback Behavior:** `DetailSerializer` supports **per-field-type fallback** to `ReadSerializer`. Each attribute (`fields`, `customs`, `optionals`, `excludes`) is checked independently:
|
|
153
|
+
|
|
154
|
+
- If `DetailSerializer.fields` is empty → uses `ReadSerializer.fields`
|
|
155
|
+
- If `DetailSerializer.customs` is empty → uses `ReadSerializer.customs`
|
|
156
|
+
- If `DetailSerializer.optionals` is empty → uses `ReadSerializer.optionals`
|
|
157
|
+
- If `DetailSerializer.excludes` is empty → uses `ReadSerializer.excludes`
|
|
158
|
+
|
|
159
|
+
This allows partial overrides: define only `DetailSerializer.fields` while inheriting `customs` from `ReadSerializer`.
|
|
153
160
|
|
|
154
161
|
**Attributes:**
|
|
155
162
|
|
|
156
163
|
| Attribute | Type | Description |
|
|
157
164
|
| ----------- | ------------------------ | ----------------------------------------------------------------------------- |
|
|
158
|
-
| `fields` | `list[str]` | Model fields to include in detail view (falls back to ReadSerializer
|
|
159
|
-
| `excludes` | `list[str]` | Fields to exclude from detail view
|
|
160
|
-
| `customs` | `list[tuple]` | Computed fields: `(name, type)` required; `(name, type, default)` optional
|
|
161
|
-
| `optionals` | `list[tuple[str, type]]` | Optional output fields
|
|
165
|
+
| `fields` | `list[str]` | Model fields to include in detail view (falls back to ReadSerializer.fields if empty) |
|
|
166
|
+
| `excludes` | `list[str]` | Fields to exclude from detail view (falls back to ReadSerializer.excludes if empty) |
|
|
167
|
+
| `customs` | `list[tuple]` | Computed fields: `(name, type)` required; `(name, type, default)` optional (falls back to ReadSerializer.customs if empty) |
|
|
168
|
+
| `optionals` | `list[tuple[str, type]]` | Optional output fields (falls back to ReadSerializer.optionals if empty) |
|
|
162
169
|
|
|
163
170
|
**Example:**
|
|
164
171
|
|
|
@@ -174,21 +181,22 @@ class Article(ModelSerializer):
|
|
|
174
181
|
class ReadSerializer:
|
|
175
182
|
# List view: minimal fields for performance
|
|
176
183
|
fields = ["id", "title", "summary", "author"]
|
|
184
|
+
customs = [
|
|
185
|
+
("word_count", int, lambda obj: len(obj.content.split())),
|
|
186
|
+
]
|
|
177
187
|
|
|
178
188
|
class DetailSerializer:
|
|
179
189
|
# Detail view: all fields including expensive relations
|
|
190
|
+
# customs inherited from ReadSerializer (word_count)
|
|
180
191
|
fields = ["id", "title", "summary", "content", "author", "tags", "view_count"]
|
|
181
|
-
customs = [
|
|
182
|
-
("reading_time", int, lambda obj: len(obj.content.split()) // 200),
|
|
183
|
-
]
|
|
184
192
|
```
|
|
185
193
|
|
|
186
194
|
**Generated Output (List):**
|
|
187
195
|
|
|
188
196
|
```json
|
|
189
197
|
[
|
|
190
|
-
{"id": 1, "title": "Getting Started", "summary": "...", "author": {...}},
|
|
191
|
-
{"id": 2, "title": "Advanced Topics", "summary": "...", "author": {...}}
|
|
198
|
+
{"id": 1, "title": "Getting Started", "summary": "...", "author": {...}, "word_count": 500},
|
|
199
|
+
{"id": 2, "title": "Advanced Topics", "summary": "...", "author": {...}, "word_count": 1200}
|
|
192
200
|
]
|
|
193
201
|
```
|
|
194
202
|
|
|
@@ -203,10 +211,26 @@ class Article(ModelSerializer):
|
|
|
203
211
|
"author": {...},
|
|
204
212
|
"tags": [{"id": 1, "name": "python"}, {"id": 2, "name": "django"}],
|
|
205
213
|
"view_count": 1234,
|
|
206
|
-
"
|
|
214
|
+
"word_count": 500
|
|
207
215
|
}
|
|
208
216
|
```
|
|
209
217
|
|
|
218
|
+
**Example with Custom Override:**
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
class Article(ModelSerializer):
|
|
222
|
+
# ... fields ...
|
|
223
|
+
|
|
224
|
+
class ReadSerializer:
|
|
225
|
+
fields = ["id", "title", "summary"]
|
|
226
|
+
customs = [("word_count", int, lambda obj: len(obj.content.split()))]
|
|
227
|
+
|
|
228
|
+
class DetailSerializer:
|
|
229
|
+
fields = ["id", "title", "summary", "content"]
|
|
230
|
+
# Override customs - reading_time instead of word_count
|
|
231
|
+
customs = [("reading_time", int, lambda obj: len(obj.content.split()) // 200)]
|
|
232
|
+
```
|
|
233
|
+
|
|
210
234
|
### UpdateSerializer
|
|
211
235
|
|
|
212
236
|
Describes how to build an update (partial/full) input schema.
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/models/serializers.md
RENAMED
|
@@ -87,6 +87,29 @@ When used with `APIViewSet`:
|
|
|
87
87
|
- **List endpoint** (`GET /articles/`) uses `schema_out`
|
|
88
88
|
- **Retrieve endpoint** (`GET /articles/{pk}`) uses `schema_detail` (falls back to `schema_out` if not defined)
|
|
89
89
|
|
|
90
|
+
**Fallback Behavior:** Unlike `ModelSerializer`, `Serializer` uses **schema-level fallback**:
|
|
91
|
+
|
|
92
|
+
- If `schema_detail` is **not defined** → all field types (`fields`, `customs`, `optionals`, `exclude`) fall back to `schema_out`
|
|
93
|
+
- If `schema_detail` **is defined** → no inheritance from `schema_out`, even for empty field types
|
|
94
|
+
|
|
95
|
+
This means you must explicitly define all needed configurations in `schema_detail` if you define it at all:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
class ArticleSerializer(serializers.Serializer):
|
|
99
|
+
class Meta:
|
|
100
|
+
model = models.Article
|
|
101
|
+
schema_out = serializers.SchemaModelConfig(
|
|
102
|
+
fields=["id", "title"],
|
|
103
|
+
customs=[("word_count", int, 0)], # This custom...
|
|
104
|
+
)
|
|
105
|
+
schema_detail = serializers.SchemaModelConfig(
|
|
106
|
+
fields=["id", "title", "content"],
|
|
107
|
+
# ...is NOT inherited here because schema_detail is defined
|
|
108
|
+
# You must explicitly add it if needed:
|
|
109
|
+
# customs=[("word_count", int, 0)],
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
90
113
|
## Example: simple FK
|
|
91
114
|
|
|
92
115
|
```python
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/models/serializers.py
RENAMED
|
@@ -333,11 +333,7 @@ class BaseSerializer:
|
|
|
333
333
|
@classmethod
|
|
334
334
|
def get_fields(cls, s_type: S_TYPES):
|
|
335
335
|
"""Return explicit declared fields for the serializer type."""
|
|
336
|
-
|
|
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
|
+
return cls._get_fields(s_type, "fields")
|
|
341
337
|
|
|
342
338
|
@classmethod
|
|
343
339
|
def is_custom(cls, field: str) -> bool:
|
|
@@ -428,8 +424,9 @@ class BaseSerializer:
|
|
|
428
424
|
@classmethod
|
|
429
425
|
def _warn_missing_relation_serializer(cls, field_name: str, model) -> None:
|
|
430
426
|
"""Emit warning for reverse relations without explicit serializer mapping."""
|
|
431
|
-
if not isinstance(model, ModelSerializerMeta) and
|
|
432
|
-
settings, "NINJA_AIO_TESTING", False
|
|
427
|
+
if not isinstance(model, ModelSerializerMeta) and (
|
|
428
|
+
not getattr(settings, "NINJA_AIO_TESTING", False)
|
|
429
|
+
or getattr(settings, "NINJA_AIO_RAISE_SERIALIZATION_WARNINGS", False)
|
|
433
430
|
):
|
|
434
431
|
warnings.warn(
|
|
435
432
|
f"{cls.__name__}: reverse relation '{field_name}' is listed in read fields "
|
|
@@ -781,7 +778,10 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
781
778
|
if not config_class_name:
|
|
782
779
|
return []
|
|
783
780
|
config_class = getattr(cls, config_class_name)
|
|
784
|
-
|
|
781
|
+
fields = getattr(config_class, f_type, [])
|
|
782
|
+
if not fields and s_type == "detail":
|
|
783
|
+
fields = getattr(cls.ReadSerializer, f_type, [])
|
|
784
|
+
return fields
|
|
785
785
|
|
|
786
786
|
@classmethod
|
|
787
787
|
def _get_model(cls) -> "ModelSerializer":
|
|
@@ -1006,7 +1006,10 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1006
1006
|
return []
|
|
1007
1007
|
schema = cls._get_schema_meta(schema_key)
|
|
1008
1008
|
if not schema:
|
|
1009
|
-
|
|
1009
|
+
if s_type == "detail":
|
|
1010
|
+
schema = cls._get_schema_meta("out")
|
|
1011
|
+
else:
|
|
1012
|
+
return []
|
|
1010
1013
|
return getattr(schema, f_type, []) or []
|
|
1011
1014
|
|
|
1012
1015
|
@classmethod
|
|
@@ -601,7 +601,7 @@ class ModelUtil:
|
|
|
601
601
|
async def _bump_object_from_schema(
|
|
602
602
|
self, obj: type["ModelSerializer"] | models.Model, schema: Schema
|
|
603
603
|
):
|
|
604
|
-
return (await sync_to_async(schema.from_orm)(obj)).model_dump(
|
|
604
|
+
return (await sync_to_async(schema.from_orm)(obj)).model_dump()
|
|
605
605
|
|
|
606
606
|
def _validate_read_params(self, request: HttpRequest, query_data: QuerySchema):
|
|
607
607
|
"""Validate required parameters for read operations."""
|
|
@@ -187,3 +187,39 @@ class TestModelSerializerWithDetail(BaseTestModelSerializer):
|
|
|
187
187
|
class DetailSerializer:
|
|
188
188
|
fields = ["id", "name", "description", "extra_info"]
|
|
189
189
|
customs = [("computed_field", str, "computed_value")]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
class TestModelSerializerWithReadCustoms(BaseTestModelSerializer):
|
|
193
|
+
"""Model with customs on ReadSerializer but no DetailSerializer."""
|
|
194
|
+
|
|
195
|
+
class ReadSerializer:
|
|
196
|
+
fields = ["id", "name"]
|
|
197
|
+
customs = [("custom_field", str, "default")]
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
class TestModelSerializerWithReadOptionals(BaseTestModelSerializer):
|
|
201
|
+
"""Model with optionals on ReadSerializer but no DetailSerializer."""
|
|
202
|
+
|
|
203
|
+
class ReadSerializer:
|
|
204
|
+
fields = ["id", "name"]
|
|
205
|
+
optionals = [("description", str)]
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class TestModelSerializerWithReadExcludes(BaseTestModelSerializer):
|
|
209
|
+
"""Model with excludes on ReadSerializer but no DetailSerializer."""
|
|
210
|
+
|
|
211
|
+
class ReadSerializer:
|
|
212
|
+
fields = ["id", "name", "description"]
|
|
213
|
+
excludes = ["description"]
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
class TestModelSerializerWithBothSerializers(BaseTestModelSerializer):
|
|
217
|
+
"""Model with both ReadSerializer and DetailSerializer configured."""
|
|
218
|
+
|
|
219
|
+
class ReadSerializer:
|
|
220
|
+
fields = ["id", "name"]
|
|
221
|
+
customs = [("read_custom", str, "default")]
|
|
222
|
+
|
|
223
|
+
class DetailSerializer:
|
|
224
|
+
fields = ["id", "name", "description"]
|
|
225
|
+
# No customs defined - should NOT inherit from read
|
|
@@ -298,6 +298,94 @@ class DetailSerializerTestCase(TestCase):
|
|
|
298
298
|
def setUp(self):
|
|
299
299
|
warnings.simplefilter("ignore", UserWarning)
|
|
300
300
|
|
|
301
|
+
def test_detail_fallback_customs_from_read(self):
|
|
302
|
+
"""Test that detail schema falls back to read customs when not configured."""
|
|
303
|
+
|
|
304
|
+
class DetailFallbackCustomsSerializer(serializers.Serializer):
|
|
305
|
+
class Meta:
|
|
306
|
+
model = TestModelForeignKey
|
|
307
|
+
schema_out = serializers.SchemaModelConfig(
|
|
308
|
+
fields=["id", "name"],
|
|
309
|
+
customs=[("read_custom", str, "default")],
|
|
310
|
+
)
|
|
311
|
+
# No schema_detail defined - should fall back to schema_out
|
|
312
|
+
|
|
313
|
+
# Detail should inherit customs from read schema
|
|
314
|
+
schema_detail = DetailFallbackCustomsSerializer.generate_detail_s()
|
|
315
|
+
self.assertIsNotNone(schema_detail)
|
|
316
|
+
self.assertIn("id", schema_detail.model_fields)
|
|
317
|
+
self.assertIn("name", schema_detail.model_fields)
|
|
318
|
+
self.assertIn("read_custom", schema_detail.model_fields)
|
|
319
|
+
|
|
320
|
+
def test_detail_fallback_optionals_from_read(self):
|
|
321
|
+
"""Test that detail schema falls back to read optionals when not configured."""
|
|
322
|
+
|
|
323
|
+
class DetailFallbackOptionalsSerializer(serializers.Serializer):
|
|
324
|
+
class Meta:
|
|
325
|
+
model = TestModelForeignKey
|
|
326
|
+
schema_out = serializers.SchemaModelConfig(
|
|
327
|
+
fields=["id", "name"],
|
|
328
|
+
optionals=[("description", str)],
|
|
329
|
+
)
|
|
330
|
+
# No schema_detail defined - should fall back to schema_out
|
|
331
|
+
|
|
332
|
+
# Detail should inherit optionals from read schema
|
|
333
|
+
schema_detail = DetailFallbackOptionalsSerializer.generate_detail_s()
|
|
334
|
+
self.assertIsNotNone(schema_detail)
|
|
335
|
+
self.assertIn("id", schema_detail.model_fields)
|
|
336
|
+
self.assertIn("name", schema_detail.model_fields)
|
|
337
|
+
self.assertIn("description", schema_detail.model_fields)
|
|
338
|
+
|
|
339
|
+
def test_detail_fallback_excludes_from_read(self):
|
|
340
|
+
"""Test that detail schema falls back to read excludes when not configured."""
|
|
341
|
+
|
|
342
|
+
class DetailFallbackExcludesSerializer(serializers.Serializer):
|
|
343
|
+
class Meta:
|
|
344
|
+
model = TestModelForeignKey
|
|
345
|
+
schema_out = serializers.SchemaModelConfig(
|
|
346
|
+
fields=["id", "name"],
|
|
347
|
+
exclude=["test_model"], # Exclude forward relation
|
|
348
|
+
)
|
|
349
|
+
# No schema_detail defined - should fall back to schema_out
|
|
350
|
+
|
|
351
|
+
# Detail should inherit excludes from read schema
|
|
352
|
+
schema_detail = DetailFallbackExcludesSerializer.generate_detail_s()
|
|
353
|
+
read_schema = DetailFallbackExcludesSerializer.generate_read_s()
|
|
354
|
+
self.assertIsNotNone(schema_detail)
|
|
355
|
+
# Both should have the same excludes applied
|
|
356
|
+
self.assertEqual(
|
|
357
|
+
set(schema_detail.model_fields.keys()),
|
|
358
|
+
set(read_schema.model_fields.keys()),
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
def test_detail_does_not_inherit_when_defined(self):
|
|
362
|
+
"""Test that detail does NOT inherit from read when schema_detail is defined."""
|
|
363
|
+
|
|
364
|
+
class DetailDoesNotInheritSerializer(serializers.Serializer):
|
|
365
|
+
class Meta:
|
|
366
|
+
model = TestModelForeignKey
|
|
367
|
+
schema_out = serializers.SchemaModelConfig(
|
|
368
|
+
fields=["id", "name"],
|
|
369
|
+
customs=[("read_custom", str, "default")],
|
|
370
|
+
)
|
|
371
|
+
schema_detail = serializers.SchemaModelConfig(
|
|
372
|
+
fields=["id", "name", "description"],
|
|
373
|
+
# No customs defined - does NOT inherit from read
|
|
374
|
+
# because schema_detail is defined (fallback is at schema level)
|
|
375
|
+
)
|
|
376
|
+
|
|
377
|
+
# Read schema should have the custom
|
|
378
|
+
schema_read = DetailDoesNotInheritSerializer.generate_read_s()
|
|
379
|
+
self.assertIn("read_custom", schema_read.model_fields)
|
|
380
|
+
|
|
381
|
+
# Detail schema does NOT inherit customs from read because schema_detail is defined
|
|
382
|
+
# (Serializer fallback is at the schema level, not per-field-type)
|
|
383
|
+
schema_detail = DetailDoesNotInheritSerializer.generate_detail_s()
|
|
384
|
+
self.assertIn("id", schema_detail.model_fields)
|
|
385
|
+
self.assertIn("name", schema_detail.model_fields)
|
|
386
|
+
self.assertIn("description", schema_detail.model_fields)
|
|
387
|
+
self.assertNotIn("read_custom", schema_detail.model_fields)
|
|
388
|
+
|
|
301
389
|
def test_generate_detail_schema_with_serializer(self):
|
|
302
390
|
"""Test generate_detail_s() with Serializer class."""
|
|
303
391
|
|
|
@@ -416,3 +504,97 @@ class DetailSerializerTestCase(TestCase):
|
|
|
416
504
|
self.assertIn("id", schema_detail.model_fields)
|
|
417
505
|
self.assertIn("name", schema_detail.model_fields)
|
|
418
506
|
self.assertIn("description", schema_detail.model_fields)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
@tag("serializers", "detail", "model_serializer")
|
|
510
|
+
class ModelSerializerDetailFallbackTestCase(TestCase):
|
|
511
|
+
"""Test cases for ModelSerializer detail->read fallback behavior."""
|
|
512
|
+
|
|
513
|
+
def setUp(self):
|
|
514
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
515
|
+
|
|
516
|
+
def test_model_serializer_detail_fallback_fields(self):
|
|
517
|
+
"""Test ModelSerializer detail falls back to read fields when not configured."""
|
|
518
|
+
from tests.test_app.models import TestModelSerializer
|
|
519
|
+
|
|
520
|
+
# TestModelSerializer has ReadSerializer with fields but no DetailSerializer
|
|
521
|
+
read_fields = TestModelSerializer.get_fields("read")
|
|
522
|
+
detail_fields = TestModelSerializer.get_fields("detail")
|
|
523
|
+
|
|
524
|
+
# Detail should fall back to read fields
|
|
525
|
+
self.assertEqual(read_fields, detail_fields)
|
|
526
|
+
|
|
527
|
+
def test_model_serializer_detail_fallback_customs(self):
|
|
528
|
+
"""Test ModelSerializer detail falls back to read customs when not configured."""
|
|
529
|
+
from tests.test_app.models import TestModelSerializerWithReadCustoms
|
|
530
|
+
|
|
531
|
+
read_customs = TestModelSerializerWithReadCustoms.get_custom_fields("read")
|
|
532
|
+
detail_customs = TestModelSerializerWithReadCustoms.get_custom_fields("detail")
|
|
533
|
+
|
|
534
|
+
# Detail should fall back to read customs
|
|
535
|
+
self.assertEqual(read_customs, detail_customs)
|
|
536
|
+
self.assertEqual(len(detail_customs), 1)
|
|
537
|
+
self.assertEqual(detail_customs[0][0], "custom_field")
|
|
538
|
+
|
|
539
|
+
def test_model_serializer_detail_fallback_optionals(self):
|
|
540
|
+
"""Test ModelSerializer detail falls back to read optionals when not configured."""
|
|
541
|
+
from tests.test_app.models import TestModelSerializerWithReadOptionals
|
|
542
|
+
|
|
543
|
+
read_optionals = TestModelSerializerWithReadOptionals.get_optional_fields("read")
|
|
544
|
+
detail_optionals = TestModelSerializerWithReadOptionals.get_optional_fields("detail")
|
|
545
|
+
|
|
546
|
+
# Detail should fall back to read optionals
|
|
547
|
+
self.assertEqual(read_optionals, detail_optionals)
|
|
548
|
+
self.assertEqual(len(detail_optionals), 1)
|
|
549
|
+
self.assertEqual(detail_optionals[0][0], "description")
|
|
550
|
+
|
|
551
|
+
def test_model_serializer_detail_fallback_excludes(self):
|
|
552
|
+
"""Test ModelSerializer detail falls back to read excludes when not configured."""
|
|
553
|
+
from tests.test_app.models import TestModelSerializerWithReadExcludes
|
|
554
|
+
|
|
555
|
+
read_excludes = TestModelSerializerWithReadExcludes.get_excluded_fields("read")
|
|
556
|
+
detail_excludes = TestModelSerializerWithReadExcludes.get_excluded_fields("detail")
|
|
557
|
+
|
|
558
|
+
# Detail should fall back to read excludes
|
|
559
|
+
self.assertEqual(read_excludes, detail_excludes)
|
|
560
|
+
self.assertIn("description", detail_excludes)
|
|
561
|
+
|
|
562
|
+
def test_model_serializer_detail_inherits_per_field_type(self):
|
|
563
|
+
"""Test ModelSerializer detail inherits from read per-field-type when empty."""
|
|
564
|
+
from tests.test_app.models import TestModelSerializerWithBothSerializers
|
|
565
|
+
|
|
566
|
+
read_customs = TestModelSerializerWithBothSerializers.get_custom_fields("read")
|
|
567
|
+
detail_customs = TestModelSerializerWithBothSerializers.get_custom_fields("detail")
|
|
568
|
+
|
|
569
|
+
# Read has customs defined
|
|
570
|
+
self.assertEqual(len(read_customs), 1)
|
|
571
|
+
# Detail inherits customs from read because DetailSerializer.customs is empty
|
|
572
|
+
# (fallback is per-field-type, not per-serializer)
|
|
573
|
+
self.assertEqual(len(detail_customs), 1)
|
|
574
|
+
self.assertEqual(detail_customs[0][0], "read_custom")
|
|
575
|
+
|
|
576
|
+
# But fields are different (DetailSerializer.fields overrides)
|
|
577
|
+
read_fields = TestModelSerializerWithBothSerializers.get_fields("read")
|
|
578
|
+
detail_fields = TestModelSerializerWithBothSerializers.get_fields("detail")
|
|
579
|
+
self.assertEqual(read_fields, ["id", "name"])
|
|
580
|
+
self.assertEqual(detail_fields, ["id", "name", "description"])
|
|
581
|
+
|
|
582
|
+
def test_model_serializer_with_detail_generates_different_schemas(self):
|
|
583
|
+
"""Test that ModelSerializer with DetailSerializer generates distinct schemas."""
|
|
584
|
+
from tests.test_app.models import TestModelSerializerWithDetail
|
|
585
|
+
|
|
586
|
+
read_schema = TestModelSerializerWithDetail.generate_read_s()
|
|
587
|
+
detail_schema = TestModelSerializerWithDetail.generate_detail_s()
|
|
588
|
+
|
|
589
|
+
# Read schema should have fewer fields
|
|
590
|
+
self.assertIn("id", read_schema.model_fields)
|
|
591
|
+
self.assertIn("name", read_schema.model_fields)
|
|
592
|
+
self.assertNotIn("description", read_schema.model_fields)
|
|
593
|
+
self.assertNotIn("extra_info", read_schema.model_fields)
|
|
594
|
+
|
|
595
|
+
# Detail schema should have more fields plus custom
|
|
596
|
+
self.assertIn("id", detail_schema.model_fields)
|
|
597
|
+
self.assertIn("name", detail_schema.model_fields)
|
|
598
|
+
self.assertIn("description", detail_schema.model_fields)
|
|
599
|
+
self.assertIn("extra_info", detail_schema.model_fields)
|
|
600
|
+
self.assertIn("computed_field", detail_schema.model_fields)
|
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/.github/workflows/coverage.yml
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
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/renderers/orjson_renderer.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/api/views/api_view_set.md
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
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/installation.md
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/getting_started/quick_start.md
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
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/docs/tutorial/authentication.md
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
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/decorators/__init__.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/decorators/operations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/ninja_aio/factory/operations.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
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/core/test_exceptions_api.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/core/test_renderer_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/helpers/test_many_to_many_api.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/models/test_model_util.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.11.1 → django_ninja_aio_crud-2.12.0}/tests/models/test_models_extra.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
|