django-ninja-aio-crud 0.6.0__py3-none-any.whl → 0.6.2__py3-none-any.whl
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.6.0.dist-info → django_ninja_aio_crud-0.6.2.dist-info}/METADATA +33 -4
- django_ninja_aio_crud-0.6.2.dist-info/RECORD +13 -0
- ninja_aio/__init__.py +1 -1
- ninja_aio/auth.py +3 -13
- ninja_aio/exceptions.py +24 -0
- ninja_aio/models.py +39 -21
- ninja_aio/views.py +13 -5
- django_ninja_aio_crud-0.6.0.dist-info/RECORD +0 -13
- {django_ninja_aio_crud-0.6.0.dist-info → django_ninja_aio_crud-0.6.2.dist-info}/WHEEL +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.3
|
|
2
2
|
Name: django-ninja-aio-crud
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -100,7 +100,7 @@ class Foo(ModelSerializer):
|
|
|
100
100
|
fields = ["name", "bar"]
|
|
101
101
|
```
|
|
102
102
|
|
|
103
|
-
- 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
|
|
103
|
+
- 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.
|
|
104
104
|
|
|
105
105
|
```python
|
|
106
106
|
# models.py
|
|
@@ -157,7 +157,7 @@ class Foo(ModelSerializer):
|
|
|
157
157
|
optionals = [("bar", str), ("active", bool)]
|
|
158
158
|
|
|
159
159
|
class UpdateSerializer:
|
|
160
|
-
optionals = [
|
|
160
|
+
optionals = [("bar", str), ("active", bool)]
|
|
161
161
|
```
|
|
162
162
|
|
|
163
163
|
- 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").
|
|
@@ -182,10 +182,39 @@ class Foo(ModelSerializer):
|
|
|
182
182
|
|
|
183
183
|
class UpdateSerializer:
|
|
184
184
|
excludes = ["id", "name"]
|
|
185
|
-
optionals = [
|
|
185
|
+
optionals = [("bar", str), ("active", bool)]
|
|
186
|
+
```
|
|
187
|
+
- If you want to add into ReadSerializer model properties you must define them as customs.
|
|
188
|
+
```python
|
|
189
|
+
# models.py
|
|
190
|
+
from django.db import models
|
|
191
|
+
from ninja_aio.models import ModelSerializer
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
class Foo(ModelSerializer):
|
|
195
|
+
name = models.CharField(max_length=30)
|
|
196
|
+
bar = models.CharField(max_length=30, default="")
|
|
197
|
+
active = models.BooleanField(default=False)
|
|
198
|
+
|
|
199
|
+
@propetry
|
|
200
|
+
def full_name(self):
|
|
201
|
+
return f"{self.name} example_full_name"
|
|
202
|
+
|
|
203
|
+
class ReadSerializer:
|
|
204
|
+
excludes = ["bar"]
|
|
205
|
+
customs = [("full_name", str, "")]
|
|
206
|
+
|
|
207
|
+
class CreateSerializer:
|
|
208
|
+
fields = ["name"]
|
|
209
|
+
optionals = [("bar", str), ("active", bool)]
|
|
210
|
+
|
|
211
|
+
class UpdateSerializer:
|
|
212
|
+
excludes = ["id", "name"]
|
|
213
|
+
optionals = [("bar", str), ("active", bool)]
|
|
186
214
|
```
|
|
187
215
|
|
|
188
216
|
|
|
217
|
+
|
|
189
218
|
### APIViewSet
|
|
190
219
|
|
|
191
220
|
- 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.
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ninja_aio/__init__.py,sha256=fj_imNMQNt3s9V2z1MaTVCq27ZeyFxCD555vG2F80d8,119
|
|
2
|
+
ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
|
|
3
|
+
ninja_aio/auth.py,sha256=fKboioU4sezPukKJukIwiboxml_KV7irhCH3vGYt5pU,1008
|
|
4
|
+
ninja_aio/exceptions.py,sha256=gPnZX1Do2GXudbU8wDYkwhO70Qj0ZNrIJJ2UXRs9vYk,2241
|
|
5
|
+
ninja_aio/models.py,sha256=58Uk24f2IQYDdnGq1vXp8HqmCwDYeSkZtbiQ9JOQEXc,15504
|
|
6
|
+
ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
|
|
7
|
+
ninja_aio/renders.py,sha256=mHeKNJtmDhZmgFpS9B6SPn5uZFcyVXrsoMhr149LeW8,1555
|
|
8
|
+
ninja_aio/schemas.py,sha256=EgRkfhnzZqwGvdBmqlZixMtMcoD1ZxV_qzJ3fmaAy20,113
|
|
9
|
+
ninja_aio/types.py,sha256=EHznS-6KWLwSX5hLeXbAi7qHWla09_rGeQraiLpH-aY,491
|
|
10
|
+
ninja_aio/views.py,sha256=z820mKzfbvh9SZ4cALQVKMfc64kx74t8xWGWaUBzHfs,9270
|
|
11
|
+
django_ninja_aio_crud-0.6.2.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
12
|
+
django_ninja_aio_crud-0.6.2.dist-info/METADATA,sha256=QFGhtfwohjs0YQG1K6ysZpHQEYtfM0BTJaopwqaFmGQ,13740
|
|
13
|
+
django_ninja_aio_crud-0.6.2.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/auth.py
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
from joserfc import jwt, jwk
|
|
1
|
+
from joserfc import jwt, jwk
|
|
2
2
|
from django.http.request import HttpRequest
|
|
3
3
|
from ninja.security.http import HttpBearer
|
|
4
4
|
|
|
5
|
-
from .exceptions import AuthError
|
|
5
|
+
from .exceptions import AuthError
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class AsyncJwtBearer(HttpBearer):
|
|
@@ -16,15 +16,7 @@ class AsyncJwtBearer(HttpBearer):
|
|
|
16
16
|
|
|
17
17
|
def validate_claims(self, claims: jwt.Claims):
|
|
18
18
|
jwt_claims = self.get_claims()
|
|
19
|
-
|
|
20
|
-
try:
|
|
21
|
-
jwt_claims.validate(claims)
|
|
22
|
-
except (
|
|
23
|
-
errors.InvalidClaimError,
|
|
24
|
-
errors.MissingClaimError,
|
|
25
|
-
errors.ExpiredTokenError,
|
|
26
|
-
) as exc:
|
|
27
|
-
raise AuthError(**parse_jose_error(exc), status_code=401)
|
|
19
|
+
jwt_claims.validate(claims)
|
|
28
20
|
|
|
29
21
|
async def auth_handler(self, request: HttpRequest):
|
|
30
22
|
"""
|
|
@@ -35,8 +27,6 @@ class AsyncJwtBearer(HttpBearer):
|
|
|
35
27
|
async def authenticate(self, request: HttpRequest, token: str):
|
|
36
28
|
try:
|
|
37
29
|
self.dcd = jwt.decode(token, self.jwt_public, algorithms=self.algorithms)
|
|
38
|
-
except errors.BadSignatureError as exc:
|
|
39
|
-
raise AuthError(**parse_jose_error(exc), status_code=401)
|
|
40
30
|
except ValueError as exc:
|
|
41
31
|
raise AuthError(", ".join(exc.args), 401)
|
|
42
32
|
|
ninja_aio/exceptions.py
CHANGED
|
@@ -3,6 +3,7 @@ from functools import partial
|
|
|
3
3
|
from joserfc.errors import JoseError
|
|
4
4
|
from ninja import NinjaAPI
|
|
5
5
|
from django.http import HttpRequest, HttpResponse
|
|
6
|
+
from pydantic import ValidationError
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class BaseException(Exception):
|
|
@@ -34,14 +35,37 @@ class AuthError(BaseException):
|
|
|
34
35
|
pass
|
|
35
36
|
|
|
36
37
|
|
|
38
|
+
class PydanticValidationError(BaseException):
|
|
39
|
+
def __init__(self, details=None):
|
|
40
|
+
super().__init__("Validation Error", 400, details)
|
|
41
|
+
|
|
42
|
+
|
|
37
43
|
def _default_error(
|
|
38
44
|
request: HttpRequest, exc: BaseException, api: type[NinjaAPI]
|
|
39
45
|
) -> HttpResponse:
|
|
40
46
|
return api.create_response(request, exc.error, status=exc.status_code)
|
|
41
47
|
|
|
42
48
|
|
|
49
|
+
def _pydantic_validation_error(
|
|
50
|
+
request: HttpRequest, exc: ValidationError, api: type[NinjaAPI]
|
|
51
|
+
) -> HttpResponse:
|
|
52
|
+
error = PydanticValidationError(exc.errors(include_input=False))
|
|
53
|
+
return api.create_response(request, error.error, status=error.status_code)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _jose_error(
|
|
57
|
+
request: HttpRequest, exc: JoseError, api: type[NinjaAPI]
|
|
58
|
+
) -> HttpResponse:
|
|
59
|
+
error = BaseException(**parse_jose_error(exc), status_code=401)
|
|
60
|
+
return api.create_response(request, error.error, status=error.status_code)
|
|
61
|
+
|
|
62
|
+
|
|
43
63
|
def set_api_exception_handlers(api: type[NinjaAPI]) -> None:
|
|
44
64
|
api.add_exception_handler(BaseException, partial(_default_error, api=api))
|
|
65
|
+
api.add_exception_handler(JoseError, partial(_jose_error, api=api))
|
|
66
|
+
api.add_exception_handler(
|
|
67
|
+
ValidationError, partial(_pydantic_validation_error, api=api)
|
|
68
|
+
)
|
|
45
69
|
|
|
46
70
|
|
|
47
71
|
def parse_jose_error(jose_exc: JoseError) -> dict:
|
ninja_aio/models.py
CHANGED
|
@@ -122,7 +122,10 @@ class ModelUtil:
|
|
|
122
122
|
try:
|
|
123
123
|
field_obj = getattr(self.model, k).field
|
|
124
124
|
except AttributeError:
|
|
125
|
-
|
|
125
|
+
try:
|
|
126
|
+
field_obj = getattr(self.model, k).related
|
|
127
|
+
except AttributeError:
|
|
128
|
+
pass
|
|
126
129
|
if isinstance(v, dict) and (
|
|
127
130
|
isinstance(field_obj, models.ForeignKey)
|
|
128
131
|
or isinstance(field_obj, models.OneToOneField)
|
|
@@ -196,6 +199,7 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
196
199
|
class ReadSerializer:
|
|
197
200
|
fields: list[str] = []
|
|
198
201
|
excludes: list[str] = []
|
|
202
|
+
customs: list[tuple[str, type, Any]] = []
|
|
199
203
|
|
|
200
204
|
class UpdateSerializer:
|
|
201
205
|
fields: list[str] = []
|
|
@@ -257,20 +261,23 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
257
261
|
case "Patch":
|
|
258
262
|
s_type = "update"
|
|
259
263
|
case "Out":
|
|
260
|
-
fields, reverse_rels, excludes = cls.get_schema_out_data()
|
|
261
|
-
if not fields and not reverse_rels and not excludes:
|
|
264
|
+
fields, reverse_rels, excludes, customs = cls.get_schema_out_data()
|
|
265
|
+
if not fields and not reverse_rels and not excludes and not customs:
|
|
262
266
|
return None
|
|
263
267
|
return create_schema(
|
|
264
268
|
model=cls,
|
|
265
269
|
name=f"{cls._meta.model_name}SchemaOut",
|
|
266
270
|
depth=depth,
|
|
267
271
|
fields=fields,
|
|
268
|
-
custom_fields=reverse_rels,
|
|
272
|
+
custom_fields=reverse_rels + customs,
|
|
269
273
|
exclude=excludes,
|
|
270
274
|
)
|
|
271
275
|
fields = cls.get_fields(s_type)
|
|
272
|
-
|
|
276
|
+
optionals = cls.get_optional_fields(s_type)
|
|
277
|
+
customs = cls.get_custom_fields(s_type) + optionals
|
|
273
278
|
excludes = cls.get_excluded_fields(s_type)
|
|
279
|
+
if not fields and not excludes:
|
|
280
|
+
fields = [f[0] for f in optionals]
|
|
274
281
|
return (
|
|
275
282
|
create_schema(
|
|
276
283
|
model=cls,
|
|
@@ -364,25 +371,36 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
364
371
|
reverse_rels = []
|
|
365
372
|
for f in cls.get_fields("read"):
|
|
366
373
|
field_obj = getattr(cls, f)
|
|
367
|
-
if isinstance(
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
374
|
+
if isinstance(
|
|
375
|
+
field_obj,
|
|
376
|
+
(
|
|
377
|
+
ManyToManyDescriptor,
|
|
378
|
+
ReverseManyToOneDescriptor,
|
|
379
|
+
ReverseOneToOneDescriptor,
|
|
380
|
+
),
|
|
381
|
+
):
|
|
382
|
+
if isinstance(field_obj, ManyToManyDescriptor):
|
|
383
|
+
rel_obj: ModelSerializer = field_obj.field.related_model
|
|
384
|
+
if field_obj.reverse:
|
|
385
|
+
rel_obj = field_obj.field.model
|
|
386
|
+
rel_type = "many"
|
|
387
|
+
elif isinstance(field_obj, ReverseManyToOneDescriptor):
|
|
388
|
+
rel_obj = field_obj.field.model
|
|
389
|
+
rel_type = "many"
|
|
390
|
+
else: # ReverseOneToOneDescriptor
|
|
391
|
+
rel_obj = field_obj.related.related_model
|
|
392
|
+
rel_type = "one"
|
|
393
|
+
|
|
394
|
+
rel_data = cls.get_reverse_relation_schema(rel_obj, rel_type, f)
|
|
382
395
|
reverse_rels.append(rel_data)
|
|
383
396
|
continue
|
|
384
397
|
fields.append(f)
|
|
385
|
-
return
|
|
398
|
+
return (
|
|
399
|
+
fields,
|
|
400
|
+
reverse_rels,
|
|
401
|
+
cls.get_excluded_fields("read"),
|
|
402
|
+
cls.get_custom_fields("read"),
|
|
403
|
+
)
|
|
386
404
|
|
|
387
405
|
@classmethod
|
|
388
406
|
def is_custom(cls, field: str):
|
ninja_aio/views.py
CHANGED
|
@@ -112,6 +112,9 @@ class APIViewSet:
|
|
|
112
112
|
def _generate_filters_schema(self):
|
|
113
113
|
return self._generate_schema(self.query_params, "FiltersSchema")
|
|
114
114
|
|
|
115
|
+
def _get_pk(self, data: Schema):
|
|
116
|
+
return data.model_dump()[self.model_util.model_pk_name]
|
|
117
|
+
|
|
115
118
|
def get_schemas(self):
|
|
116
119
|
if isinstance(self.model, ModelSerializerMeta):
|
|
117
120
|
return (
|
|
@@ -153,14 +156,17 @@ class APIViewSet:
|
|
|
153
156
|
},
|
|
154
157
|
)
|
|
155
158
|
@paginate(self.pagination_class)
|
|
156
|
-
async def list(
|
|
159
|
+
async def list(
|
|
160
|
+
request: HttpRequest, filters: Query[self.filters_schema] = None
|
|
161
|
+
):
|
|
157
162
|
qs = self.model.objects.select_related()
|
|
158
163
|
if isinstance(self.model, ModelSerializerMeta):
|
|
159
164
|
qs = await self.model.queryset_request(request)
|
|
160
165
|
rels = self.model_util.get_reverse_relations()
|
|
161
166
|
if len(rels) > 0:
|
|
162
167
|
qs = qs.prefetch_related(*rels)
|
|
163
|
-
|
|
168
|
+
if filters is not None:
|
|
169
|
+
qs = await self.query_params_handler(qs, filters.model_dump())
|
|
164
170
|
objs = [
|
|
165
171
|
await self.model_util.read_s(request, obj, self.schema_out)
|
|
166
172
|
async for obj in qs.all()
|
|
@@ -177,7 +183,7 @@ class APIViewSet:
|
|
|
177
183
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
178
184
|
)
|
|
179
185
|
async def retrieve(request: HttpRequest, pk: Path[self.path_schema]):
|
|
180
|
-
obj = await self.model_util.get_object(request, pk)
|
|
186
|
+
obj = await self.model_util.get_object(request, self._get_pk(pk))
|
|
181
187
|
return await self.model_util.read_s(request, obj, self.schema_out)
|
|
182
188
|
|
|
183
189
|
retrieve.__name__ = f"retrieve_{self.model_util.model_name}"
|
|
@@ -192,7 +198,9 @@ class APIViewSet:
|
|
|
192
198
|
async def update(
|
|
193
199
|
request: HttpRequest, data: self.schema_update, pk: Path[self.path_schema]
|
|
194
200
|
):
|
|
195
|
-
return await self.model_util.update_s(
|
|
201
|
+
return await self.model_util.update_s(
|
|
202
|
+
request, data, self._get_pk(pk), self.schema_out
|
|
203
|
+
)
|
|
196
204
|
|
|
197
205
|
update.__name__ = f"update_{self.model_util.model_name}"
|
|
198
206
|
return update
|
|
@@ -204,7 +212,7 @@ class APIViewSet:
|
|
|
204
212
|
response={204: None, self.error_codes: GenericMessageSchema},
|
|
205
213
|
)
|
|
206
214
|
async def delete(request: HttpRequest, pk: Path[self.path_schema]):
|
|
207
|
-
return 204, await self.model_util.delete_s(request, pk)
|
|
215
|
+
return 204, await self.model_util.delete_s(request, self._get_pk(pk))
|
|
208
216
|
|
|
209
217
|
delete.__name__ = f"delete_{self.model_util.model_name}"
|
|
210
218
|
return delete
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=-m_l3-9_2Q1-PvkjWqAfSmu2jt2ILNb490jT2yC4sbk,119
|
|
2
|
-
ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
|
|
3
|
-
ninja_aio/auth.py,sha256=z9gniLIgT8SjRqhGN7ZI0AGHjsALwgU6eyr2m46fwFY,1389
|
|
4
|
-
ninja_aio/exceptions.py,sha256=JGUvZyYevGnFYFk2JYnNqng1e9ilG8l325wA5YC1RUA,1364
|
|
5
|
-
ninja_aio/models.py,sha256=ddV2QGzWSJzwZ4vt-AJ9rRdiQCVSEtl6Bj3mxroSiQE,15096
|
|
6
|
-
ninja_aio/parsers.py,sha256=e_4lGCPV7zs-HTqtdJTc8yQD2KPAn9njbL8nF_Mmgkc,153
|
|
7
|
-
ninja_aio/renders.py,sha256=mHeKNJtmDhZmgFpS9B6SPn5uZFcyVXrsoMhr149LeW8,1555
|
|
8
|
-
ninja_aio/schemas.py,sha256=EgRkfhnzZqwGvdBmqlZixMtMcoD1ZxV_qzJ3fmaAy20,113
|
|
9
|
-
ninja_aio/types.py,sha256=EHznS-6KWLwSX5hLeXbAi7qHWla09_rGeQraiLpH-aY,491
|
|
10
|
-
ninja_aio/views.py,sha256=b-KLPkNhfMXsTx4egfi-nKeuKqYEUOLXJFNt0fYariE,9027
|
|
11
|
-
django_ninja_aio_crud-0.6.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
12
|
-
django_ninja_aio_crud-0.6.0.dist-info/METADATA,sha256=aRvvPaOFsyvqGwg_wCpoQJ0PiQJfo0obaBPlovRlXeA,13078
|
|
13
|
-
django_ninja_aio_crud-0.6.0.dist-info/RECORD,,
|
|
File without changes
|