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.
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/workflows/docs.yml +4 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/PKG-INFO +38 -24
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/README.md +36 -22
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/api_view.md +56 -10
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/api_view_set.md +128 -29
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/decorators.md +39 -2
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/index.md +34 -43
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/__init__.py +1 -1
- django_ninja_aio_crud-2.3.1/ninja_aio/decorators/__init__.py +23 -0
- django_ninja_aio_crud-2.3.1/ninja_aio/decorators/operations.py +9 -0
- 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
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/exceptions.py +23 -1
- django_ninja_aio_crud-2.3.1/ninja_aio/factory/__init__.py +3 -0
- django_ninja_aio_crud-2.3.1/ninja_aio/factory/operations.py +296 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/helpers/api.py +16 -2
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/helpers/query.py +6 -1
- django_ninja_aio_crud-2.3.1/ninja_aio/schemas/helpers.py +170 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/views/api.py +20 -7
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/pyproject.toml +1 -1
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/helpers/test_many_to_many_api.py +11 -10
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/views/test_views.py +45 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/views/test_viewset.py +50 -0
- django_ninja_aio_crud-2.2.0/examples/ex_1/models.py +0 -35
- django_ninja_aio_crud-2.2.0/examples/ex_1/urls.py +0 -11
- django_ninja_aio_crud-2.2.0/examples/ex_1/views.py +0 -16
- django_ninja_aio_crud-2.2.0/examples/ex_2/auth.py +0 -33
- django_ninja_aio_crud-2.2.0/examples/ex_2/models.py +0 -62
- django_ninja_aio_crud-2.2.0/examples/ex_2/urls.py +0 -11
- django_ninja_aio_crud-2.2.0/examples/ex_2/views.py +0 -26
- django_ninja_aio_crud-2.2.0/ninja_aio/schemas/helpers.py +0 -90
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.gitignore +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/LICENSE +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/models/model_serializer.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/models/model_util.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/api/views/mixins.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/extra.css +0 -0
- {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
- {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
- {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
- {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
- {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
- {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
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/images/logo.png +0 -0
- {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
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/main.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/mkdocs.yml +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/models.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/models.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_app/views.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.2.0 → django_ninja_aio_crud-2.3.1}/tests/test_settings.py +0 -0
- {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.
|
|
3
|
+
Version: 2.3.1
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
|
-
Requires-Python: >=3.10,
|
|
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
|
-
|
|
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
|
-
|
|
225
|
-
|
|
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
|
-
-
|
|
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
|
|
334
|
-
|
|
335
|
-
| PyPI
|
|
336
|
-
| Docs
|
|
337
|
-
| 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
|
-
|
|
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
|
-
|
|
190
|
-
|
|
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
|
-
-
|
|
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
|
|
299
|
-
|
|
300
|
-
| PyPI
|
|
301
|
-
| Docs
|
|
302
|
-
| 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
|
-
###
|
|
34
|
+
### Recommended: decorator-based endpoints
|
|
35
35
|
|
|
36
|
-
|
|
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
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
109
|
-
|
|
110
|
-
|
|
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
|
|