django-ninja-aio-crud 2.3.2__tar.gz → 2.5.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.3.2 → django_ninja_aio_crud-2.5.0}/.github/workflows/docs.yml +2 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/PKG-INFO +31 -6
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/README.md +30 -5
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/models/model_util.md +77 -9
- django_ninja_aio_crud-2.5.0/docs/api/models/serializers.md +349 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/views/api_view_set.md +86 -19
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/index.md +58 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/mkdocs.yml +1 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/helpers/query.py +2 -2
- django_ninja_aio_crud-2.5.0/ninja_aio/models/__init__.py +4 -0
- django_ninja_aio_crud-2.5.0/ninja_aio/models/serializers.py +888 -0
- django_ninja_aio_crud-2.3.2/ninja_aio/models.py → django_ninja_aio_crud-2.5.0/ninja_aio/models/utils.py +75 -670
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/views/api.py +44 -19
- django_ninja_aio_crud-2.5.0/tests/test_app/serializers.py +31 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_app/views.py +16 -2
- django_ninja_aio_crud-2.5.0/tests/test_serializers.py +106 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_settings.py +2 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/views/test_viewset.py +36 -2
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/models/model_serializer.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/views/api_view.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/views/decorators.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/api/views/mixins.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/extra.css +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/main.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/helpers/api.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/helpers.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/helpers/test_many_to_many_api.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_app/models.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.3.2 → django_ninja_aio_crud-2.5.0}/tests/views/test_views.py +0 -0
|
@@ -15,6 +15,7 @@ on:
|
|
|
15
15
|
- "2.0"
|
|
16
16
|
- "2.2"
|
|
17
17
|
- "2.3"
|
|
18
|
+
- "2.4"
|
|
18
19
|
make_latest:
|
|
19
20
|
description: 'Set as "latest" and default?'
|
|
20
21
|
type: boolean
|
|
@@ -33,6 +34,7 @@ on:
|
|
|
33
34
|
- "2.0"
|
|
34
35
|
- "2.2"
|
|
35
36
|
- "2.3"
|
|
37
|
+
- "2.4"
|
|
36
38
|
delete_confirm:
|
|
37
39
|
description: 'Confirm deletion of the selected version'
|
|
38
40
|
type: boolean
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: django-ninja-aio-crud
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.5.0
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
6
|
Requires-Python: >=3.10, <3.15
|
|
@@ -48,6 +48,7 @@ Provides-Extra: test
|
|
|
48
48
|
|
|
49
49
|
## ✨ Features
|
|
50
50
|
|
|
51
|
+
- Serializer (Meta-driven) first-class: dynamic schemas for existing Django models without inheriting ModelSerializer
|
|
51
52
|
- Async CRUD ViewSets (create, list, retrieve, update, delete)
|
|
52
53
|
- Automatic Pydantic schemas from `ModelSerializer` (read/create/update)
|
|
53
54
|
- Dynamic query params (runtime schema via `pydantic.create_model`)
|
|
@@ -61,17 +62,35 @@ Provides-Extra: test
|
|
|
61
62
|
|
|
62
63
|
---
|
|
63
64
|
|
|
64
|
-
##
|
|
65
|
+
## 🚀 Quick Start (Serializer)
|
|
65
66
|
|
|
66
|
-
|
|
67
|
-
|
|
67
|
+
If you already have Django models, start with the Meta-driven Serializer for instant CRUD without changing model base classes.
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from ninja_aio.models import serializers
|
|
71
|
+
from ninja_aio.views import APIViewSet
|
|
72
|
+
from ninja_aio import NinjaAIO
|
|
73
|
+
from . import models
|
|
74
|
+
|
|
75
|
+
class BookSerializer(serializers.Serializer):
|
|
76
|
+
class Meta:
|
|
77
|
+
model = models.Book
|
|
78
|
+
schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
|
|
79
|
+
schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
|
|
80
|
+
schema_update = serializers.SchemaModelConfig(optionals=[("title", str), ("published", bool)])
|
|
81
|
+
|
|
82
|
+
api = NinjaAIO()
|
|
83
|
+
|
|
84
|
+
@api.viewset(models.Book)
|
|
85
|
+
class BookViewSet(APIViewSet):
|
|
86
|
+
serializer_class = BookSerializer
|
|
68
87
|
```
|
|
69
88
|
|
|
70
|
-
|
|
89
|
+
Visit `/docs` → CRUD endpoints ready.
|
|
71
90
|
|
|
72
91
|
---
|
|
73
92
|
|
|
74
|
-
## 🚀 Quick Start
|
|
93
|
+
## 🚀 Quick Start (ModelSerializer)
|
|
75
94
|
|
|
76
95
|
models.py
|
|
77
96
|
|
|
@@ -262,6 +281,12 @@ class BookViewSet(APIViewSet):
|
|
|
262
281
|
|
|
263
282
|
---
|
|
264
283
|
|
|
284
|
+
## Meta-driven Serializer (for vanilla Django models)
|
|
285
|
+
|
|
286
|
+
Moved above as the primary quick start.
|
|
287
|
+
|
|
288
|
+
---
|
|
289
|
+
|
|
265
290
|
## 🛠 Project Structure & Docs
|
|
266
291
|
|
|
267
292
|
Documentation (MkDocs + Material):
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
## ✨ Features
|
|
15
15
|
|
|
16
|
+
- Serializer (Meta-driven) first-class: dynamic schemas for existing Django models without inheriting ModelSerializer
|
|
16
17
|
- Async CRUD ViewSets (create, list, retrieve, update, delete)
|
|
17
18
|
- Automatic Pydantic schemas from `ModelSerializer` (read/create/update)
|
|
18
19
|
- Dynamic query params (runtime schema via `pydantic.create_model`)
|
|
@@ -26,17 +27,35 @@
|
|
|
26
27
|
|
|
27
28
|
---
|
|
28
29
|
|
|
29
|
-
##
|
|
30
|
+
## 🚀 Quick Start (Serializer)
|
|
30
31
|
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
If you already have Django models, start with the Meta-driven Serializer for instant CRUD without changing model base classes.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
from ninja_aio.models import serializers
|
|
36
|
+
from ninja_aio.views import APIViewSet
|
|
37
|
+
from ninja_aio import NinjaAIO
|
|
38
|
+
from . import models
|
|
39
|
+
|
|
40
|
+
class BookSerializer(serializers.Serializer):
|
|
41
|
+
class Meta:
|
|
42
|
+
model = models.Book
|
|
43
|
+
schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
|
|
44
|
+
schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
|
|
45
|
+
schema_update = serializers.SchemaModelConfig(optionals=[("title", str), ("published", bool)])
|
|
46
|
+
|
|
47
|
+
api = NinjaAIO()
|
|
48
|
+
|
|
49
|
+
@api.viewset(models.Book)
|
|
50
|
+
class BookViewSet(APIViewSet):
|
|
51
|
+
serializer_class = BookSerializer
|
|
33
52
|
```
|
|
34
53
|
|
|
35
|
-
|
|
54
|
+
Visit `/docs` → CRUD endpoints ready.
|
|
36
55
|
|
|
37
56
|
---
|
|
38
57
|
|
|
39
|
-
## 🚀 Quick Start
|
|
58
|
+
## 🚀 Quick Start (ModelSerializer)
|
|
40
59
|
|
|
41
60
|
models.py
|
|
42
61
|
|
|
@@ -227,6 +246,12 @@ class BookViewSet(APIViewSet):
|
|
|
227
246
|
|
|
228
247
|
---
|
|
229
248
|
|
|
249
|
+
## Meta-driven Serializer (for vanilla Django models)
|
|
250
|
+
|
|
251
|
+
Moved above as the primary quick start.
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
230
255
|
## 🛠 Project Structure & Docs
|
|
231
256
|
|
|
232
257
|
Documentation (MkDocs + Material):
|
|
@@ -17,15 +17,36 @@ ModelUtil acts as a bridge between Django Ninja schemas and Django ORM, handling
|
|
|
17
17
|
```python
|
|
18
18
|
from ninja_aio.models import ModelUtil
|
|
19
19
|
|
|
20
|
-
util = ModelUtil(model)
|
|
20
|
+
util = ModelUtil(model, serializer_class=None)
|
|
21
21
|
```
|
|
22
22
|
|
|
23
23
|
**Parameters:**
|
|
24
24
|
|
|
25
25
|
- `model` (`type[ModelSerializer] | models.Model`): Django model or ModelSerializer subclass
|
|
26
|
+
- `serializer_class` (`Serializer | None`): Optional Serializer class for plain Django models
|
|
26
27
|
|
|
27
28
|
## Properties
|
|
28
29
|
|
|
30
|
+
### `with_serializer`
|
|
31
|
+
|
|
32
|
+
Indicates if a serializer_class is associated.
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
util = ModelUtil(User, serializer_class=UserSerializer)
|
|
36
|
+
print(util.with_serializer) # True
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### `pk_field_type`
|
|
40
|
+
|
|
41
|
+
Returns the Python type corresponding to the model's primary key field.
|
|
42
|
+
|
|
43
|
+
```python
|
|
44
|
+
util = ModelUtil(User)
|
|
45
|
+
print(util.pk_field_type) # <class 'int'>
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
Uses the Django field's internal type and `ninja.orm.fields.TYPES` mapping. Raises `ConfigError` if the internal type is not registered.
|
|
49
|
+
|
|
29
50
|
### `model_pk_name`
|
|
30
51
|
|
|
31
52
|
Returns the primary key field name.
|
|
@@ -45,7 +66,7 @@ print(util.model_fields)
|
|
|
45
66
|
# ["id", "username", "email", "created_at", "is_active"]
|
|
46
67
|
```
|
|
47
68
|
|
|
48
|
-
### `
|
|
69
|
+
### `serializable_fields`
|
|
49
70
|
|
|
50
71
|
Returns serializable fields (ReadSerializer fields or all model fields).
|
|
51
72
|
|
|
@@ -59,10 +80,19 @@ class User(ModelSerializer):
|
|
|
59
80
|
fields = ["id", "username", "email"]
|
|
60
81
|
|
|
61
82
|
util = ModelUtil(User)
|
|
62
|
-
print(util.
|
|
83
|
+
print(util.serializable_fields)
|
|
63
84
|
# ["id", "username", "email"] (password excluded)
|
|
64
85
|
```
|
|
65
86
|
|
|
87
|
+
### `model_name`
|
|
88
|
+
|
|
89
|
+
Returns the Django internal model name.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
util = ModelUtil(User)
|
|
93
|
+
print(util.model_name) # "user"
|
|
94
|
+
```
|
|
95
|
+
|
|
66
96
|
### `serializer_meta`
|
|
67
97
|
|
|
68
98
|
Returns the ModelSerializerMeta instance if model uses ModelSerializer.
|
|
@@ -147,10 +177,20 @@ qs = await ModelUtil(Book).get_objects(
|
|
|
147
177
|
select_related=["author"],
|
|
148
178
|
prefetch_related=["tags"],
|
|
149
179
|
),
|
|
180
|
+
with_qs_request=True, # Apply queryset_request hook
|
|
150
181
|
is_for_read=True, # union with auto-discovered relations
|
|
151
182
|
)
|
|
152
183
|
```
|
|
153
184
|
|
|
185
|
+
**Parameters:**
|
|
186
|
+
|
|
187
|
+
- `request` (`HttpRequest`): Current HTTP request
|
|
188
|
+
- `query_data` (`ObjectsQuerySchema | None`): Query configuration (filters, select_related, prefetch_related)
|
|
189
|
+
- `with_qs_request` (`bool`): Apply queryset_request hook if available (default: True)
|
|
190
|
+
- `is_for_read` (`bool`): Merge with read-specific optimizations (default: False)
|
|
191
|
+
|
|
192
|
+
**Returns:** Optimized `QuerySet`
|
|
193
|
+
|
|
154
194
|
### `get_object`
|
|
155
195
|
|
|
156
196
|
Fetch a single object by pk or getters with optimizations:
|
|
@@ -163,6 +203,7 @@ obj = await ModelUtil(Book).get_object(
|
|
|
163
203
|
request,
|
|
164
204
|
pk=42,
|
|
165
205
|
query_data=ObjectQuerySchema(select_related=["author"]),
|
|
206
|
+
with_qs_request=True,
|
|
166
207
|
is_for_read=True,
|
|
167
208
|
)
|
|
168
209
|
|
|
@@ -173,10 +214,20 @@ obj = await ModelUtil(Book).get_object(
|
|
|
173
214
|
)
|
|
174
215
|
```
|
|
175
216
|
|
|
176
|
-
|
|
217
|
+
**Parameters:**
|
|
177
218
|
|
|
178
|
-
-
|
|
179
|
-
-
|
|
219
|
+
- `request` (`HttpRequest`): Current HTTP request
|
|
220
|
+
- `pk` (`int | str | None`): Primary key value (optional if getters provided)
|
|
221
|
+
- `query_data` (`ObjectQuerySchema | QuerySchema | None`): Query configuration
|
|
222
|
+
- `with_qs_request` (`bool`): Apply queryset_request hook if available (default: True)
|
|
223
|
+
- `is_for_read` (`bool`): Merge with read-specific optimizations (default: False)
|
|
224
|
+
|
|
225
|
+
**Returns:** Model instance
|
|
226
|
+
|
|
227
|
+
**Errors:**
|
|
228
|
+
|
|
229
|
+
- `ValueError` if neither pk nor getters provided
|
|
230
|
+
- `NotFoundError` if no match found
|
|
180
231
|
|
|
181
232
|
### `read_s` and `list_read_s`
|
|
182
233
|
|
|
@@ -208,10 +259,27 @@ items = await ModelUtil(Book).list_read_s(
|
|
|
208
259
|
)
|
|
209
260
|
```
|
|
210
261
|
|
|
211
|
-
|
|
262
|
+
**Parameters (read_s):**
|
|
263
|
+
|
|
264
|
+
- `schema` (`Schema`): Output schema for serialization
|
|
265
|
+
- `request` (`HttpRequest`): Current HTTP request
|
|
266
|
+
- `instance` (`Model | None`): Model instance to serialize (optional)
|
|
267
|
+
- `query_data` (`ObjectQuerySchema | QuerySchema | None`): Query configuration for fetching (optional)
|
|
268
|
+
- `is_for_read` (`bool`): Merge with read-specific optimizations (default: False)
|
|
269
|
+
|
|
270
|
+
**Parameters (list_read_s):**
|
|
271
|
+
|
|
272
|
+
- `schema` (`Schema`): Output schema for serialization
|
|
273
|
+
- `request` (`HttpRequest`): Current HTTP request
|
|
274
|
+
- `instances` (`QuerySet | list[Model] | None`): Instances to serialize (optional)
|
|
275
|
+
- `query_data` (`ObjectsQuerySchema | None`): Query configuration for fetching (optional)
|
|
276
|
+
- `is_for_read` (`bool`): Merge with read-specific optimizations (default: False)
|
|
277
|
+
|
|
278
|
+
**Behavior:**
|
|
212
279
|
|
|
213
|
-
- When is_for_read=True
|
|
214
|
-
- Passing instances skips fetching; passing query_data fetches automatically
|
|
280
|
+
- When `is_for_read=True`, select_related and prefetch_related are merged with model-discovered relations
|
|
281
|
+
- Passing `instance`/`instances` skips fetching; passing `query_data` fetches automatically
|
|
282
|
+
- Either `instance`/`instances` OR `query_data` must be provided, not both
|
|
215
283
|
|
|
216
284
|
### `get_reverse_relations()`
|
|
217
285
|
|
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Serializer (Meta-driven)
|
|
2
|
+
|
|
3
|
+
The `Serializer` class provides dynamic schema generation and relation handling for existing Django models without requiring you to adopt the ModelSerializer base class. Use it when:
|
|
4
|
+
|
|
5
|
+
- You already have vanilla Django models in a project and want dynamic Ninja schemas.
|
|
6
|
+
- You prefer to keep models unchanged and define serialization externally.
|
|
7
|
+
- You need to keep your models lean and define API concerns separately.
|
|
8
|
+
|
|
9
|
+
It mirrors the behavior of ModelSerializer but reads configuration from a nested Meta class.
|
|
10
|
+
|
|
11
|
+
## Key Differences from ModelSerializer
|
|
12
|
+
|
|
13
|
+
While both `ModelSerializer` and `Serializer` provide schema generation and CRUD operations, there are important differences:
|
|
14
|
+
|
|
15
|
+
| Feature | ModelSerializer | Serializer |
|
|
16
|
+
| ------------------------ | ----------------------------------- | ---------------------------------- |
|
|
17
|
+
| Model class | Custom base class | Plain Django model |
|
|
18
|
+
| Configuration | Nested classes (CreateSerializer) | Meta class (schema_in/out/update) |
|
|
19
|
+
| Lifecycle hooks | Instance methods (uses `self`) | Receives `instance` parameter |
|
|
20
|
+
| Auto-binding | Automatic via metaclass | Manual via `__init_subclass__` |
|
|
21
|
+
| Usage | Inherit from ModelSerializer | Separate serializer class |
|
|
22
|
+
| Query optimization | QuerySet nested class | QuerySet nested class (inherited) |
|
|
23
|
+
| Relation serializers | Auto-resolved | Explicit via relations_serializers |
|
|
24
|
+
|
|
25
|
+
## Key points
|
|
26
|
+
|
|
27
|
+
- Works with any Django model (no inheritance required).
|
|
28
|
+
- Generates read/create/update/related schemas on demand via ninja.orm.create_schema.
|
|
29
|
+
- Supports explicit relation serializers for forward and reverse relations.
|
|
30
|
+
- Plays nicely with APIViewSet to auto-wire schemas and queryset handling.
|
|
31
|
+
|
|
32
|
+
## Configuration
|
|
33
|
+
|
|
34
|
+
Define a Serializer subclass with a nested Meta:
|
|
35
|
+
|
|
36
|
+
- model: Django model class.
|
|
37
|
+
- schema_in: SchemaModelConfig for create inputs.
|
|
38
|
+
- schema_out: SchemaModelConfig for read outputs.
|
|
39
|
+
- schema_update: SchemaModelConfig for patch/update inputs.
|
|
40
|
+
- relations_serializers: mapping of relation field name -> Serializer to include nested schemas for relations.
|
|
41
|
+
|
|
42
|
+
SchemaModelConfig fields:
|
|
43
|
+
|
|
44
|
+
- fields: list[str]
|
|
45
|
+
- optionals: list[tuple[str, type]]
|
|
46
|
+
- exclude: list[str]
|
|
47
|
+
- customs: list[tuple[str, type, Any]]
|
|
48
|
+
|
|
49
|
+
## Example: simple FK
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from ninja_aio.models import serializers
|
|
53
|
+
from . import models
|
|
54
|
+
|
|
55
|
+
class ArticleSerializer(serializers.Serializer):
|
|
56
|
+
class Meta:
|
|
57
|
+
model = models.Article
|
|
58
|
+
schema_in = serializers.SchemaModelConfig(
|
|
59
|
+
fields=["title", "content", "author"]
|
|
60
|
+
)
|
|
61
|
+
schema_out = serializers.SchemaModelConfig(
|
|
62
|
+
fields=["id", "title", "content", "author"]
|
|
63
|
+
)
|
|
64
|
+
schema_update = serializers.SchemaModelConfig(
|
|
65
|
+
optionals=[("title", str), ("content", str)]
|
|
66
|
+
)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Schemas are auto-generated in `__init_subclass__` and available as class attributes:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
# Schemas are automatically generated
|
|
73
|
+
ArticleSerializer.generate_read_s() # Returns read schema
|
|
74
|
+
ArticleSerializer.generate_create_s() # Returns create schema
|
|
75
|
+
ArticleSerializer.generate_update_s() # Returns update schema
|
|
76
|
+
ArticleSerializer.generate_related_s() # Returns related schema
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Lifecycle Hooks
|
|
80
|
+
|
|
81
|
+
Serializer supports lifecycle hooks similar to ModelSerializer, but with a key difference: **all hooks receive an `instance` parameter** instead of using `self`:
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
class ArticleSerializer(serializers.Serializer):
|
|
85
|
+
class Meta:
|
|
86
|
+
model = models.Article
|
|
87
|
+
schema_in = serializers.SchemaModelConfig(
|
|
88
|
+
fields=["title", "content", "author"],
|
|
89
|
+
customs=[("notify_author", bool, True)]
|
|
90
|
+
)
|
|
91
|
+
schema_out = serializers.SchemaModelConfig(
|
|
92
|
+
fields=["id", "title", "content", "author"]
|
|
93
|
+
)
|
|
94
|
+
|
|
95
|
+
@classmethod
|
|
96
|
+
async def queryset_request(cls, request):
|
|
97
|
+
"""Filter and optimize queryset per request."""
|
|
98
|
+
return cls._meta.model.objects.select_related("author")
|
|
99
|
+
|
|
100
|
+
async def custom_actions(self, payload, instance):
|
|
101
|
+
"""Execute custom actions with access to the instance."""
|
|
102
|
+
if payload.get("notify_author"):
|
|
103
|
+
await send_email(instance.author.email, f"Article created: {instance.title}")
|
|
104
|
+
|
|
105
|
+
async def post_create(self, instance):
|
|
106
|
+
"""Hook after instance creation."""
|
|
107
|
+
await AuditLog.objects.acreate(
|
|
108
|
+
action="article_created",
|
|
109
|
+
article_id=instance.id
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def before_save(self, instance):
|
|
113
|
+
"""Sync hook before save (receives instance)."""
|
|
114
|
+
instance.slug = slugify(instance.title)
|
|
115
|
+
|
|
116
|
+
def after_save(self, instance):
|
|
117
|
+
"""Sync hook after save (receives instance)."""
|
|
118
|
+
cache.delete(f"article:{instance.id}")
|
|
119
|
+
|
|
120
|
+
def on_delete(self, instance):
|
|
121
|
+
"""Sync hook after deletion (receives instance)."""
|
|
122
|
+
logger.info(f"Article {instance.id} deleted")
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Available Hooks
|
|
126
|
+
|
|
127
|
+
| Hook | Type | When Called | Parameters |
|
|
128
|
+
| --------------------------- | ----- | ------------------------- | ----------------------- |
|
|
129
|
+
| `queryset_request(request)` | async | Before queryset building | `request` |
|
|
130
|
+
| `custom_actions(payload, i)`| async | After field assignment | `payload`, `instance` |
|
|
131
|
+
| `post_create(instance)` | async | After first save | `instance` |
|
|
132
|
+
| `before_save(instance)` | sync | Before any save | `instance` |
|
|
133
|
+
| `after_save(instance)` | sync | After any save | `instance` |
|
|
134
|
+
| `on_create_before_save(i)` | sync | Before creation save only | `instance` |
|
|
135
|
+
| `on_create_after_save(i)` | sync | After creation save only | `instance` |
|
|
136
|
+
| `on_delete(instance)` | sync | After deletion | `instance` |
|
|
137
|
+
|
|
138
|
+
## Example: reverse relation with nested serialization
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
class AuthorSerializer(serializers.Serializer):
|
|
142
|
+
class Meta:
|
|
143
|
+
model = models.Author
|
|
144
|
+
schema_out = serializers.SchemaModelConfig(
|
|
145
|
+
fields=["id", "name", "articles"] # reverse related name
|
|
146
|
+
)
|
|
147
|
+
relations_serializers = {
|
|
148
|
+
"articles": ArticleSerializer, # include nested article schema
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Notes:
|
|
153
|
+
|
|
154
|
+
- Forward relations are included as plain fields unless a related ModelSerializer/Serializer is declared.
|
|
155
|
+
- Reverse relations require an entry in relations_serializers when using vanilla Django models.
|
|
156
|
+
- When the related model is a ModelSerializer, related schemas can be auto-resolved.
|
|
157
|
+
|
|
158
|
+
## Using with APIViewSet
|
|
159
|
+
|
|
160
|
+
You can attach a Serializer to an APIViewSet to auto-generate schemas and leverage queryset_request when present:
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from ninja_aio.views import APIViewSet
|
|
164
|
+
from ninja_aio import NinjaAIO
|
|
165
|
+
from . import models
|
|
166
|
+
|
|
167
|
+
api = NinjaAIO()
|
|
168
|
+
|
|
169
|
+
@api.viewset(model=models.Article)
|
|
170
|
+
class ArticleViewSet(APIViewSet):
|
|
171
|
+
serializer_class = ArticleSerializer
|
|
172
|
+
# Optionally define query_params or custom handlers
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
Behavior:
|
|
176
|
+
|
|
177
|
+
- If `model` is a ModelSerializer, APIViewSet uses the model to generate schemas directly
|
|
178
|
+
- If `model` is a vanilla Django model and `serializer_class` is provided, APIViewSet uses the Serializer to generate missing schemas
|
|
179
|
+
- ModelUtil creates a serializer instance and uses its `queryset_request()` hook if defined to build optimized querysets
|
|
180
|
+
- Lifecycle hooks from the serializer are invoked during CRUD operations
|
|
181
|
+
|
|
182
|
+
## CRUD Operations with Serializer
|
|
183
|
+
|
|
184
|
+
When using a Serializer with APIViewSet, CRUD operations automatically invoke the appropriate lifecycle hooks:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
# Create operation flow:
|
|
188
|
+
# 1. parse_input_data() - normalize payload
|
|
189
|
+
# 2. create() - create instance
|
|
190
|
+
# 3. custom_actions() - execute custom logic
|
|
191
|
+
# 4. save() - persists with before/after hooks
|
|
192
|
+
# 5. post_create() - post-creation hook
|
|
193
|
+
# 6. read_s() - serialize response
|
|
194
|
+
|
|
195
|
+
# Update operation flow:
|
|
196
|
+
# 1. get_object() - fetch instance
|
|
197
|
+
# 2. parse_input_data() - normalize payload
|
|
198
|
+
# 3. update() - update instance fields
|
|
199
|
+
# 4. custom_actions() - execute custom logic
|
|
200
|
+
# 5. save() - persists with before/after hooks
|
|
201
|
+
# 6. read_s() - serialize response
|
|
202
|
+
|
|
203
|
+
# Delete operation flow:
|
|
204
|
+
# 1. get_object() - fetch instance
|
|
205
|
+
# 2. adelete() - delete instance
|
|
206
|
+
# 3. on_delete() - deletion hook
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Advanced: customs and optionals
|
|
210
|
+
|
|
211
|
+
Customs and optionals behave like ModelSerializer:
|
|
212
|
+
|
|
213
|
+
- customs: synthetic fields included in schemas (with default or required when default is Ellipsis).
|
|
214
|
+
- optionals: patch-like optional fields. In read schema, they are included with default None.
|
|
215
|
+
|
|
216
|
+
```python
|
|
217
|
+
class PublishSerializer(serializers.Serializer):
|
|
218
|
+
class Meta:
|
|
219
|
+
model = models.Article
|
|
220
|
+
schema_update = serializers.SchemaModelConfig(
|
|
221
|
+
optionals=[("is_published", bool)],
|
|
222
|
+
customs=[("notify_subscribers", bool, True)],
|
|
223
|
+
)
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
generate_update_s merges optionals and customs for the Patch schema.
|
|
227
|
+
|
|
228
|
+
## Query Optimization with Serializer
|
|
229
|
+
|
|
230
|
+
Like ModelSerializer, Serializer supports query optimization via a nested QuerySet class:
|
|
231
|
+
|
|
232
|
+
```python
|
|
233
|
+
from ninja_aio.schemas.helpers import ModelQuerySetSchema, ModelQuerySetExtraSchema
|
|
234
|
+
|
|
235
|
+
class ArticleSerializer(serializers.Serializer):
|
|
236
|
+
class Meta:
|
|
237
|
+
model = models.Article
|
|
238
|
+
schema_out = serializers.SchemaModelConfig(
|
|
239
|
+
fields=["id", "title", "content", "author", "category"]
|
|
240
|
+
)
|
|
241
|
+
|
|
242
|
+
class QuerySet:
|
|
243
|
+
read = ModelQuerySetSchema(
|
|
244
|
+
select_related=["author", "category"],
|
|
245
|
+
prefetch_related=["tags"],
|
|
246
|
+
)
|
|
247
|
+
queryset_request = ModelQuerySetSchema(
|
|
248
|
+
select_related=[],
|
|
249
|
+
prefetch_related=["comments"],
|
|
250
|
+
)
|
|
251
|
+
extras = [
|
|
252
|
+
ModelQuerySetExtraSchema(
|
|
253
|
+
scope="detail_view",
|
|
254
|
+
select_related=["author__profile"],
|
|
255
|
+
prefetch_related=["tags", "comments__author"],
|
|
256
|
+
)
|
|
257
|
+
]
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
The QuerySet configuration is used by ModelUtil to automatically optimize database queries during read operations.
|
|
261
|
+
|
|
262
|
+
## Complete Example
|
|
263
|
+
|
|
264
|
+
```python
|
|
265
|
+
from ninja_aio.models import serializers
|
|
266
|
+
from ninja_aio import NinjaAIO
|
|
267
|
+
from ninja_aio.views import APIViewSet
|
|
268
|
+
from django.db import models as django_models
|
|
269
|
+
|
|
270
|
+
# Plain Django models
|
|
271
|
+
class Author(django_models.Model):
|
|
272
|
+
name = django_models.CharField(max_length=200)
|
|
273
|
+
email = django_models.EmailField()
|
|
274
|
+
|
|
275
|
+
class Article(django_models.Model):
|
|
276
|
+
title = django_models.CharField(max_length=200)
|
|
277
|
+
content = django_models.TextField()
|
|
278
|
+
author = django_models.ForeignKey(Author, on_delete=django_models.CASCADE)
|
|
279
|
+
is_published = django_models.BooleanField(default=False)
|
|
280
|
+
|
|
281
|
+
# Serializers
|
|
282
|
+
class AuthorSerializer(serializers.Serializer):
|
|
283
|
+
class Meta:
|
|
284
|
+
model = Author
|
|
285
|
+
schema_out = serializers.SchemaModelConfig(
|
|
286
|
+
fields=["id", "name", "email"]
|
|
287
|
+
)
|
|
288
|
+
|
|
289
|
+
class ArticleSerializer(serializers.Serializer):
|
|
290
|
+
class Meta:
|
|
291
|
+
model = Article
|
|
292
|
+
schema_in = serializers.SchemaModelConfig(
|
|
293
|
+
fields=["title", "content", "author"]
|
|
294
|
+
)
|
|
295
|
+
schema_out = serializers.SchemaModelConfig(
|
|
296
|
+
fields=["id", "title", "content", "author", "is_published"]
|
|
297
|
+
)
|
|
298
|
+
schema_update = serializers.SchemaModelConfig(
|
|
299
|
+
optionals=[("title", str), ("content", str)],
|
|
300
|
+
customs=[("publish_now", bool, False)]
|
|
301
|
+
)
|
|
302
|
+
relations_serializers = {
|
|
303
|
+
"author": AuthorSerializer
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
class QuerySet:
|
|
307
|
+
read = serializers.ModelQuerySetSchema(
|
|
308
|
+
select_related=["author"]
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
async def custom_actions(self, payload, instance):
|
|
312
|
+
if payload.get("publish_now"):
|
|
313
|
+
instance.is_published = True
|
|
314
|
+
await sync_to_async(instance.save)()
|
|
315
|
+
|
|
316
|
+
# ViewSets
|
|
317
|
+
api = NinjaAIO()
|
|
318
|
+
|
|
319
|
+
@api.viewset(model=Article)
|
|
320
|
+
class ArticleViewSet(APIViewSet):
|
|
321
|
+
serializer_class = ArticleSerializer
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## When to choose Serializer vs ModelSerializer
|
|
325
|
+
|
|
326
|
+
**Choose Serializer when:**
|
|
327
|
+
|
|
328
|
+
- You have existing Django models that you can't or don't want to modify
|
|
329
|
+
- You want to keep models and API concerns separated
|
|
330
|
+
- You're incrementally adding API functionality to an existing project
|
|
331
|
+
- You prefer declarative configuration via Meta classes
|
|
332
|
+
- Multiple teams work on models vs API layers
|
|
333
|
+
|
|
334
|
+
**Choose ModelSerializer when:**
|
|
335
|
+
|
|
336
|
+
- You're building a new project from scratch
|
|
337
|
+
- You want to centralize all model and API concerns in one place
|
|
338
|
+
- You prefer configuration via nested classes on the model
|
|
339
|
+
- You want auto-binding and less boilerplate
|
|
340
|
+
- Your models are specifically designed for API usage
|
|
341
|
+
|
|
342
|
+
Both approaches support:
|
|
343
|
+
|
|
344
|
+
- Nested relations and dynamic schema generation
|
|
345
|
+
- Query optimization via QuerySet configuration
|
|
346
|
+
- Lifecycle hooks for custom business logic
|
|
347
|
+
- Integration with APIViewSet for auto-generated CRUD endpoints
|
|
348
|
+
|
|
349
|
+
Choose the pattern that best fits your project architecture and team structure.
|