django-ninja-aio-crud 2.2.0__tar.gz → 2.3.1__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 (105) hide show
  1. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/workflows/docs.yml +4 -0
  2. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/PKG-INFO +38 -24
  3. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/README.md +36 -22
  4. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/api_view.md +56 -10
  5. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/api_view_set.md +128 -29
  6. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/decorators.md +39 -2
  7. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/index.md +34 -43
  8. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/__init__.py +1 -1
  9. django_ninja_aio_crud-2.3.1/ninja_aio/decorators/__init__.py +23 -0
  10. django_ninja_aio_crud-2.3.1/ninja_aio/decorators/operations.py +9 -0
  11. django_ninja_aio_crud-2.2.0/ninja_aio/decorators.py → django_ninja_aio_crud-2.3.1/ninja_aio/decorators/views.py +4 -3
  12. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/exceptions.py +23 -1
  13. django_ninja_aio_crud-2.3.1/ninja_aio/factory/__init__.py +3 -0
  14. django_ninja_aio_crud-2.3.1/ninja_aio/factory/operations.py +296 -0
  15. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/helpers/api.py +16 -2
  16. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/helpers/query.py +6 -1
  17. django_ninja_aio_crud-2.3.1/ninja_aio/schemas/helpers.py +170 -0
  18. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/views/api.py +20 -7
  19. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/pyproject.toml +1 -1
  20. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/helpers/test_many_to_many_api.py +11 -10
  21. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/views/test_views.py +45 -0
  22. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/views/test_viewset.py +50 -0
  23. django_ninja_aio_crud-2.2.0/examples/ex_1/models.py +0 -35
  24. django_ninja_aio_crud-2.2.0/examples/ex_1/urls.py +0 -11
  25. django_ninja_aio_crud-2.2.0/examples/ex_1/views.py +0 -16
  26. django_ninja_aio_crud-2.2.0/examples/ex_2/auth.py +0 -33
  27. django_ninja_aio_crud-2.2.0/examples/ex_2/models.py +0 -62
  28. django_ninja_aio_crud-2.2.0/examples/ex_2/urls.py +0 -11
  29. django_ninja_aio_crud-2.2.0/examples/ex_2/views.py +0 -26
  30. django_ninja_aio_crud-2.2.0/ninja_aio/schemas/helpers.py +0 -90
  31. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/dependabot.yml +0 -0
  32. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/workflows/coverage.yml +0 -0
  33. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/workflows/publish.yml +0 -0
  34. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.gitignore +0 -0
  35. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.pre-commit-config.yaml +0 -0
  36. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/LICENSE +0 -0
  37. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/CNAME +0 -0
  38. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/authentication.md +0 -0
  39. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/models/model_serializer.md +0 -0
  40. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/models/model_util.md +0 -0
  41. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/pagination.md +0 -0
  42. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/renderers/orjson_renderer.md +0 -0
  43. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/mixins.md +0 -0
  44. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/auth.md +0 -0
  45. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/contributing.md +0 -0
  46. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/extra.css +0 -0
  47. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
  48. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
  49. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
  50. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
  51. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
  52. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
  53. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/installation.md +0 -0
  54. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/quick_start.md +0 -0
  55. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/bar-swagger.png +0 -0
  56. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/favicon.ico +0 -0
  57. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/foo-swagger.png +0 -0
  58. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/logo.png +0 -0
  59. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
  60. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/release_notes.md +0 -0
  61. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/requirements.txt +0 -0
  62. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/authentication.md +0 -0
  63. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/crud.md +0 -0
  64. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/filtering.md +0 -0
  65. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/model.md +0 -0
  66. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/main.py +0 -0
  67. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/mkdocs.yml +0 -0
  68. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/api.py +0 -0
  69. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/auth.py +0 -0
  70. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/helpers/__init__.py +0 -0
  71. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/models.py +0 -0
  72. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/parsers.py +0 -0
  73. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/renders.py +0 -0
  74. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/schemas/__init__.py +0 -0
  75. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/schemas/api.py +0 -0
  76. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/schemas/generics.py +0 -0
  77. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/types.py +0 -0
  78. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/views/__init__.py +0 -0
  79. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/views/mixins.py +0 -0
  80. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/requirements.dev.txt +0 -0
  81. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/run-local-coverage.sh +0 -0
  82. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/__init__.py +0 -0
  83. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/__init__.py +0 -0
  84. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/test_decorators.py +0 -0
  85. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/test_exceptions_api.py +0 -0
  86. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/test_renderer_parser.py +0 -0
  87. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/__init__.py +0 -0
  88. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/literals.py +0 -0
  89. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/models.py +0 -0
  90. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/request.py +0 -0
  91. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/views.py +0 -0
  92. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/helpers/__init__.py +0 -0
  93. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/models/__init__.py +0 -0
  94. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/models/test_model_util.py +0 -0
  95. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/models/test_models_extra.py +0 -0
  96. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/__init__.py +0 -0
  97. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/models.py +0 -0
  98. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/schema.py +0 -0
  99. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/views.py +0 -0
  100. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_auth.py +0 -0
  101. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_decorators.py +0 -0
  102. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_exceptions.py +0 -0
  103. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_query_util.py +0 -0
  104. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_settings.py +0 -0
  105. {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/views/__init__.py +0 -0
@@ -13,6 +13,8 @@ on:
13
13
  - stable
14
14
  - "1.0"
15
15
  - "2.0"
16
+ - "2.2"
17
+ - "2.3"
16
18
  make_latest:
17
19
  description: 'Set as "latest" and default?'
18
20
  type: boolean
@@ -29,6 +31,8 @@ on:
29
31
  - stable
30
32
  - "1.0"
31
33
  - "2.0"
34
+ - "2.2"
35
+ - "2.3"
32
36
  delete_confirm:
33
37
  description: 'Confirm deletion of the selected version'
34
38
  type: boolean
@@ -1,9 +1,9 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ninja-aio-crud
3
- Version: 2.2.0
3
+ Version: 2.3.1
4
4
  Summary: Django Ninja AIO CRUD - Rest Framework
5
5
  Author: Giuseppe Casillo
6
- Requires-Python: >=3.10, <=3.14
6
+ Requires-Python: >=3.10, <3.15
7
7
  Description-Content-Type: text/markdown
8
8
  Classifier: Operating System :: OS Independent
9
9
  Classifier: Topic :: Internet
@@ -74,6 +74,7 @@ Add to your project’s dependencies and ensure Django Ninja is installed.
74
74
  ## 🚀 Quick Start
75
75
 
76
76
  models.py
77
+
77
78
  ```python
78
79
  from django.db import models
79
80
  from ninja_aio.models import ModelSerializer
@@ -93,6 +94,7 @@ class Book(ModelSerializer):
93
94
  ```
94
95
 
95
96
  views.py
97
+
96
98
  ```python
97
99
  from ninja_aio import NinjaAIO
98
100
  from ninja_aio.views import APIViewSet
@@ -100,11 +102,10 @@ from .models import Book
100
102
 
101
103
  api = NinjaAIO()
102
104
 
105
+ @api.viewset(Book)
103
106
  class BookViewSet(APIViewSet):
104
- model = Book
105
- api = api
107
+ pass
106
108
 
107
- BookViewSet().add_views_to_route()
108
109
  ```
109
110
 
110
111
  Visit `/docs` → CRUD endpoints ready.
@@ -114,9 +115,8 @@ Visit `/docs` → CRUD endpoints ready.
114
115
  ## 🔄 Query Filtering
115
116
 
116
117
  ```python
118
+ @api.viewset(Book)
117
119
  class BookViewSet(APIViewSet):
118
- model = Book
119
- api = api
120
120
  query_params = {"published": (bool, None), "title": (str, None)}
121
121
 
122
122
  async def query_params_handler(self, queryset, filters):
@@ -128,6 +128,7 @@ class BookViewSet(APIViewSet):
128
128
  ```
129
129
 
130
130
  Request:
131
+
131
132
  ```
132
133
  GET /book/?published=true&title=python
133
134
  ```
@@ -151,9 +152,8 @@ class Article(ModelSerializer):
151
152
  class ReadSerializer:
152
153
  fields = ["id", "title", "tags"]
153
154
 
155
+ @api.viewset(Article)
154
156
  class ArticleViewSet(APIViewSet):
155
- model = Article
156
- api = api
157
157
  m2m_relations = [
158
158
  M2MRelationSchema(
159
159
  model=Tag,
@@ -168,10 +168,10 @@ class ArticleViewSet(APIViewSet):
168
168
  queryset = queryset.filter(name__icontains=n)
169
169
  return queryset
170
170
 
171
- ArticleViewSet().add_views_to_route()
172
171
  ```
173
172
 
174
173
  Endpoints:
174
+
175
175
  - `GET /article/{pk}/tag?name=dev`
176
176
  - `POST /article/{pk}/tag/` body: `{"add":[1,2],"remove":[3]}`
177
177
 
@@ -195,9 +195,8 @@ class JWTAuth(AsyncJwtBearer):
195
195
  book_id = self.dcd.claims.get("sub")
196
196
  return await Book.objects.aget(id=book_id)
197
197
 
198
+ @api.viewset(Book)
198
199
  class SecureBookViewSet(APIViewSet):
199
- model = Book
200
- api = api
201
200
  auth = [JWTAuth()]
202
201
  get_auth = None # list/retrieve public
203
202
  ```
@@ -207,6 +206,7 @@ class SecureBookViewSet(APIViewSet):
207
206
  ## 📑 Lifecycle Hooks (ModelSerializer)
208
207
 
209
208
  Available on every save/delete:
209
+
210
210
  - `on_create_before_save`
211
211
  - `on_create_after_save`
212
212
  - `before_save`
@@ -220,10 +220,21 @@ Available on every save/delete:
220
220
  ## 🧩 Adding Custom Endpoints
221
221
 
222
222
  ```python
223
+ from ninja_aio.decorators import api_get
224
+
225
+ @api.viewset(Book)
223
226
  class BookViewSet(APIViewSet):
224
- model = Book
225
- api = api
227
+ @api_get("/stats/")
228
+ async def stats(self, request):
229
+ total = await Book.objects.acount()
230
+ return {"total": total}
231
+ ```
226
232
 
233
+ Or
234
+
235
+ ```python
236
+ @api.viewset(Book)
237
+ class BookViewSet(APIViewSet):
227
238
  def views(self):
228
239
  @self.router.get("/stats/")
229
240
  async def stats(request):
@@ -244,9 +255,8 @@ class LargePagination(PageNumberPagination):
244
255
  page_size = 50
245
256
  max_page_size = 200
246
257
 
258
+ @api.viewset(Book)
247
259
  class BookViewSet(APIViewSet):
248
- model = Book
249
- api = api
250
260
  pagination_class = LargePagination
251
261
  ```
252
262
 
@@ -255,6 +265,7 @@ class BookViewSet(APIViewSet):
255
265
  ## 🛠 Project Structure & Docs
256
266
 
257
267
  Documentation (MkDocs + Material):
268
+
258
269
  ```
259
270
  docs/
260
271
  getting_started/
@@ -267,17 +278,19 @@ docs/
267
278
  ```
268
279
 
269
280
  Browse full reference:
281
+
270
282
  - APIViewSet: `docs/api/views/api_view_set.md`
271
283
  - APIView: `docs/api/views/api_view.md`
272
284
  - ModelSerializer: `docs/api/models/model_serializer.md`
273
285
  - Authentication: `docs/api/authentication.md`
274
- - Pagination: `docs/api/pagination.md`
286
+ - Example repository: https://github.com/caspel26/ninja-aio-blog-example
275
287
 
276
288
  ---
277
289
 
278
290
  ## 🧪 Tests
279
291
 
280
292
  Use Django test runner + async ORM patterns. Example async pattern:
293
+
281
294
  ```python
282
295
  obj = await Book.objects.acreate(title="T1", published=True)
283
296
  count = await Book.objects.acount()
@@ -288,9 +301,8 @@ count = await Book.objects.acount()
288
301
  ## 🚫 Disable Operations
289
302
 
290
303
  ```python
304
+ @api.viewset(Book)
291
305
  class ReadOnlyBookViewSet(APIViewSet):
292
- model = Book
293
- api = api
294
306
  disable = ["update", "delete"]
295
307
  ```
296
308
 
@@ -318,6 +330,7 @@ class ReadOnlyBookViewSet(APIViewSet):
318
330
  ## ⭐ Support
319
331
 
320
332
  Star the repo or donate:
333
+
321
334
  - [Buy me a coffee](https://buymeacoffee.com/caspel26)
322
335
 
323
336
  ---
@@ -330,11 +343,12 @@ MIT License. See [LICENSE](LICENSE).
330
343
 
331
344
  ## 🔗 Quick Links
332
345
 
333
- | Item | Link |
334
- |------|------|
335
- | PyPI | https://pypi.org/project/django-ninja-aio-crud/ |
336
- | Docs | https://django-ninja-aio.com
337
- | Issues | https://github.com/caspel26/django-ninja-aio-crud/issues |
346
+ | Item | Link |
347
+ | ------- | -------------------------------------------------------- |
348
+ | PyPI | https://pypi.org/project/django-ninja-aio-crud/ |
349
+ | Docs | https://django-ninja-aio.com |
350
+ | Issues | https://github.com/caspel26/django-ninja-aio-crud/issues |
351
+ | Example | https://github.com/caspel26/ninja-aio-blog-example |
338
352
 
339
353
  ---
340
354
 
@@ -39,6 +39,7 @@ Add to your project’s dependencies and ensure Django Ninja is installed.
39
39
  ## 🚀 Quick Start
40
40
 
41
41
  models.py
42
+
42
43
  ```python
43
44
  from django.db import models
44
45
  from ninja_aio.models import ModelSerializer
@@ -58,6 +59,7 @@ class Book(ModelSerializer):
58
59
  ```
59
60
 
60
61
  views.py
62
+
61
63
  ```python
62
64
  from ninja_aio import NinjaAIO
63
65
  from ninja_aio.views import APIViewSet
@@ -65,11 +67,10 @@ from .models import Book
65
67
 
66
68
  api = NinjaAIO()
67
69
 
70
+ @api.viewset(Book)
68
71
  class BookViewSet(APIViewSet):
69
- model = Book
70
- api = api
72
+ pass
71
73
 
72
- BookViewSet().add_views_to_route()
73
74
  ```
74
75
 
75
76
  Visit `/docs` → CRUD endpoints ready.
@@ -79,9 +80,8 @@ Visit `/docs` → CRUD endpoints ready.
79
80
  ## 🔄 Query Filtering
80
81
 
81
82
  ```python
83
+ @api.viewset(Book)
82
84
  class BookViewSet(APIViewSet):
83
- model = Book
84
- api = api
85
85
  query_params = {"published": (bool, None), "title": (str, None)}
86
86
 
87
87
  async def query_params_handler(self, queryset, filters):
@@ -93,6 +93,7 @@ class BookViewSet(APIViewSet):
93
93
  ```
94
94
 
95
95
  Request:
96
+
96
97
  ```
97
98
  GET /book/?published=true&title=python
98
99
  ```
@@ -116,9 +117,8 @@ class Article(ModelSerializer):
116
117
  class ReadSerializer:
117
118
  fields = ["id", "title", "tags"]
118
119
 
120
+ @api.viewset(Article)
119
121
  class ArticleViewSet(APIViewSet):
120
- model = Article
121
- api = api
122
122
  m2m_relations = [
123
123
  M2MRelationSchema(
124
124
  model=Tag,
@@ -133,10 +133,10 @@ class ArticleViewSet(APIViewSet):
133
133
  queryset = queryset.filter(name__icontains=n)
134
134
  return queryset
135
135
 
136
- ArticleViewSet().add_views_to_route()
137
136
  ```
138
137
 
139
138
  Endpoints:
139
+
140
140
  - `GET /article/{pk}/tag?name=dev`
141
141
  - `POST /article/{pk}/tag/` body: `{"add":[1,2],"remove":[3]}`
142
142
 
@@ -160,9 +160,8 @@ class JWTAuth(AsyncJwtBearer):
160
160
  book_id = self.dcd.claims.get("sub")
161
161
  return await Book.objects.aget(id=book_id)
162
162
 
163
+ @api.viewset(Book)
163
164
  class SecureBookViewSet(APIViewSet):
164
- model = Book
165
- api = api
166
165
  auth = [JWTAuth()]
167
166
  get_auth = None # list/retrieve public
168
167
  ```
@@ -172,6 +171,7 @@ class SecureBookViewSet(APIViewSet):
172
171
  ## 📑 Lifecycle Hooks (ModelSerializer)
173
172
 
174
173
  Available on every save/delete:
174
+
175
175
  - `on_create_before_save`
176
176
  - `on_create_after_save`
177
177
  - `before_save`
@@ -185,10 +185,21 @@ Available on every save/delete:
185
185
  ## 🧩 Adding Custom Endpoints
186
186
 
187
187
  ```python
188
+ from ninja_aio.decorators import api_get
189
+
190
+ @api.viewset(Book)
188
191
  class BookViewSet(APIViewSet):
189
- model = Book
190
- api = api
192
+ @api_get("/stats/")
193
+ async def stats(self, request):
194
+ total = await Book.objects.acount()
195
+ return {"total": total}
196
+ ```
191
197
 
198
+ Or
199
+
200
+ ```python
201
+ @api.viewset(Book)
202
+ class BookViewSet(APIViewSet):
192
203
  def views(self):
193
204
  @self.router.get("/stats/")
194
205
  async def stats(request):
@@ -209,9 +220,8 @@ class LargePagination(PageNumberPagination):
209
220
  page_size = 50
210
221
  max_page_size = 200
211
222
 
223
+ @api.viewset(Book)
212
224
  class BookViewSet(APIViewSet):
213
- model = Book
214
- api = api
215
225
  pagination_class = LargePagination
216
226
  ```
217
227
 
@@ -220,6 +230,7 @@ class BookViewSet(APIViewSet):
220
230
  ## 🛠 Project Structure & Docs
221
231
 
222
232
  Documentation (MkDocs + Material):
233
+
223
234
  ```
224
235
  docs/
225
236
  getting_started/
@@ -232,17 +243,19 @@ docs/
232
243
  ```
233
244
 
234
245
  Browse full reference:
246
+
235
247
  - APIViewSet: `docs/api/views/api_view_set.md`
236
248
  - APIView: `docs/api/views/api_view.md`
237
249
  - ModelSerializer: `docs/api/models/model_serializer.md`
238
250
  - Authentication: `docs/api/authentication.md`
239
- - Pagination: `docs/api/pagination.md`
251
+ - Example repository: https://github.com/caspel26/ninja-aio-blog-example
240
252
 
241
253
  ---
242
254
 
243
255
  ## 🧪 Tests
244
256
 
245
257
  Use Django test runner + async ORM patterns. Example async pattern:
258
+
246
259
  ```python
247
260
  obj = await Book.objects.acreate(title="T1", published=True)
248
261
  count = await Book.objects.acount()
@@ -253,9 +266,8 @@ count = await Book.objects.acount()
253
266
  ## 🚫 Disable Operations
254
267
 
255
268
  ```python
269
+ @api.viewset(Book)
256
270
  class ReadOnlyBookViewSet(APIViewSet):
257
- model = Book
258
- api = api
259
271
  disable = ["update", "delete"]
260
272
  ```
261
273
 
@@ -283,6 +295,7 @@ class ReadOnlyBookViewSet(APIViewSet):
283
295
  ## ⭐ Support
284
296
 
285
297
  Star the repo or donate:
298
+
286
299
  - [Buy me a coffee](https://buymeacoffee.com/caspel26)
287
300
 
288
301
  ---
@@ -295,10 +308,11 @@ MIT License. See [LICENSE](LICENSE).
295
308
 
296
309
  ## 🔗 Quick Links
297
310
 
298
- | Item | Link |
299
- |------|------|
300
- | PyPI | https://pypi.org/project/django-ninja-aio-crud/ |
301
- | Docs | https://django-ninja-aio.com
302
- | Issues | https://github.com/caspel26/django-ninja-aio-crud/issues |
311
+ | Item | Link |
312
+ | ------- | -------------------------------------------------------- |
313
+ | PyPI | https://pypi.org/project/django-ninja-aio-crud/ |
314
+ | Docs | https://django-ninja-aio.com |
315
+ | Issues | https://github.com/caspel26/django-ninja-aio-crud/issues |
316
+ | Example | https://github.com/caspel26/ninja-aio-blog-example |
303
317
 
304
318
  ---
@@ -31,9 +31,54 @@ class APIView:
31
31
 
32
32
  ## Methods
33
33
 
34
- ### `views()`
34
+ ### Recommended: decorator-based endpoints
35
35
 
36
- Override this method to define your custom endpoints.
36
+ Prefer class method decorators to define non-CRUD endpoints. Decorators lazily bind instance methods to the router and automatically remove `self` from the OpenAPI signature while preserving type hints.
37
+
38
+ Available decorators (from `ninja_aio.decorators`):
39
+
40
+ - `@api_get(path, ...)`
41
+ - `@api_post(path, ...)`
42
+ - `@api_put(path, ...)`
43
+ - `@api_patch(path, ...)`
44
+ - `@api_delete(path, ...)`
45
+ - `@api_options(path, ...)`
46
+ - `@api_head(path, ...)`
47
+
48
+ Example:
49
+
50
+ ```python
51
+ from ninja_aio import NinjaAIO
52
+ from ninja_aio.views import APIView
53
+ from ninja_aio.decorators import api_get, api_post
54
+ from ninja import Schema
55
+
56
+ api = NinjaAIO(title="My API")
57
+
58
+ class StatsSchema(Schema):
59
+ total: int
60
+ active: int
61
+
62
+ @api.view(prefix="/analytics", tags=["Analytics"])
63
+ class AnalyticsView(APIView):
64
+ @api_get("/dashboard", response=StatsSchema)
65
+ async def dashboard(self, request):
66
+ return {"total": 1000, "active": 750}
67
+
68
+ @api_post("/track")
69
+ async def track_event(self, request, event: str):
70
+ return {"tracked": event}
71
+ ```
72
+
73
+ Notes:
74
+
75
+ - Decorators support per-endpoint `auth`, `response`, `tags`, `summary`, `description`, throttling, and OpenAPI extras.
76
+ - Sync methods run via `sync_to_async` automatically.
77
+ - `self` is excluded from the exposed signature; parameter type hints are preserved.
78
+
79
+ ### Legacy: `views()` (still supported)
80
+
81
+ You can still override `views()` to define endpoints imperatively.
37
82
 
38
83
  **Example - Basic Views:**
39
84
 
@@ -81,7 +126,7 @@ Registers all defined views to the API instance.
81
126
 
82
127
  **Returns:** The router instance
83
128
 
84
- **Note:** When using `@api.view(prefix="/path", tags=[...])`, manual registration via `add_views_to_route()` is not required; the router is mounted automatically.
129
+ **Note:** When using `@api.view(prefix="/path", tags=[...])`, the router is mounted automatically and decorator-based endpoints are registered lazily on instantiation; manual registration via `add_views_to_route()` is not required.
85
130
 
86
131
  ## Complete Example
87
132
 
@@ -90,6 +135,7 @@ Registers all defined views to the API instance.
90
135
  ```python
91
136
  from ninja_aio import NinjaAIO
92
137
  from ninja_aio.views import APIView
138
+ from ninja_aio.decorators import api_get, api_post
93
139
  from ninja import Schema
94
140
 
95
141
  api = NinjaAIO(title="My API")
@@ -100,14 +146,13 @@ class StatsSchema(Schema):
100
146
 
101
147
  @api.view(prefix="/analytics", tags=["Analytics"])
102
148
  class AnalyticsView(APIView):
103
- def views(self):
104
- @self.router.get("/dashboard", response=StatsSchema)
105
- async def dashboard(request):
106
- return {"total": 1000, "active": 750}
149
+ @api_get("/dashboard", response=StatsSchema)
150
+ async def dashboard(self, request):
151
+ return {"total": 1000, "active": 750}
107
152
 
108
- @self.router.post("/track")
109
- async def track_event(request, event: str):
110
- return {"tracked": event}
153
+ @api_post("/track")
154
+ async def track_event(self, request, event: str):
155
+ return {"tracked": event}
111
156
  ```
112
157
 
113
158
  **Alternative implementation:**
@@ -138,6 +183,7 @@ AnalyticsView().add_views_to_route()
138
183
  - For CRUD operations, use [`APIViewSet`](api_view_set.md)
139
184
  - All views are async-compatible
140
185
  - Standard error codes are available via `self.error_codes`
186
+ - Decorator-based endpoints are preferred for clarity and better OpenAPI signatures.
141
187
 
142
188
  Note:
143
189