django-ninja-aio-crud 0.1.2__tar.gz → 0.1.4__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.
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/PKG-INFO +132 -9
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/README.md +131 -8
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/models.py +19 -29
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/renders.py +6 -6
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/views.py +3 -0
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/ninja_aio/schemas.py +0 -0
- {django_ninja_aio_crud-0.1.2 → django_ninja_aio_crud-0.1.4}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: django-ninja-aio-crud
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -34,24 +34,32 @@ Project-URL: Repository, https://github.com/caspel26/django-ninja-aio-crud
|
|
|
34
34
|
## 📝 Instructions
|
|
35
35
|
|
|
36
36
|
### 📚 Prerequisites
|
|
37
|
+
|
|
37
38
|
- Install Python from the [official website](https://www.python.org/) (latest version) and ensure it is added to the system Path and environment variables.
|
|
38
39
|
|
|
39
40
|
### 💻 Setup your environment
|
|
41
|
+
|
|
40
42
|
- Create a virtual environment
|
|
41
43
|
```bash
|
|
42
44
|
python -m venv .venv
|
|
43
45
|
```
|
|
46
|
+
|
|
44
47
|
### ✅ Activate it
|
|
48
|
+
|
|
45
49
|
- If you are from linux activate it with
|
|
50
|
+
|
|
46
51
|
```bash
|
|
47
52
|
. .venv/bin/activate
|
|
48
53
|
```
|
|
54
|
+
|
|
49
55
|
- If you are from windows activate it with
|
|
56
|
+
|
|
50
57
|
```bash
|
|
51
58
|
. .venv/Scripts/activate
|
|
52
59
|
```
|
|
53
60
|
|
|
54
61
|
### 📥 Install package
|
|
62
|
+
|
|
55
63
|
```bash
|
|
56
64
|
pip install django-ninja-aio-crud
|
|
57
65
|
```
|
|
@@ -63,14 +71,17 @@ pip install django-ninja-aio-crud
|
|
|
63
71
|
> and why not ... [Buy me a coffee](https://buymeacoffee.com/caspel26)
|
|
64
72
|
|
|
65
73
|
### ModelSerializer
|
|
74
|
+
|
|
66
75
|
- You can serialize your models using ModelSerializer and made them inherit from it. In your models.py import ModelSerializer
|
|
67
76
|
```Python
|
|
77
|
+
# models.py
|
|
78
|
+
from django.db import models
|
|
68
79
|
from ninja_aio.models import ModelSerializer
|
|
69
80
|
|
|
70
81
|
|
|
71
82
|
class Foo(ModelSerializer):
|
|
72
|
-
name =
|
|
73
|
-
bar = models.CharField()
|
|
83
|
+
name = models.CharField(max_length=30)
|
|
84
|
+
bar = models.CharField(max_length=30)
|
|
74
85
|
|
|
75
86
|
class ReadSerializer:
|
|
76
87
|
fields = ["id", "name", "bar"]
|
|
@@ -81,14 +92,18 @@ class Foo(ModelSerializer):
|
|
|
81
92
|
class UpdateSerializer:
|
|
82
93
|
fields = ["name", "bar"]
|
|
83
94
|
```
|
|
95
|
+
|
|
84
96
|
- ReadSerializer, CreateSerializer, UpdateSerializer are used to define which fields would be included in runtime schemas creation. You can also specify custom fields and handle their function by overriding custom_actions ModelSerializer's method(custom fields are only available for Create and Update serializers).
|
|
97
|
+
|
|
85
98
|
```Python
|
|
99
|
+
# models.py
|
|
100
|
+
from django.db import models
|
|
86
101
|
from ninja_aio.models import ModelSerializer
|
|
87
102
|
|
|
88
103
|
|
|
89
104
|
class Foo(ModelSerializer):
|
|
90
|
-
name =
|
|
91
|
-
bar = models.CharField()
|
|
105
|
+
name = models.CharField(max_length=30)
|
|
106
|
+
bar = models.CharField(max_length=30)
|
|
92
107
|
active = models.BooleanField(default=False)
|
|
93
108
|
|
|
94
109
|
class ReadSerializer:
|
|
@@ -112,12 +127,15 @@ class Foo(ModelSerializer):
|
|
|
112
127
|
self.active = True
|
|
113
128
|
await self.asave()
|
|
114
129
|
```
|
|
115
|
-
- post create method is a custom method that comes out to handle actions which will be excuted after that the object is created. It can be used, indeed, for example to handle custom fields' actions.
|
|
116
130
|
|
|
131
|
+
- post create method is a custom method that comes out to handle actions which will be excuted after that the object is created. It can be used, indeed, for example to handle custom fields' actions.
|
|
117
132
|
|
|
118
133
|
### APIViewSet
|
|
134
|
+
|
|
119
135
|
- View class used to automatically generate CRUD views. in your views.py import APIViewSet and define your api using NinjaAPI class. As Parser and Render of the API you must use ninja_aio built-in classes which will serialize data using orjson.
|
|
136
|
+
|
|
120
137
|
```Python
|
|
138
|
+
# views.py
|
|
121
139
|
from ninja import NinjAPI
|
|
122
140
|
from ninja_aio.views import APIViewSet
|
|
123
141
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -135,8 +153,11 @@ class FooAPI(APIViewSet):
|
|
|
135
153
|
|
|
136
154
|
FooAPI().add_views_to_route()
|
|
137
155
|
```
|
|
156
|
+
|
|
138
157
|
- and that's it, your model CRUD will be automatically created. You can also add custom views to CRUD overriding the built-in method "views".
|
|
158
|
+
|
|
139
159
|
```Python
|
|
160
|
+
# views.py
|
|
140
161
|
from ninja import NinjAPI, Schema
|
|
141
162
|
from ninja_aio.views import APIViewSet
|
|
142
163
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -161,7 +182,7 @@ class FooAPI(APIViewSet):
|
|
|
161
182
|
api = api
|
|
162
183
|
|
|
163
184
|
def views(self):
|
|
164
|
-
@self.router.post("numbers-sum/", response={200: ExampleSchemaOut)
|
|
185
|
+
@self.router.post("numbers-sum/", response={200: ExampleSchemaOut})
|
|
165
186
|
async def sum(request: HttpRequest, data: ExampleSchemaIn):
|
|
166
187
|
return 200, {sum: data.n1 + data.n2}
|
|
167
188
|
|
|
@@ -170,8 +191,11 @@ FooAPI().add_views_to_route()
|
|
|
170
191
|
```
|
|
171
192
|
|
|
172
193
|
### APIView
|
|
194
|
+
|
|
173
195
|
- View class to code generic views class based. In your views.py import APIView class.
|
|
196
|
+
|
|
174
197
|
```Python
|
|
198
|
+
# views.py
|
|
175
199
|
from ninja import NinjAPI, Schema
|
|
176
200
|
from ninja_aio.views import APIView
|
|
177
201
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -195,7 +219,7 @@ class SumView(APIView):
|
|
|
195
219
|
router_tag = "Sum"
|
|
196
220
|
|
|
197
221
|
def views(self):
|
|
198
|
-
@self.router.post(
|
|
222
|
+
@self.router.post("/", response={200: ExampleSchemaOut})
|
|
199
223
|
async def sum(request: HttpRequest, data: ExampleSchemaIn):
|
|
200
224
|
return 200, {sum: data.n1 + data.n2}
|
|
201
225
|
|
|
@@ -203,9 +227,91 @@ class SumView(APIView):
|
|
|
203
227
|
SumView().add_views_to_route()
|
|
204
228
|
```
|
|
205
229
|
|
|
230
|
+
### Relations
|
|
231
|
+
- You can also set ForeignKey and OneToOne relations into serialization(reverse relations are supported too). Django ninja aio crud will serialize every of these relation automatically.
|
|
232
|
+
|
|
233
|
+
> [!WARNING]
|
|
234
|
+
> Only ForeignKey and OneToOne relations are supported for serialization, ManyToMany relations are not supported yet.
|
|
235
|
+
|
|
236
|
+
- Define models:
|
|
237
|
+
|
|
238
|
+
```Python
|
|
239
|
+
# models.py
|
|
240
|
+
class Bar(ModelSerializer):
|
|
241
|
+
name = models.CharField(max_length=30)
|
|
242
|
+
description = models.TextField(max_length=30)
|
|
243
|
+
|
|
244
|
+
# ReadSerializer with reverse OneToMany relation (foos)
|
|
245
|
+
class ReadSerializer:
|
|
246
|
+
fields = ["id", "name", "description", "foos"]
|
|
247
|
+
|
|
248
|
+
class CreateSerializer:
|
|
249
|
+
fields = ["name", "description"]
|
|
250
|
+
|
|
251
|
+
class UpdateSerializer:
|
|
252
|
+
fields = ["name", "description"]
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
class Foo(ModelSerializer):
|
|
256
|
+
name = models.CharField(max_length=30)
|
|
257
|
+
bar = models.ForeignKey(Bar, on_delete=models.CASCADE, related_name="foos")
|
|
258
|
+
|
|
259
|
+
class ReadSerializer:
|
|
260
|
+
fields = ["id", "name", "bar"]
|
|
261
|
+
|
|
262
|
+
class CreateSerializer:
|
|
263
|
+
fields = ["name", "bar"]
|
|
264
|
+
|
|
265
|
+
class UpdateSerializer:
|
|
266
|
+
fields = ["name"]
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
- Define views:
|
|
270
|
+
|
|
271
|
+
```Python
|
|
272
|
+
# views.py
|
|
273
|
+
from ninja import NinjAPI
|
|
274
|
+
from ninja_aio.views import APIViewSet
|
|
275
|
+
from ninja_aio.parsers import ORJSONParser
|
|
276
|
+
from ninja_aio.renders import ORJSONRender
|
|
277
|
+
|
|
278
|
+
from .models import Foo, Bar
|
|
279
|
+
|
|
280
|
+
api = NinjaAPI(renderer=ORJSONRenderer(), parser=ORJSONParser())
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
class FooAPI(APIViewSet):
|
|
284
|
+
model = Foo
|
|
285
|
+
api = api
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class BarAPI(APIViewSet):
|
|
289
|
+
model = Bar
|
|
290
|
+
api = api
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
FooAPI().add_views_to_route()
|
|
294
|
+
BarAPI().add_views_to_route()
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
- Now run your server and go to /docs url:
|
|
298
|
+
|
|
299
|
+
### Docs
|
|
300
|
+
|
|
301
|
+
- Foo Schemas
|
|
302
|
+
|
|
303
|
+

|
|
304
|
+
|
|
305
|
+
- Bar Schemas with reverse relation
|
|
306
|
+
|
|
307
|
+

|
|
308
|
+
|
|
206
309
|
## 🔒 Authentication
|
|
310
|
+
|
|
207
311
|
### Jwt
|
|
312
|
+
|
|
208
313
|
- AsyncJWTBearer built-in class is an authenticator class which use joserfc module. It cames out with authenticate method which validate given claims. Override auth handler method to write your own authentication method. Default algorithms used is RS256. a jwt Token istance is set as class atribute so you can use it by self.dcd.
|
|
314
|
+
|
|
209
315
|
```Python
|
|
210
316
|
from ninja_aio.auth import AsyncJWTBearer
|
|
211
317
|
from django.conf import settings
|
|
@@ -225,8 +331,11 @@ class CustomJWTBearer(AsyncJWTBearer):
|
|
|
225
331
|
return None
|
|
226
332
|
return request.user
|
|
227
333
|
```
|
|
334
|
+
|
|
228
335
|
- Then add it to views.
|
|
336
|
+
|
|
229
337
|
```Python
|
|
338
|
+
# views.py
|
|
230
339
|
from ninja import NinjAPI, Schema
|
|
231
340
|
from ninja_aio.views import APIViewSet, APIView
|
|
232
341
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -259,7 +368,7 @@ class SumView(APIView):
|
|
|
259
368
|
auths = CustomJWTBearer()
|
|
260
369
|
|
|
261
370
|
def views(self):
|
|
262
|
-
@self.router.post(
|
|
371
|
+
@self.router.post("/", response={200: ExampleSchemaOut}, auth=self.auths)
|
|
263
372
|
async def sum(request: HttpRequest, data: ExampleSchemaIn):
|
|
264
373
|
return 200, {sum: data.n1 + data.n2}
|
|
265
374
|
|
|
@@ -268,6 +377,20 @@ FooAPI().add_views_to_route()
|
|
|
268
377
|
SumView().add_views_to_route()
|
|
269
378
|
```
|
|
270
379
|
|
|
380
|
+
## 📝 Pagination
|
|
381
|
+
|
|
382
|
+
- By default APIViewSet list view uses Django Ninja built-in AsyncPagination class "PageNumberPagination". You can customize and assign it to APIViewSet class. To make your custom pagination consult **<a href="https://django-ninja.dev/guides/response/pagination/#async-pagination">Django Ninja pagination documentation</a>**.
|
|
383
|
+
|
|
384
|
+
```Python
|
|
385
|
+
# views.py
|
|
386
|
+
|
|
387
|
+
class FooAPI(APIViewSet):
|
|
388
|
+
model = Foo
|
|
389
|
+
api = api
|
|
390
|
+
pagination_class = CustomPaginationClass
|
|
391
|
+
|
|
392
|
+
```
|
|
393
|
+
|
|
271
394
|
## 📌 Notes
|
|
272
395
|
- Feel free to contribute and improve the program. 🛠️
|
|
273
396
|
|
|
@@ -5,24 +5,32 @@
|
|
|
5
5
|
## 📝 Instructions
|
|
6
6
|
|
|
7
7
|
### 📚 Prerequisites
|
|
8
|
+
|
|
8
9
|
- Install Python from the [official website](https://www.python.org/) (latest version) and ensure it is added to the system Path and environment variables.
|
|
9
10
|
|
|
10
11
|
### 💻 Setup your environment
|
|
12
|
+
|
|
11
13
|
- Create a virtual environment
|
|
12
14
|
```bash
|
|
13
15
|
python -m venv .venv
|
|
14
16
|
```
|
|
17
|
+
|
|
15
18
|
### ✅ Activate it
|
|
19
|
+
|
|
16
20
|
- If you are from linux activate it with
|
|
21
|
+
|
|
17
22
|
```bash
|
|
18
23
|
. .venv/bin/activate
|
|
19
24
|
```
|
|
25
|
+
|
|
20
26
|
- If you are from windows activate it with
|
|
27
|
+
|
|
21
28
|
```bash
|
|
22
29
|
. .venv/Scripts/activate
|
|
23
30
|
```
|
|
24
31
|
|
|
25
32
|
### 📥 Install package
|
|
33
|
+
|
|
26
34
|
```bash
|
|
27
35
|
pip install django-ninja-aio-crud
|
|
28
36
|
```
|
|
@@ -34,14 +42,17 @@ pip install django-ninja-aio-crud
|
|
|
34
42
|
> and why not ... [Buy me a coffee](https://buymeacoffee.com/caspel26)
|
|
35
43
|
|
|
36
44
|
### ModelSerializer
|
|
45
|
+
|
|
37
46
|
- You can serialize your models using ModelSerializer and made them inherit from it. In your models.py import ModelSerializer
|
|
38
47
|
```Python
|
|
48
|
+
# models.py
|
|
49
|
+
from django.db import models
|
|
39
50
|
from ninja_aio.models import ModelSerializer
|
|
40
51
|
|
|
41
52
|
|
|
42
53
|
class Foo(ModelSerializer):
|
|
43
|
-
name =
|
|
44
|
-
bar = models.CharField()
|
|
54
|
+
name = models.CharField(max_length=30)
|
|
55
|
+
bar = models.CharField(max_length=30)
|
|
45
56
|
|
|
46
57
|
class ReadSerializer:
|
|
47
58
|
fields = ["id", "name", "bar"]
|
|
@@ -52,14 +63,18 @@ class Foo(ModelSerializer):
|
|
|
52
63
|
class UpdateSerializer:
|
|
53
64
|
fields = ["name", "bar"]
|
|
54
65
|
```
|
|
66
|
+
|
|
55
67
|
- ReadSerializer, CreateSerializer, UpdateSerializer are used to define which fields would be included in runtime schemas creation. You can also specify custom fields and handle their function by overriding custom_actions ModelSerializer's method(custom fields are only available for Create and Update serializers).
|
|
68
|
+
|
|
56
69
|
```Python
|
|
70
|
+
# models.py
|
|
71
|
+
from django.db import models
|
|
57
72
|
from ninja_aio.models import ModelSerializer
|
|
58
73
|
|
|
59
74
|
|
|
60
75
|
class Foo(ModelSerializer):
|
|
61
|
-
name =
|
|
62
|
-
bar = models.CharField()
|
|
76
|
+
name = models.CharField(max_length=30)
|
|
77
|
+
bar = models.CharField(max_length=30)
|
|
63
78
|
active = models.BooleanField(default=False)
|
|
64
79
|
|
|
65
80
|
class ReadSerializer:
|
|
@@ -83,12 +98,15 @@ class Foo(ModelSerializer):
|
|
|
83
98
|
self.active = True
|
|
84
99
|
await self.asave()
|
|
85
100
|
```
|
|
86
|
-
- post create method is a custom method that comes out to handle actions which will be excuted after that the object is created. It can be used, indeed, for example to handle custom fields' actions.
|
|
87
101
|
|
|
102
|
+
- post create method is a custom method that comes out to handle actions which will be excuted after that the object is created. It can be used, indeed, for example to handle custom fields' actions.
|
|
88
103
|
|
|
89
104
|
### APIViewSet
|
|
105
|
+
|
|
90
106
|
- View class used to automatically generate CRUD views. in your views.py import APIViewSet and define your api using NinjaAPI class. As Parser and Render of the API you must use ninja_aio built-in classes which will serialize data using orjson.
|
|
107
|
+
|
|
91
108
|
```Python
|
|
109
|
+
# views.py
|
|
92
110
|
from ninja import NinjAPI
|
|
93
111
|
from ninja_aio.views import APIViewSet
|
|
94
112
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -106,8 +124,11 @@ class FooAPI(APIViewSet):
|
|
|
106
124
|
|
|
107
125
|
FooAPI().add_views_to_route()
|
|
108
126
|
```
|
|
127
|
+
|
|
109
128
|
- and that's it, your model CRUD will be automatically created. You can also add custom views to CRUD overriding the built-in method "views".
|
|
129
|
+
|
|
110
130
|
```Python
|
|
131
|
+
# views.py
|
|
111
132
|
from ninja import NinjAPI, Schema
|
|
112
133
|
from ninja_aio.views import APIViewSet
|
|
113
134
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -132,7 +153,7 @@ class FooAPI(APIViewSet):
|
|
|
132
153
|
api = api
|
|
133
154
|
|
|
134
155
|
def views(self):
|
|
135
|
-
@self.router.post("numbers-sum/", response={200: ExampleSchemaOut)
|
|
156
|
+
@self.router.post("numbers-sum/", response={200: ExampleSchemaOut})
|
|
136
157
|
async def sum(request: HttpRequest, data: ExampleSchemaIn):
|
|
137
158
|
return 200, {sum: data.n1 + data.n2}
|
|
138
159
|
|
|
@@ -141,8 +162,11 @@ FooAPI().add_views_to_route()
|
|
|
141
162
|
```
|
|
142
163
|
|
|
143
164
|
### APIView
|
|
165
|
+
|
|
144
166
|
- View class to code generic views class based. In your views.py import APIView class.
|
|
167
|
+
|
|
145
168
|
```Python
|
|
169
|
+
# views.py
|
|
146
170
|
from ninja import NinjAPI, Schema
|
|
147
171
|
from ninja_aio.views import APIView
|
|
148
172
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -166,7 +190,7 @@ class SumView(APIView):
|
|
|
166
190
|
router_tag = "Sum"
|
|
167
191
|
|
|
168
192
|
def views(self):
|
|
169
|
-
@self.router.post(
|
|
193
|
+
@self.router.post("/", response={200: ExampleSchemaOut})
|
|
170
194
|
async def sum(request: HttpRequest, data: ExampleSchemaIn):
|
|
171
195
|
return 200, {sum: data.n1 + data.n2}
|
|
172
196
|
|
|
@@ -174,9 +198,91 @@ class SumView(APIView):
|
|
|
174
198
|
SumView().add_views_to_route()
|
|
175
199
|
```
|
|
176
200
|
|
|
201
|
+
### Relations
|
|
202
|
+
- You can also set ForeignKey and OneToOne relations into serialization(reverse relations are supported too). Django ninja aio crud will serialize every of these relation automatically.
|
|
203
|
+
|
|
204
|
+
> [!WARNING]
|
|
205
|
+
> Only ForeignKey and OneToOne relations are supported for serialization, ManyToMany relations are not supported yet.
|
|
206
|
+
|
|
207
|
+
- Define models:
|
|
208
|
+
|
|
209
|
+
```Python
|
|
210
|
+
# models.py
|
|
211
|
+
class Bar(ModelSerializer):
|
|
212
|
+
name = models.CharField(max_length=30)
|
|
213
|
+
description = models.TextField(max_length=30)
|
|
214
|
+
|
|
215
|
+
# ReadSerializer with reverse OneToMany relation (foos)
|
|
216
|
+
class ReadSerializer:
|
|
217
|
+
fields = ["id", "name", "description", "foos"]
|
|
218
|
+
|
|
219
|
+
class CreateSerializer:
|
|
220
|
+
fields = ["name", "description"]
|
|
221
|
+
|
|
222
|
+
class UpdateSerializer:
|
|
223
|
+
fields = ["name", "description"]
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
class Foo(ModelSerializer):
|
|
227
|
+
name = models.CharField(max_length=30)
|
|
228
|
+
bar = models.ForeignKey(Bar, on_delete=models.CASCADE, related_name="foos")
|
|
229
|
+
|
|
230
|
+
class ReadSerializer:
|
|
231
|
+
fields = ["id", "name", "bar"]
|
|
232
|
+
|
|
233
|
+
class CreateSerializer:
|
|
234
|
+
fields = ["name", "bar"]
|
|
235
|
+
|
|
236
|
+
class UpdateSerializer:
|
|
237
|
+
fields = ["name"]
|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
- Define views:
|
|
241
|
+
|
|
242
|
+
```Python
|
|
243
|
+
# views.py
|
|
244
|
+
from ninja import NinjAPI
|
|
245
|
+
from ninja_aio.views import APIViewSet
|
|
246
|
+
from ninja_aio.parsers import ORJSONParser
|
|
247
|
+
from ninja_aio.renders import ORJSONRender
|
|
248
|
+
|
|
249
|
+
from .models import Foo, Bar
|
|
250
|
+
|
|
251
|
+
api = NinjaAPI(renderer=ORJSONRenderer(), parser=ORJSONParser())
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
class FooAPI(APIViewSet):
|
|
255
|
+
model = Foo
|
|
256
|
+
api = api
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
class BarAPI(APIViewSet):
|
|
260
|
+
model = Bar
|
|
261
|
+
api = api
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
FooAPI().add_views_to_route()
|
|
265
|
+
BarAPI().add_views_to_route()
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
- Now run your server and go to /docs url:
|
|
269
|
+
|
|
270
|
+
### Docs
|
|
271
|
+
|
|
272
|
+
- Foo Schemas
|
|
273
|
+
|
|
274
|
+

|
|
275
|
+
|
|
276
|
+
- Bar Schemas with reverse relation
|
|
277
|
+
|
|
278
|
+

|
|
279
|
+
|
|
177
280
|
## 🔒 Authentication
|
|
281
|
+
|
|
178
282
|
### Jwt
|
|
283
|
+
|
|
179
284
|
- AsyncJWTBearer built-in class is an authenticator class which use joserfc module. It cames out with authenticate method which validate given claims. Override auth handler method to write your own authentication method. Default algorithms used is RS256. a jwt Token istance is set as class atribute so you can use it by self.dcd.
|
|
285
|
+
|
|
180
286
|
```Python
|
|
181
287
|
from ninja_aio.auth import AsyncJWTBearer
|
|
182
288
|
from django.conf import settings
|
|
@@ -196,8 +302,11 @@ class CustomJWTBearer(AsyncJWTBearer):
|
|
|
196
302
|
return None
|
|
197
303
|
return request.user
|
|
198
304
|
```
|
|
305
|
+
|
|
199
306
|
- Then add it to views.
|
|
307
|
+
|
|
200
308
|
```Python
|
|
309
|
+
# views.py
|
|
201
310
|
from ninja import NinjAPI, Schema
|
|
202
311
|
from ninja_aio.views import APIViewSet, APIView
|
|
203
312
|
from ninja_aio.parsers import ORJSONParser
|
|
@@ -230,7 +339,7 @@ class SumView(APIView):
|
|
|
230
339
|
auths = CustomJWTBearer()
|
|
231
340
|
|
|
232
341
|
def views(self):
|
|
233
|
-
@self.router.post(
|
|
342
|
+
@self.router.post("/", response={200: ExampleSchemaOut}, auth=self.auths)
|
|
234
343
|
async def sum(request: HttpRequest, data: ExampleSchemaIn):
|
|
235
344
|
return 200, {sum: data.n1 + data.n2}
|
|
236
345
|
|
|
@@ -239,5 +348,19 @@ FooAPI().add_views_to_route()
|
|
|
239
348
|
SumView().add_views_to_route()
|
|
240
349
|
```
|
|
241
350
|
|
|
351
|
+
## 📝 Pagination
|
|
352
|
+
|
|
353
|
+
- By default APIViewSet list view uses Django Ninja built-in AsyncPagination class "PageNumberPagination". You can customize and assign it to APIViewSet class. To make your custom pagination consult **<a href="https://django-ninja.dev/guides/response/pagination/#async-pagination">Django Ninja pagination documentation</a>**.
|
|
354
|
+
|
|
355
|
+
```Python
|
|
356
|
+
# views.py
|
|
357
|
+
|
|
358
|
+
class FooAPI(APIViewSet):
|
|
359
|
+
model = Foo
|
|
360
|
+
api = api
|
|
361
|
+
pagination_class = CustomPaginationClass
|
|
362
|
+
|
|
363
|
+
```
|
|
364
|
+
|
|
242
365
|
## 📌 Notes
|
|
243
366
|
- Feel free to contribute and improve the program. 🛠️
|
|
@@ -87,20 +87,24 @@ class ModelSerializer(models.Model):
|
|
|
87
87
|
for f in cls.ReadSerializer.fields:
|
|
88
88
|
field_obj = getattr(cls, f)
|
|
89
89
|
if isinstance(field_obj, ReverseManyToOneDescriptor):
|
|
90
|
-
reverse_rels.append(field_obj.field.
|
|
90
|
+
reverse_rels.append(field_obj.field._related_name)
|
|
91
91
|
if isinstance(field_obj, ReverseOneToOneDescriptor):
|
|
92
|
-
reverse_rels.append(
|
|
93
|
-
list(field_obj.__dict__.values())[0].__dict__.get("related_name")
|
|
94
|
-
)
|
|
92
|
+
reverse_rels.append(list(field_obj[0].related_name))
|
|
95
93
|
return reverse_rels
|
|
96
94
|
|
|
97
95
|
@classmethod
|
|
98
96
|
def get_reverse_relation_schema(
|
|
99
97
|
cls, obj: type["ModelSerializer"], rel_type: type[REL_TYPES], field: str
|
|
100
98
|
):
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
99
|
+
cls_f = []
|
|
100
|
+
for rel_f in obj.ReadSerializer.fields:
|
|
101
|
+
rel_f_obj = getattr(obj, rel_f).field
|
|
102
|
+
if (
|
|
103
|
+
isinstance(rel_f_obj, (models.ForeignKey, models.OneToOneField))
|
|
104
|
+
and rel_f_obj.related_model == cls
|
|
105
|
+
):
|
|
106
|
+
cls_f.append(rel_f)
|
|
107
|
+
obj.ReadSerializer.fields.remove(rel_f)
|
|
104
108
|
break
|
|
105
109
|
rel_schema = obj.generate_read_s(depth=0)
|
|
106
110
|
if rel_type == "many":
|
|
@@ -110,7 +114,8 @@ class ModelSerializer(models.Model):
|
|
|
110
114
|
rel_schema | None,
|
|
111
115
|
None,
|
|
112
116
|
)
|
|
113
|
-
|
|
117
|
+
if len(cls_f) > 0:
|
|
118
|
+
obj.ReadSerializer.fields.append(*cls_f)
|
|
114
119
|
return rel_data
|
|
115
120
|
|
|
116
121
|
@classmethod
|
|
@@ -120,14 +125,12 @@ class ModelSerializer(models.Model):
|
|
|
120
125
|
for f in cls.ReadSerializer.fields:
|
|
121
126
|
field_obj = getattr(cls, f)
|
|
122
127
|
if isinstance(field_obj, ReverseManyToOneDescriptor):
|
|
123
|
-
rel_obj: ModelSerializer = field_obj.field.
|
|
128
|
+
rel_obj: ModelSerializer = field_obj.field.model
|
|
124
129
|
rel_data = cls.get_reverse_relation_schema(rel_obj, "many", f)
|
|
125
130
|
reverse_rels.append(rel_data)
|
|
126
131
|
continue
|
|
127
132
|
if isinstance(field_obj, ReverseOneToOneDescriptor):
|
|
128
|
-
rel_obj: ModelSerializer = list(field_obj
|
|
129
|
-
0
|
|
130
|
-
].__dict__.get("related_model")
|
|
133
|
+
rel_obj: ModelSerializer = list(field_obj)[0].related_model
|
|
131
134
|
rel_data = cls.get_reverse_relation_schema(rel_obj, "one", f)
|
|
132
135
|
reverse_rels.append(rel_data)
|
|
133
136
|
continue
|
|
@@ -149,9 +152,10 @@ class ModelSerializer(models.Model):
|
|
|
149
152
|
continue
|
|
150
153
|
field_obj = getattr(cls, k).field
|
|
151
154
|
if isinstance(field_obj, models.BinaryField):
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
try:
|
|
156
|
+
payload |= {k: base64.b64decode(v)}
|
|
157
|
+
except Exception as exc:
|
|
158
|
+
raise SerializeError({k: ". ".join(exc.args)}, 400)
|
|
155
159
|
if isinstance(field_obj, models.ForeignKey):
|
|
156
160
|
try:
|
|
157
161
|
rel: ModelSerializer = await field_obj.related_model.get_object(
|
|
@@ -204,18 +208,6 @@ class ModelSerializer(models.Model):
|
|
|
204
208
|
return None
|
|
205
209
|
return customs
|
|
206
210
|
|
|
207
|
-
@classmethod
|
|
208
|
-
def get_optional_fields(cls, s_type: type[S_TYPES]) -> list[str] | None:
|
|
209
|
-
try:
|
|
210
|
-
match s_type:
|
|
211
|
-
case "create":
|
|
212
|
-
optionals = cls.CreateSerializer.optionals
|
|
213
|
-
case "update":
|
|
214
|
-
optionals = cls.UpdateSerializer.optionals
|
|
215
|
-
except AttributeError:
|
|
216
|
-
return None
|
|
217
|
-
return optionals
|
|
218
|
-
|
|
219
211
|
@classmethod
|
|
220
212
|
def generate_read_s(cls, depth: int = 1) -> Schema:
|
|
221
213
|
fields, reverse_rels = cls.get_schema_out_data()
|
|
@@ -234,7 +226,6 @@ class ModelSerializer(models.Model):
|
|
|
234
226
|
model=cls,
|
|
235
227
|
name=f"{cls._meta.model_name}SchemaIn",
|
|
236
228
|
fields=cls.CreateSerializer.fields,
|
|
237
|
-
optional_fields=cls.get_optional_fields("create"),
|
|
238
229
|
custom_fields=cls.get_custom_fields("create"),
|
|
239
230
|
)
|
|
240
231
|
|
|
@@ -244,7 +235,6 @@ class ModelSerializer(models.Model):
|
|
|
244
235
|
model=cls,
|
|
245
236
|
name=f"{cls._meta.model_name}SchemaPatch",
|
|
246
237
|
fields=cls.UpdateSerializer.fields,
|
|
247
|
-
optional_fields=cls.get_optional_fields("update"),
|
|
248
238
|
custom_fields=cls.get_custom_fields("update"),
|
|
249
239
|
)
|
|
250
240
|
|
|
@@ -8,9 +8,11 @@ from ninja.renderers import BaseRenderer
|
|
|
8
8
|
class ORJSONRenderer(BaseRenderer):
|
|
9
9
|
media_type = "application/json"
|
|
10
10
|
|
|
11
|
-
def render(self, request: HttpRequest, data: dict
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
def render(self, request: HttpRequest, data: dict, *, response_status):
|
|
12
|
+
old_d = data
|
|
13
|
+
for k, v in old_d.items():
|
|
14
|
+
if isinstance(v, list):
|
|
15
|
+
data |= {k: self.render_list(v)}
|
|
14
16
|
return orjson.dumps(self.render_dict(data))
|
|
15
17
|
|
|
16
18
|
@classmethod
|
|
@@ -33,11 +35,9 @@ class ORJSONRenderer(BaseRenderer):
|
|
|
33
35
|
v |= {k_rel: base64.b64encode(v_rel).decode()}
|
|
34
36
|
data |= {k: v}
|
|
35
37
|
if isinstance(v, list):
|
|
36
|
-
index_rel
|
|
37
|
-
for f_rel in v:
|
|
38
|
+
for index_rel, f_rel in enumerate(v):
|
|
38
39
|
for k_rel, v_rel in f_rel.items():
|
|
39
40
|
if isinstance(v_rel, bytes):
|
|
40
41
|
v[index_rel] |= {k_rel: base64.b64encode(v_rel).decode()}
|
|
41
|
-
index_rel += 1
|
|
42
42
|
data |= {k: v}
|
|
43
43
|
return data
|
|
@@ -2,6 +2,7 @@ from typing import List
|
|
|
2
2
|
|
|
3
3
|
from ninja import NinjaAPI, Router
|
|
4
4
|
from ninja.constants import NOT_SET
|
|
5
|
+
from ninja.pagination import paginate, AsyncPaginationBase, PageNumberPagination
|
|
5
6
|
from django.http import HttpRequest
|
|
6
7
|
|
|
7
8
|
from .models import ModelSerializer
|
|
@@ -67,6 +68,7 @@ class APIViewSet:
|
|
|
67
68
|
model: ModelSerializer
|
|
68
69
|
api: NinjaAPI
|
|
69
70
|
auths: list | None = NOT_SET
|
|
71
|
+
pagination_class: type[AsyncPaginationBase] = PageNumberPagination
|
|
70
72
|
|
|
71
73
|
def __init__(self) -> None:
|
|
72
74
|
self.router = Router(tags=[self.model._meta.model_name.capitalize()])
|
|
@@ -97,6 +99,7 @@ class APIViewSet:
|
|
|
97
99
|
self.error_codes: GenericMessageSchema,
|
|
98
100
|
},
|
|
99
101
|
)
|
|
102
|
+
@paginate(self.pagination_class)
|
|
100
103
|
async def list(request: HttpRequest):
|
|
101
104
|
qs = await self.model.queryset_request(request)
|
|
102
105
|
rels = self.model.get_reverse_relations()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|