django-ninja-aio-crud 0.4.0__tar.gz → 0.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.
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/PKG-INFO +48 -11
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/README.md +47 -10
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/models.py +75 -50
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/types.py +4 -3
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/views.py +50 -20
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/ninja_aio/schemas.py +0 -0
- {django_ninja_aio_crud-0.4.0 → django_ninja_aio_crud-0.5.0}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-ninja-aio-crud
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.0
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -153,6 +153,32 @@ class Foo(ModelSerializer):
|
|
|
153
153
|
optionals = [[("bar", str), ("active", bool)]
|
|
154
154
|
```
|
|
155
155
|
|
|
156
|
+
- Instead of declaring your fields maybe you want to exclude some of them. Declaring "excludes" attribute into serializers will exclude the given fields. (You can declare only one between "fields" and "excludes").
|
|
157
|
+
|
|
158
|
+
```python
|
|
159
|
+
# models.py
|
|
160
|
+
from django.db import models
|
|
161
|
+
from ninja_aio.models import ModelSerializer
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Foo(ModelSerializer):
|
|
165
|
+
name = models.CharField(max_length=30)
|
|
166
|
+
bar = models.CharField(max_length=30, default="")
|
|
167
|
+
active = models.BooleanField(default=False)
|
|
168
|
+
|
|
169
|
+
class ReadSerializer:
|
|
170
|
+
excludes = ["bar"]
|
|
171
|
+
|
|
172
|
+
class CreateSerializer:
|
|
173
|
+
fields = ["name"]
|
|
174
|
+
optionals = [("bar", str), ("active", bool)]
|
|
175
|
+
|
|
176
|
+
class UpdateSerializer:
|
|
177
|
+
excludes = ["id", "name"]
|
|
178
|
+
optionals = [[("bar", str), ("active", bool)]
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
|
|
156
182
|
### APIViewSet
|
|
157
183
|
|
|
158
184
|
- View class used to automatically generate CRUD views. in your views.py import APIViewSet and define your api using NinjaAIO class. NinjaAIO class uses built-in parser and renderer which use orjson for data serialization.
|
|
@@ -161,8 +187,6 @@ class Foo(ModelSerializer):
|
|
|
161
187
|
# views.py
|
|
162
188
|
from ninja_aio import NinjaAIO
|
|
163
189
|
from ninja_aio.views import APIViewSet
|
|
164
|
-
from ninja_aio.parsers import ORJSONParser
|
|
165
|
-
from ninja_aio.renders import ORJSONRender
|
|
166
190
|
|
|
167
191
|
from .models import Foo
|
|
168
192
|
|
|
@@ -184,8 +208,6 @@ FooAPI().add_views_to_route()
|
|
|
184
208
|
from ninja import Schema
|
|
185
209
|
from ninja_aio import NinjaAIO
|
|
186
210
|
from ninja_aio.views import APIViewSet
|
|
187
|
-
from ninja_aio.parsers import ORJSONParser
|
|
188
|
-
from ninja_aio.renders import ORJSONRender
|
|
189
211
|
|
|
190
212
|
from .models import Foo
|
|
191
213
|
|
|
@@ -211,6 +233,27 @@ class FooAPI(APIViewSet):
|
|
|
211
233
|
return 200, {sum: data.n1 + data.n2}
|
|
212
234
|
|
|
213
235
|
|
|
236
|
+
FooAPI().add_views_to_route()
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- You can also choose to disable any operation from crud by declaring "disbale" attribute. You can give "all" to disable every crud operation except for additional views.
|
|
240
|
+
|
|
241
|
+
```python
|
|
242
|
+
# views.py
|
|
243
|
+
from ninja_aio import NinjaAIO
|
|
244
|
+
from ninja_aio.views import APIViewSet
|
|
245
|
+
|
|
246
|
+
from .models import Foo
|
|
247
|
+
|
|
248
|
+
api = NinjaAIO()
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
class FooAPI(APIViewSet):
|
|
252
|
+
model = Foo
|
|
253
|
+
api = api
|
|
254
|
+
disable = ["retrieve", "update"]
|
|
255
|
+
|
|
256
|
+
|
|
214
257
|
FooAPI().add_views_to_route()
|
|
215
258
|
```
|
|
216
259
|
|
|
@@ -223,8 +266,6 @@ FooAPI().add_views_to_route()
|
|
|
223
266
|
from ninja import Schema
|
|
224
267
|
from ninja_aio import NinjaAIO
|
|
225
268
|
from ninja_aio.views import APIView
|
|
226
|
-
from ninja_aio.parsers import ORJSONParser
|
|
227
|
-
from ninja_aio.renders import ORJSONRender
|
|
228
269
|
|
|
229
270
|
api = NinjaAIO()
|
|
230
271
|
|
|
@@ -294,8 +335,6 @@ class Foo(ModelSerializer):
|
|
|
294
335
|
# views.py
|
|
295
336
|
from ninja_aio import NinjaAIO
|
|
296
337
|
from ninja_aio.views import APIViewSet
|
|
297
|
-
from ninja_aio.parsers import ORJSONParser
|
|
298
|
-
from ninja_aio.renders import ORJSONRender
|
|
299
338
|
|
|
300
339
|
from .models import Foo, Bar
|
|
301
340
|
|
|
@@ -361,8 +400,6 @@ class CustomJWTBearer(AsyncJWTBearer):
|
|
|
361
400
|
from ninja import Schema
|
|
362
401
|
from ninja_aio import NinjaAIO
|
|
363
402
|
from ninja_aio.views import APIViewSet, APIView
|
|
364
|
-
from ninja_aio.parsers import ORJSONParser
|
|
365
|
-
from ninja_aio.renders import ORJSONRender
|
|
366
403
|
|
|
367
404
|
from .models import Foo
|
|
368
405
|
|
|
@@ -124,6 +124,32 @@ class Foo(ModelSerializer):
|
|
|
124
124
|
optionals = [[("bar", str), ("active", bool)]
|
|
125
125
|
```
|
|
126
126
|
|
|
127
|
+
- Instead of declaring your fields maybe you want to exclude some of them. Declaring "excludes" attribute into serializers will exclude the given fields. (You can declare only one between "fields" and "excludes").
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# models.py
|
|
131
|
+
from django.db import models
|
|
132
|
+
from ninja_aio.models import ModelSerializer
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class Foo(ModelSerializer):
|
|
136
|
+
name = models.CharField(max_length=30)
|
|
137
|
+
bar = models.CharField(max_length=30, default="")
|
|
138
|
+
active = models.BooleanField(default=False)
|
|
139
|
+
|
|
140
|
+
class ReadSerializer:
|
|
141
|
+
excludes = ["bar"]
|
|
142
|
+
|
|
143
|
+
class CreateSerializer:
|
|
144
|
+
fields = ["name"]
|
|
145
|
+
optionals = [("bar", str), ("active", bool)]
|
|
146
|
+
|
|
147
|
+
class UpdateSerializer:
|
|
148
|
+
excludes = ["id", "name"]
|
|
149
|
+
optionals = [[("bar", str), ("active", bool)]
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
|
|
127
153
|
### APIViewSet
|
|
128
154
|
|
|
129
155
|
- View class used to automatically generate CRUD views. in your views.py import APIViewSet and define your api using NinjaAIO class. NinjaAIO class uses built-in parser and renderer which use orjson for data serialization.
|
|
@@ -132,8 +158,6 @@ class Foo(ModelSerializer):
|
|
|
132
158
|
# views.py
|
|
133
159
|
from ninja_aio import NinjaAIO
|
|
134
160
|
from ninja_aio.views import APIViewSet
|
|
135
|
-
from ninja_aio.parsers import ORJSONParser
|
|
136
|
-
from ninja_aio.renders import ORJSONRender
|
|
137
161
|
|
|
138
162
|
from .models import Foo
|
|
139
163
|
|
|
@@ -155,8 +179,6 @@ FooAPI().add_views_to_route()
|
|
|
155
179
|
from ninja import Schema
|
|
156
180
|
from ninja_aio import NinjaAIO
|
|
157
181
|
from ninja_aio.views import APIViewSet
|
|
158
|
-
from ninja_aio.parsers import ORJSONParser
|
|
159
|
-
from ninja_aio.renders import ORJSONRender
|
|
160
182
|
|
|
161
183
|
from .models import Foo
|
|
162
184
|
|
|
@@ -182,6 +204,27 @@ class FooAPI(APIViewSet):
|
|
|
182
204
|
return 200, {sum: data.n1 + data.n2}
|
|
183
205
|
|
|
184
206
|
|
|
207
|
+
FooAPI().add_views_to_route()
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
- You can also choose to disable any operation from crud by declaring "disbale" attribute. You can give "all" to disable every crud operation except for additional views.
|
|
211
|
+
|
|
212
|
+
```python
|
|
213
|
+
# views.py
|
|
214
|
+
from ninja_aio import NinjaAIO
|
|
215
|
+
from ninja_aio.views import APIViewSet
|
|
216
|
+
|
|
217
|
+
from .models import Foo
|
|
218
|
+
|
|
219
|
+
api = NinjaAIO()
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
class FooAPI(APIViewSet):
|
|
223
|
+
model = Foo
|
|
224
|
+
api = api
|
|
225
|
+
disable = ["retrieve", "update"]
|
|
226
|
+
|
|
227
|
+
|
|
185
228
|
FooAPI().add_views_to_route()
|
|
186
229
|
```
|
|
187
230
|
|
|
@@ -194,8 +237,6 @@ FooAPI().add_views_to_route()
|
|
|
194
237
|
from ninja import Schema
|
|
195
238
|
from ninja_aio import NinjaAIO
|
|
196
239
|
from ninja_aio.views import APIView
|
|
197
|
-
from ninja_aio.parsers import ORJSONParser
|
|
198
|
-
from ninja_aio.renders import ORJSONRender
|
|
199
240
|
|
|
200
241
|
api = NinjaAIO()
|
|
201
242
|
|
|
@@ -265,8 +306,6 @@ class Foo(ModelSerializer):
|
|
|
265
306
|
# views.py
|
|
266
307
|
from ninja_aio import NinjaAIO
|
|
267
308
|
from ninja_aio.views import APIViewSet
|
|
268
|
-
from ninja_aio.parsers import ORJSONParser
|
|
269
|
-
from ninja_aio.renders import ORJSONRender
|
|
270
309
|
|
|
271
310
|
from .models import Foo, Bar
|
|
272
311
|
|
|
@@ -332,8 +371,6 @@ class CustomJWTBearer(AsyncJWTBearer):
|
|
|
332
371
|
from ninja import Schema
|
|
333
372
|
from ninja_aio import NinjaAIO
|
|
334
373
|
from ninja_aio.views import APIViewSet, APIView
|
|
335
|
-
from ninja_aio.parsers import ORJSONParser
|
|
336
|
-
from ninja_aio.renders import ORJSONRender
|
|
337
374
|
|
|
338
375
|
from .models import Foo
|
|
339
376
|
|
|
@@ -5,7 +5,7 @@ from ninja.schema import Schema
|
|
|
5
5
|
from ninja.orm import create_schema
|
|
6
6
|
|
|
7
7
|
from django.db import models
|
|
8
|
-
from django.http import
|
|
8
|
+
from django.http import HttpRequest
|
|
9
9
|
from django.core.exceptions import ObjectDoesNotExist
|
|
10
10
|
from django.db.models.fields.related_descriptors import (
|
|
11
11
|
ReverseManyToOneDescriptor,
|
|
@@ -14,7 +14,7 @@ from django.db.models.fields.related_descriptors import (
|
|
|
14
14
|
)
|
|
15
15
|
|
|
16
16
|
from .exceptions import SerializeError
|
|
17
|
-
from .types import S_TYPES, REL_TYPES, F_TYPES, ModelSerializerMeta
|
|
17
|
+
from .types import S_TYPES, REL_TYPES, F_TYPES, SCHEMA_TYPES, ModelSerializerMeta
|
|
18
18
|
|
|
19
19
|
|
|
20
20
|
class ModelUtil:
|
|
@@ -24,12 +24,15 @@ class ModelUtil:
|
|
|
24
24
|
@property
|
|
25
25
|
def serializable_fields(self):
|
|
26
26
|
if isinstance(self.model, ModelSerializerMeta):
|
|
27
|
-
return self.model.
|
|
27
|
+
return self.model.get_fields("read")
|
|
28
28
|
return [field.name for field in self.model._meta.get_fields()]
|
|
29
29
|
|
|
30
30
|
def verbose_name_path_resolver(self) -> str:
|
|
31
31
|
return "-".join(self.model._meta.verbose_name_plural.split(" "))
|
|
32
32
|
|
|
33
|
+
def verbose_name_view_resolver(self) -> str:
|
|
34
|
+
return self.model._meta.verbose_name_plural.replace(" ", "")
|
|
35
|
+
|
|
33
36
|
async def get_object(self, request: HttpRequest, pk: int | str):
|
|
34
37
|
q = {self.model._meta.pk.attname: pk}
|
|
35
38
|
obj_qs = self.model.objects.select_related()
|
|
@@ -121,7 +124,7 @@ class ModelUtil:
|
|
|
121
124
|
if isinstance(self.model, ModelSerializerMeta):
|
|
122
125
|
await obj.custom_actions(customs)
|
|
123
126
|
await obj.post_create()
|
|
124
|
-
return
|
|
127
|
+
return await self.read_s(request, obj, obj_schema)
|
|
125
128
|
|
|
126
129
|
async def read_s(
|
|
127
130
|
self,
|
|
@@ -150,7 +153,7 @@ class ModelUtil:
|
|
|
150
153
|
async def delete_s(self, request: HttpRequest, pk: int | str):
|
|
151
154
|
obj = await self.get_object(request, pk)
|
|
152
155
|
await obj.adelete()
|
|
153
|
-
return
|
|
156
|
+
return None
|
|
154
157
|
|
|
155
158
|
|
|
156
159
|
class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
@@ -160,15 +163,18 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
160
163
|
class CreateSerializer:
|
|
161
164
|
fields: list[str] = []
|
|
162
165
|
customs: list[tuple[str, type, Any]] = []
|
|
163
|
-
optionals: list[str] = []
|
|
166
|
+
optionals: list[tuple[str, type]] = []
|
|
167
|
+
excludes: list[str] = []
|
|
164
168
|
|
|
165
169
|
class ReadSerializer:
|
|
166
170
|
fields: list[str] = []
|
|
171
|
+
excludes: list[str] = []
|
|
167
172
|
|
|
168
173
|
class UpdateSerializer:
|
|
169
174
|
fields: list[str] = []
|
|
170
175
|
customs: list[tuple[str, type, Any]] = []
|
|
171
|
-
optionals: list[str] = []
|
|
176
|
+
optionals: list[tuple[str, type]] = []
|
|
177
|
+
excludes: list[str] = []
|
|
172
178
|
|
|
173
179
|
@property
|
|
174
180
|
def has_custom_fields_create(self):
|
|
@@ -195,24 +201,61 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
195
201
|
return self.has_optional_fields_create or self.has_optional_fields_update
|
|
196
202
|
|
|
197
203
|
@classmethod
|
|
198
|
-
def
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
return []
|
|
204
|
+
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
205
|
+
match s_type:
|
|
206
|
+
case "create":
|
|
207
|
+
fields = getattr(cls.CreateSerializer, f_type, [])
|
|
208
|
+
case "update":
|
|
209
|
+
fields = getattr(cls.UpdateSerializer, f_type, [])
|
|
210
|
+
case "read":
|
|
211
|
+
fields = getattr(cls.ReadSerializer, f_type, [])
|
|
207
212
|
return fields
|
|
208
213
|
|
|
209
214
|
@classmethod
|
|
210
215
|
def _is_special_field(
|
|
211
216
|
cls, s_type: type[S_TYPES], field: str, f_type: type[F_TYPES]
|
|
212
217
|
):
|
|
213
|
-
special_fields = cls.
|
|
218
|
+
special_fields = cls._get_fields(s_type, f_type)
|
|
214
219
|
return any(field in special_f for special_f in special_fields)
|
|
215
220
|
|
|
221
|
+
@classmethod
|
|
222
|
+
def _generate_model_schema(
|
|
223
|
+
cls,
|
|
224
|
+
schema_type: type[SCHEMA_TYPES],
|
|
225
|
+
depth: int = None,
|
|
226
|
+
) -> Schema:
|
|
227
|
+
match schema_type:
|
|
228
|
+
case "In":
|
|
229
|
+
s_type = "create"
|
|
230
|
+
case "Patch":
|
|
231
|
+
s_type = "update"
|
|
232
|
+
case "Out":
|
|
233
|
+
fields, reverse_rels, excludes = cls.get_schema_out_data()
|
|
234
|
+
if not fields and not reverse_rels and not excludes:
|
|
235
|
+
return None
|
|
236
|
+
return create_schema(
|
|
237
|
+
model=cls,
|
|
238
|
+
name=f"{cls._meta.model_name}SchemaOut",
|
|
239
|
+
depth=depth,
|
|
240
|
+
fields=fields,
|
|
241
|
+
custom_fields=reverse_rels,
|
|
242
|
+
exclude=excludes,
|
|
243
|
+
)
|
|
244
|
+
fields = cls.get_fields(s_type)
|
|
245
|
+
customs = cls.get_custom_fields(s_type) + cls.get_optional_fields(s_type)
|
|
246
|
+
excludes = cls.get_excluded_fields(s_type)
|
|
247
|
+
return (
|
|
248
|
+
create_schema(
|
|
249
|
+
model=cls,
|
|
250
|
+
name=f"{cls._meta.model_name}Schema{schema_type}",
|
|
251
|
+
fields=fields,
|
|
252
|
+
custom_fields=customs,
|
|
253
|
+
exclude=excludes,
|
|
254
|
+
)
|
|
255
|
+
if fields or customs or excludes
|
|
256
|
+
else None
|
|
257
|
+
)
|
|
258
|
+
|
|
216
259
|
@classmethod
|
|
217
260
|
def verbose_name_path_resolver(cls) -> str:
|
|
218
261
|
return "-".join(cls._meta.verbose_name_plural.split(" "))
|
|
@@ -292,7 +335,7 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
292
335
|
def get_schema_out_data(cls):
|
|
293
336
|
fields = []
|
|
294
337
|
reverse_rels = []
|
|
295
|
-
for f in cls.
|
|
338
|
+
for f in cls.get_fields("read"):
|
|
296
339
|
field_obj = getattr(cls, f)
|
|
297
340
|
if isinstance(field_obj, ManyToManyDescriptor):
|
|
298
341
|
rel_obj: ModelSerializer = field_obj.field.related_model
|
|
@@ -312,7 +355,7 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
312
355
|
reverse_rels.append(rel_data)
|
|
313
356
|
continue
|
|
314
357
|
fields.append(f)
|
|
315
|
-
return fields, reverse_rels
|
|
358
|
+
return fields, reverse_rels, cls.get_excluded_fields("read")
|
|
316
359
|
|
|
317
360
|
@classmethod
|
|
318
361
|
def is_custom(cls, field: str):
|
|
@@ -328,49 +371,31 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
328
371
|
|
|
329
372
|
@classmethod
|
|
330
373
|
def get_custom_fields(cls, s_type: type[S_TYPES]):
|
|
331
|
-
return cls.
|
|
374
|
+
return cls._get_fields(s_type, "customs")
|
|
332
375
|
|
|
333
376
|
@classmethod
|
|
334
377
|
def get_optional_fields(cls, s_type: type[S_TYPES]):
|
|
335
378
|
return [
|
|
336
379
|
(field, field_type, None)
|
|
337
|
-
for field, field_type in cls.
|
|
380
|
+
for field, field_type in cls._get_fields(s_type, "optionals")
|
|
338
381
|
]
|
|
382
|
+
|
|
383
|
+
@classmethod
|
|
384
|
+
def get_excluded_fields(cls, s_type: type[S_TYPES]):
|
|
385
|
+
return cls._get_fields(s_type, "excludes")
|
|
386
|
+
|
|
387
|
+
@classmethod
|
|
388
|
+
def get_fields(cls, s_type: type[S_TYPES]):
|
|
389
|
+
return cls._get_fields(s_type, "fields")
|
|
339
390
|
|
|
340
391
|
@classmethod
|
|
341
392
|
def generate_read_s(cls, depth: int = 1) -> Schema:
|
|
342
|
-
|
|
343
|
-
customs = [custom for custom in reverse_rels]
|
|
344
|
-
return create_schema(
|
|
345
|
-
model=cls,
|
|
346
|
-
name=f"{cls._meta.model_name}SchemaOut",
|
|
347
|
-
depth=depth,
|
|
348
|
-
fields=fields,
|
|
349
|
-
custom_fields=customs,
|
|
350
|
-
)
|
|
393
|
+
return cls._generate_model_schema("Out", depth)
|
|
351
394
|
|
|
352
395
|
@classmethod
|
|
353
396
|
def generate_create_s(cls) -> Schema:
|
|
354
|
-
|
|
355
|
-
field[0] for field in cls.get_optional_fields("create")
|
|
356
|
-
]
|
|
357
|
-
customs = cls.get_custom_fields("create") + cls.get_optional_fields("create")
|
|
358
|
-
return create_schema(
|
|
359
|
-
model=cls,
|
|
360
|
-
name=f"{cls._meta.model_name}SchemaIn",
|
|
361
|
-
fields=fields,
|
|
362
|
-
custom_fields=customs,
|
|
363
|
-
)
|
|
397
|
+
return cls._generate_model_schema("In")
|
|
364
398
|
|
|
365
399
|
@classmethod
|
|
366
400
|
def generate_update_s(cls) -> Schema:
|
|
367
|
-
|
|
368
|
-
field[0] for field in cls.get_optional_fields("update")
|
|
369
|
-
]
|
|
370
|
-
customs = cls.get_custom_fields("update") + cls.get_optional_fields("update")
|
|
371
|
-
return create_schema(
|
|
372
|
-
model=cls,
|
|
373
|
-
name=f"{cls._meta.model_name}SchemaPatch",
|
|
374
|
-
fields=fields,
|
|
375
|
-
custom_fields=customs,
|
|
376
|
-
)
|
|
401
|
+
return cls._generate_model_schema("Patch")
|
|
@@ -2,10 +2,11 @@ from typing import Literal
|
|
|
2
2
|
|
|
3
3
|
from django.db.models import Model
|
|
4
4
|
|
|
5
|
-
S_TYPES = Literal["create", "update"]
|
|
5
|
+
S_TYPES = Literal["read", "create", "update"]
|
|
6
6
|
REL_TYPES = Literal["many", "one"]
|
|
7
|
-
F_TYPES = Literal["customs", "optionals"]
|
|
8
|
-
|
|
7
|
+
F_TYPES = Literal["fields", "customs", "optionals", "excludes"]
|
|
8
|
+
SCHEMA_TYPES = Literal["In", "Out", "Patch"]
|
|
9
|
+
VIEW_TYPES = Literal["list", "retrieve", "create", "update", "delete", "all"]
|
|
9
10
|
|
|
10
11
|
class ModelSerializerType(type):
|
|
11
12
|
def __repr__(self):
|
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from ninja import NinjaAPI, Router, Schema
|
|
3
|
+
from ninja import NinjaAPI, Router, Schema, Path
|
|
4
4
|
from ninja.constants import NOT_SET
|
|
5
5
|
from ninja.pagination import paginate, AsyncPaginationBase, PageNumberPagination
|
|
6
6
|
from django.http import HttpRequest
|
|
7
7
|
from django.db.models import Model
|
|
8
|
+
from pydantic import create_model
|
|
8
9
|
|
|
9
10
|
from .models import ModelSerializer, ModelUtil
|
|
10
11
|
from .schemas import GenericMessageSchema
|
|
11
|
-
from .types import ModelSerializerMeta
|
|
12
|
+
from .types import ModelSerializerMeta, VIEW_TYPES
|
|
12
13
|
|
|
13
14
|
ERROR_CODES = frozenset({400, 401, 404, 428})
|
|
14
15
|
|
|
@@ -55,7 +56,6 @@ class APIView:
|
|
|
55
56
|
async def some_method(request, *args, **kwargs):
|
|
56
57
|
pass
|
|
57
58
|
"""
|
|
58
|
-
pass
|
|
59
59
|
|
|
60
60
|
def add_views(self):
|
|
61
61
|
self.views()
|
|
@@ -73,23 +73,45 @@ class APIViewSet:
|
|
|
73
73
|
schema_update: Schema | None = None
|
|
74
74
|
auths: list | None = NOT_SET
|
|
75
75
|
pagination_class: type[AsyncPaginationBase] = PageNumberPagination
|
|
76
|
+
disable: list[type[VIEW_TYPES]] = []
|
|
76
77
|
|
|
77
78
|
def __init__(self) -> None:
|
|
78
79
|
self.router = Router(tags=[self.model._meta.model_name.capitalize()])
|
|
79
80
|
self.path = "/"
|
|
80
|
-
self.path_retrieve = f"{self.model._meta.pk.attname}/"
|
|
81
|
+
self.path_retrieve = f"{{{self.model._meta.pk.attname}}}/"
|
|
81
82
|
self.error_codes = ERROR_CODES
|
|
82
83
|
self.model_util = ModelUtil(self.model)
|
|
83
|
-
self.schema_out, self.
|
|
84
|
+
self.schema_out, self.schema_in, self.schema_update = self.get_schemas()
|
|
85
|
+
self.path_schema = self._create_path_schema()
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def _crud_views(self):
|
|
89
|
+
"""
|
|
90
|
+
key: view type (create, list, retrieve, update, delete or all)
|
|
91
|
+
value: tuple with schema and view method
|
|
92
|
+
"""
|
|
93
|
+
return {
|
|
94
|
+
"create": (self.schema_in, self.create_view),
|
|
95
|
+
"list": (self.schema_out, self.list_view),
|
|
96
|
+
"retrieve": (self.schema_out, self.retrieve_view),
|
|
97
|
+
"update": (self.schema_update, self.update_view),
|
|
98
|
+
"delete": (None, self.delete_view),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
def _create_path_schema(self):
|
|
102
|
+
fields = {
|
|
103
|
+
self.model._meta.pk.attname: (str | int , ...),
|
|
104
|
+
}
|
|
105
|
+
return create_model(f"{self.model._meta.model_name}PathSchema", **fields)
|
|
84
106
|
|
|
85
107
|
def get_schemas(self):
|
|
86
108
|
if isinstance(self.model, ModelSerializerMeta):
|
|
87
109
|
return (
|
|
88
110
|
self.model.generate_read_s(),
|
|
89
|
-
self.model.generate_update_s(),
|
|
90
111
|
self.model.generate_create_s(),
|
|
112
|
+
self.model.generate_update_s(),
|
|
91
113
|
)
|
|
92
|
-
return self.schema_out, self.
|
|
114
|
+
return self.schema_out, self.schema_in, self.schema_update
|
|
93
115
|
|
|
94
116
|
def create_view(self):
|
|
95
117
|
@self.router.post(
|
|
@@ -98,9 +120,10 @@ class APIViewSet:
|
|
|
98
120
|
response={201: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
99
121
|
)
|
|
100
122
|
async def create(request: HttpRequest, data: self.schema_in):
|
|
101
|
-
return await self.model_util.create_s(request, data, self.schema_out)
|
|
123
|
+
return 201, await self.model_util.create_s(request, data, self.schema_out)
|
|
102
124
|
|
|
103
125
|
create.__name__ = f"create_{self.model._meta.model_name}"
|
|
126
|
+
return create
|
|
104
127
|
|
|
105
128
|
def list_view(self):
|
|
106
129
|
@self.router.get(
|
|
@@ -117,7 +140,6 @@ class APIViewSet:
|
|
|
117
140
|
if isinstance(self.model, ModelSerializerMeta):
|
|
118
141
|
qs = await self.model.queryset_request(request)
|
|
119
142
|
rels = self.model_util.get_reverse_relations()
|
|
120
|
-
print(rels)
|
|
121
143
|
if len(rels) > 0:
|
|
122
144
|
qs = qs.prefetch_related(*rels)
|
|
123
145
|
objs = [
|
|
@@ -126,7 +148,8 @@ class APIViewSet:
|
|
|
126
148
|
]
|
|
127
149
|
return objs
|
|
128
150
|
|
|
129
|
-
list.__name__ = f"list_{self.
|
|
151
|
+
list.__name__ = f"list_{self.model_util.verbose_name_view_resolver()}"
|
|
152
|
+
return list
|
|
130
153
|
|
|
131
154
|
def retrieve_view(self):
|
|
132
155
|
@self.router.get(
|
|
@@ -134,11 +157,12 @@ class APIViewSet:
|
|
|
134
157
|
auth=self.auths,
|
|
135
158
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
136
159
|
)
|
|
137
|
-
async def retrieve(request: HttpRequest, pk:
|
|
160
|
+
async def retrieve(request: HttpRequest, pk: Path[self.path_schema]):
|
|
138
161
|
obj = await self.model_util.get_object(request, pk)
|
|
139
162
|
return await self.model_util.read_s(request, obj, self.schema_out)
|
|
140
163
|
|
|
141
164
|
retrieve.__name__ = f"retrieve_{self.model._meta.model_name}"
|
|
165
|
+
return retrieve
|
|
142
166
|
|
|
143
167
|
def update_view(self):
|
|
144
168
|
@self.router.patch(
|
|
@@ -146,10 +170,11 @@ class APIViewSet:
|
|
|
146
170
|
auth=self.auths,
|
|
147
171
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
148
172
|
)
|
|
149
|
-
async def update(request: HttpRequest, data: self.schema_update, pk:
|
|
173
|
+
async def update(request: HttpRequest, data: self.schema_update, pk: Path[self.path_schema]):
|
|
150
174
|
return await self.model_util.update_s(request, data, pk, self.schema_out)
|
|
151
175
|
|
|
152
176
|
update.__name__ = f"update_{self.model._meta.model_name}"
|
|
177
|
+
return update
|
|
153
178
|
|
|
154
179
|
def delete_view(self):
|
|
155
180
|
@self.router.delete(
|
|
@@ -157,10 +182,11 @@ class APIViewSet:
|
|
|
157
182
|
auth=self.auths,
|
|
158
183
|
response={204: None, self.error_codes: GenericMessageSchema},
|
|
159
184
|
)
|
|
160
|
-
async def delete(request: HttpRequest, pk:
|
|
161
|
-
return await self.model_util.delete_s(request, pk)
|
|
185
|
+
async def delete(request: HttpRequest, pk: Path[self.path_schema]):
|
|
186
|
+
return 204, await self.model_util.delete_s(request, pk)
|
|
162
187
|
|
|
163
188
|
delete.__name__ = f"delete_{self.model._meta.model_name}"
|
|
189
|
+
return delete
|
|
164
190
|
|
|
165
191
|
def views(self):
|
|
166
192
|
"""
|
|
@@ -194,14 +220,18 @@ class APIViewSet:
|
|
|
194
220
|
async def some_method(request, *args, **kwargs):
|
|
195
221
|
pass
|
|
196
222
|
"""
|
|
197
|
-
pass
|
|
198
223
|
|
|
199
224
|
def add_views(self):
|
|
200
|
-
self.
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
self.
|
|
225
|
+
if "all" in self.disable:
|
|
226
|
+
self.views()
|
|
227
|
+
return self.router
|
|
228
|
+
|
|
229
|
+
for views_type, (schema, view) in self._crud_views.items():
|
|
230
|
+
if views_type not in self.disable and (
|
|
231
|
+
schema is not None or views_type == "delete"
|
|
232
|
+
):
|
|
233
|
+
view()
|
|
234
|
+
|
|
205
235
|
self.views()
|
|
206
236
|
return self.router
|
|
207
237
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|