django-ninja-aio-crud 2.4.0__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.

Files changed (103) hide show
  1. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/.github/workflows/docs.yml +2 -0
  2. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/PKG-INFO +26 -29
  3. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/README.md +25 -28
  4. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/models/model_util.md +77 -9
  5. django_ninja_aio_crud-2.5.0/docs/api/models/serializers.md +349 -0
  6. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/views/api_view_set.md +86 -19
  7. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/index.md +46 -11
  8. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/__init__.py +1 -1
  9. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/helpers/query.py +2 -2
  10. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/models/serializers.py +194 -44
  11. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/models/utils.py +57 -9
  12. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/views/api.py +18 -10
  13. django_ninja_aio_crud-2.4.0/docs/api/models/serializers.md +0 -130
  14. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/.github/dependabot.yml +0 -0
  15. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/.github/workflows/coverage.yml +0 -0
  16. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/.github/workflows/publish.yml +0 -0
  17. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/.gitignore +0 -0
  18. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/.pre-commit-config.yaml +0 -0
  19. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/LICENSE +0 -0
  20. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/CNAME +0 -0
  21. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/authentication.md +0 -0
  22. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/models/model_serializer.md +0 -0
  23. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/pagination.md +0 -0
  24. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/renderers/orjson_renderer.md +0 -0
  25. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/views/api_view.md +0 -0
  26. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/views/decorators.md +0 -0
  27. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/api/views/mixins.md +0 -0
  28. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/auth.md +0 -0
  29. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/contributing.md +0 -0
  30. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/extra.css +0 -0
  31. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
  32. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
  33. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
  34. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
  35. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
  36. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
  37. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/installation.md +0 -0
  38. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/getting_started/quick_start.md +0 -0
  39. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/images/bar-swagger.png +0 -0
  40. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/images/favicon.ico +0 -0
  41. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/images/foo-swagger.png +0 -0
  42. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/images/logo.png +0 -0
  43. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
  44. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/release_notes.md +0 -0
  45. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/requirements.txt +0 -0
  46. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/tutorial/authentication.md +0 -0
  47. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/tutorial/crud.md +0 -0
  48. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/tutorial/filtering.md +0 -0
  49. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/docs/tutorial/model.md +0 -0
  50. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/main.py +0 -0
  51. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/mkdocs.yml +0 -0
  52. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/api.py +0 -0
  53. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/auth.py +0 -0
  54. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/decorators/__init__.py +0 -0
  55. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/decorators/operations.py +0 -0
  56. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/decorators/views.py +0 -0
  57. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/exceptions.py +0 -0
  58. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/factory/__init__.py +0 -0
  59. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/factory/operations.py +0 -0
  60. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/helpers/__init__.py +0 -0
  61. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/helpers/api.py +0 -0
  62. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/models/__init__.py +0 -0
  63. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/parsers.py +0 -0
  64. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/renders.py +0 -0
  65. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/__init__.py +0 -0
  66. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/api.py +0 -0
  67. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/generics.py +0 -0
  68. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/schemas/helpers.py +0 -0
  69. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/types.py +0 -0
  70. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/views/__init__.py +0 -0
  71. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/ninja_aio/views/mixins.py +0 -0
  72. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/pyproject.toml +0 -0
  73. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/requirements.dev.txt +0 -0
  74. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/run-local-coverage.sh +0 -0
  75. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/__init__.py +0 -0
  76. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/core/__init__.py +0 -0
  77. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/core/test_decorators.py +0 -0
  78. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/core/test_exceptions_api.py +0 -0
  79. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/core/test_renderer_parser.py +0 -0
  80. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/generics/__init__.py +0 -0
  81. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/generics/literals.py +0 -0
  82. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/generics/models.py +0 -0
  83. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/generics/request.py +0 -0
  84. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/generics/views.py +0 -0
  85. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/helpers/__init__.py +0 -0
  86. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/helpers/test_many_to_many_api.py +0 -0
  87. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/models/__init__.py +0 -0
  88. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/models/test_model_util.py +0 -0
  89. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/models/test_models_extra.py +0 -0
  90. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_app/__init__.py +0 -0
  91. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_app/models.py +0 -0
  92. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_app/schema.py +0 -0
  93. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_app/serializers.py +0 -0
  94. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_app/views.py +0 -0
  95. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_auth.py +0 -0
  96. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_decorators.py +0 -0
  97. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_exceptions.py +0 -0
  98. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_query_util.py +0 -0
  99. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_serializers.py +0 -0
  100. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/test_settings.py +0 -0
  101. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/views/__init__.py +0 -0
  102. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/views/test_views.py +0 -0
  103. {django_ninja_aio_crud-2.4.0 → django_ninja_aio_crud-2.5.0}/tests/views/test_viewset.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.4.0
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
- ## 📦 Installation
65
+ ## 🚀 Quick Start (Serializer)
65
66
 
66
- ```bash
67
- pip install django-ninja-aio-crud
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
- Add to your project’s dependencies and ensure Django Ninja is installed.
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
 
@@ -264,29 +283,7 @@ class BookViewSet(APIViewSet):
264
283
 
265
284
  ## Meta-driven Serializer (for vanilla Django models)
266
285
 
267
- If you already have Django models and don't want to inherit from ModelSerializer, use the Meta-driven Serializer to generate dynamic schemas and integrate with APIViewSet.
268
-
269
- Example:
270
-
271
- ```python
272
- from ninja_aio.models import serializers
273
- from . import models
274
-
275
- class BookSerializer(serializers.Serializer):
276
- class Meta:
277
- model = models.Book
278
- schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
279
- schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
280
- schema_update = serializers.SchemaModelConfig(optionals=[("title", str), ("published", bool)])
281
-
282
- @api.viewset(models.Book)
283
- class BookViewSet(APIViewSet):
284
- serializer_class = BookSerializer
285
- ```
286
-
287
- - Works without modifying existing models
288
- - Supports nested relations via relations_serializers
289
- - APIViewSet will auto-generate missing schemas from the serializer
286
+ Moved above as the primary quick start.
290
287
 
291
288
  ---
292
289
 
@@ -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
- ## 📦 Installation
30
+ ## 🚀 Quick Start (Serializer)
30
31
 
31
- ```bash
32
- pip install django-ninja-aio-crud
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
- Add to your project’s dependencies and ensure Django Ninja is installed.
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
 
@@ -229,29 +248,7 @@ class BookViewSet(APIViewSet):
229
248
 
230
249
  ## Meta-driven Serializer (for vanilla Django models)
231
250
 
232
- If you already have Django models and don't want to inherit from ModelSerializer, use the Meta-driven Serializer to generate dynamic schemas and integrate with APIViewSet.
233
-
234
- Example:
235
-
236
- ```python
237
- from ninja_aio.models import serializers
238
- from . import models
239
-
240
- class BookSerializer(serializers.Serializer):
241
- class Meta:
242
- model = models.Book
243
- schema_in = serializers.SchemaModelConfig(fields=["title", "published"])
244
- schema_out = serializers.SchemaModelConfig(fields=["id", "title", "published"])
245
- schema_update = serializers.SchemaModelConfig(optionals=[("title", str), ("published", bool)])
246
-
247
- @api.viewset(models.Book)
248
- class BookViewSet(APIViewSet):
249
- serializer_class = BookSerializer
250
- ```
251
-
252
- - Works without modifying existing models
253
- - Supports nested relations via relations_serializers
254
- - APIViewSet will auto-generate missing schemas from the serializer
251
+ Moved above as the primary quick start.
255
252
 
256
253
  ---
257
254
 
@@ -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
- ### `schema_out_fields`
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.schema_out_fields)
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
- Errors:
217
+ **Parameters:**
177
218
 
178
- - ValueError if neither pk nor getters provided.
179
- - NotFoundError if no match.
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
- Behavior:
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, select_related and prefetch_related are merged with model-discovered relations.
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.