django-ninja-aio-crud 0.5.0__py3-none-any.whl → 0.6.1__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.5.0.dist-info → django_ninja_aio_crud-0.6.1.dist-info}/METADATA +40 -6
- django_ninja_aio_crud-0.6.1.dist-info/RECORD +13 -0
- ninja_aio/__init__.py +1 -1
- ninja_aio/auth.py +8 -12
- ninja_aio/exceptions.py +34 -6
- ninja_aio/models.py +40 -10
- ninja_aio/views.py +49 -20
- django_ninja_aio_crud-0.5.0.dist-info/RECORD +0 -13
- {django_ninja_aio_crud-0.5.0.dist-info → django_ninja_aio_crud-0.6.1.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.
|
|
3
|
+
Version: 0.6.1
|
|
4
4
|
Summary: Django Ninja AIO CRUD - Rest Framework
|
|
5
5
|
Author: Giuseppe Casillo
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -25,9 +25,16 @@ Classifier: Topic :: Internet :: WWW/HTTP
|
|
|
25
25
|
Requires-Dist: django-ninja >=1.3.0
|
|
26
26
|
Requires-Dist: joserfc >=1.0.0
|
|
27
27
|
Requires-Dist: orjson >= 3.10.7
|
|
28
|
+
Requires-Dist: coverage ; extra == "test"
|
|
28
29
|
Project-URL: Repository, https://github.com/caspel26/django-ninja-aio-crud
|
|
30
|
+
Provides-Extra: test
|
|
29
31
|
|
|
30
32
|
# 🥷 django-ninja-aio-crud
|
|
33
|
+

|
|
34
|
+
[](https://codecov.io/gh/caspel26/django-ninja-aio-crud)
|
|
35
|
+
[](https://pypi.org/project/django-ninja-aio-crud/)
|
|
36
|
+
[](https://github.com/caspel26/django-ninja-aio-crud/blob/main/LICENSE)
|
|
37
|
+
[](https://github.com/astral-sh/ruff)
|
|
31
38
|
> [!NOTE]
|
|
32
39
|
> Django ninja aio crud framework is based on **<a href="https://django-ninja.dev/">Django Ninja framework</a>**. It comes out with built-in views and models which are able to make automatic async CRUD operations and codes views class based making the developers' life easier and the code cleaner.
|
|
33
40
|
|
|
@@ -120,7 +127,7 @@ class Foo(ModelSerializer):
|
|
|
120
127
|
if not payload.get("force_activation"):
|
|
121
128
|
return
|
|
122
129
|
setattr(self, "force_activation", True)
|
|
123
|
-
|
|
130
|
+
|
|
124
131
|
async def post_create(self) -> None:
|
|
125
132
|
if not hasattr(self, "force_activation") or not getattr(self, "force_activation"):
|
|
126
133
|
return
|
|
@@ -150,7 +157,7 @@ class Foo(ModelSerializer):
|
|
|
150
157
|
optionals = [("bar", str), ("active", bool)]
|
|
151
158
|
|
|
152
159
|
class UpdateSerializer:
|
|
153
|
-
optionals = [
|
|
160
|
+
optionals = [("bar", str), ("active", bool)]
|
|
154
161
|
```
|
|
155
162
|
|
|
156
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").
|
|
@@ -175,7 +182,7 @@ class Foo(ModelSerializer):
|
|
|
175
182
|
|
|
176
183
|
class UpdateSerializer:
|
|
177
184
|
excludes = ["id", "name"]
|
|
178
|
-
optionals = [
|
|
185
|
+
optionals = [("bar", str), ("active", bool)]
|
|
179
186
|
```
|
|
180
187
|
|
|
181
188
|
|
|
@@ -197,7 +204,7 @@ class FooAPI(APIViewSet):
|
|
|
197
204
|
model = Foo
|
|
198
205
|
api = api
|
|
199
206
|
|
|
200
|
-
|
|
207
|
+
|
|
201
208
|
FooAPI().add_views_to_route()
|
|
202
209
|
```
|
|
203
210
|
|
|
@@ -238,6 +245,10 @@ FooAPI().add_views_to_route()
|
|
|
238
245
|
|
|
239
246
|
- 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
247
|
|
|
248
|
+
> [!TIP]
|
|
249
|
+
> You can exclude by default an endpoint without declaring the serializer.
|
|
250
|
+
> For example if you don't want to give update method to a CRUD just do not declare UpdateSerializer into model.
|
|
251
|
+
|
|
241
252
|
```python
|
|
242
253
|
# views.py
|
|
243
254
|
from ninja_aio import NinjaAIO
|
|
@@ -254,6 +265,29 @@ class FooAPI(APIViewSet):
|
|
|
254
265
|
disable = ["retrieve", "update"]
|
|
255
266
|
|
|
256
267
|
|
|
268
|
+
FooAPI().add_views_to_route()
|
|
269
|
+
```
|
|
270
|
+
For the list endpoint you can also set query params and handle them. They will be also visible into swagger.
|
|
271
|
+
|
|
272
|
+
```python
|
|
273
|
+
# views.py
|
|
274
|
+
from ninja_aio import NinjaAIO
|
|
275
|
+
from ninja_aio.views import APIViewSet
|
|
276
|
+
|
|
277
|
+
from .models import Foo
|
|
278
|
+
|
|
279
|
+
api = NinjaAIO()
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
class FooAPI(APIViewSet):
|
|
283
|
+
model = Foo
|
|
284
|
+
api = api
|
|
285
|
+
query_params = {"name": (str, None), "active": (bool, None)}
|
|
286
|
+
|
|
287
|
+
async def query_params_handler(self, queryset, filters):
|
|
288
|
+
return queryset.filter(**{k: v for k, v in filters.items() if v is not None})
|
|
289
|
+
|
|
290
|
+
|
|
257
291
|
FooAPI().add_views_to_route()
|
|
258
292
|
```
|
|
259
293
|
|
|
@@ -371,7 +405,7 @@ BarAPI().add_views_to_route()
|
|
|
371
405
|
|
|
372
406
|
### Jwt
|
|
373
407
|
|
|
374
|
-
- 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.
|
|
408
|
+
- 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.
|
|
375
409
|
|
|
376
410
|
```python
|
|
377
411
|
from ninja_aio.auth import AsyncJWTBearer
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
ninja_aio/__init__.py,sha256=VSJ4PO3jZ-Z_NF8y2IrjsTc6MSqxlJ_b7TtAvZW4f28,119
|
|
2
|
+
ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
|
|
3
|
+
ninja_aio/auth.py,sha256=z9gniLIgT8SjRqhGN7ZI0AGHjsALwgU6eyr2m46fwFY,1389
|
|
4
|
+
ninja_aio/exceptions.py,sha256=Lg0nUJe6kWEf0qvXpQ9FqQ8sCFBFH-lP4xovvY-YfiY,1922
|
|
5
|
+
ninja_aio/models.py,sha256=lplwxtZKweoppJEi0ERPSHRtsq5UwQaGK3NtR_I-FBw,15213
|
|
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.1.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
12
|
+
django_ninja_aio_crud-0.6.1.dist-info/METADATA,sha256=4nH4p3l9UUIUGilQ4C_fDM1Uv-WiF6FveqrG25JosQk,13076
|
|
13
|
+
django_ninja_aio_crud-0.6.1.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/auth.py
CHANGED
|
@@ -2,7 +2,7 @@ from joserfc import jwt, jwk, errors
|
|
|
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, parse_jose_error
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class AsyncJwtBearer(HttpBearer):
|
|
@@ -23,8 +23,8 @@ class AsyncJwtBearer(HttpBearer):
|
|
|
23
23
|
errors.InvalidClaimError,
|
|
24
24
|
errors.MissingClaimError,
|
|
25
25
|
errors.ExpiredTokenError,
|
|
26
|
-
):
|
|
27
|
-
raise AuthError()
|
|
26
|
+
) as exc:
|
|
27
|
+
raise AuthError(**parse_jose_error(exc), status_code=401)
|
|
28
28
|
|
|
29
29
|
async def auth_handler(self, request: HttpRequest):
|
|
30
30
|
"""
|
|
@@ -35,15 +35,11 @@ class AsyncJwtBearer(HttpBearer):
|
|
|
35
35
|
async def authenticate(self, request: HttpRequest, token: str):
|
|
36
36
|
try:
|
|
37
37
|
self.dcd = jwt.decode(token, self.jwt_public, algorithms=self.algorithms)
|
|
38
|
-
except
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
return None
|
|
38
|
+
except errors.BadSignatureError as exc:
|
|
39
|
+
raise AuthError(**parse_jose_error(exc), status_code=401)
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
raise AuthError(", ".join(exc.args), 401)
|
|
43
42
|
|
|
44
|
-
|
|
45
|
-
self.validate_claims(self.dcd.claims)
|
|
46
|
-
except AuthError:
|
|
47
|
-
return None
|
|
43
|
+
self.validate_claims(self.dcd.claims)
|
|
48
44
|
|
|
49
45
|
return await self.auth_handler(request)
|
ninja_aio/exceptions.py
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
from functools import partial
|
|
2
|
+
|
|
3
|
+
from joserfc.errors import JoseError
|
|
2
4
|
from ninja import NinjaAPI
|
|
3
5
|
from django.http import HttpRequest, HttpResponse
|
|
6
|
+
from pydantic import ValidationError
|
|
4
7
|
|
|
5
8
|
|
|
6
9
|
class BaseException(Exception):
|
|
@@ -11,11 +14,14 @@ class BaseException(Exception):
|
|
|
11
14
|
self,
|
|
12
15
|
error: str | dict = None,
|
|
13
16
|
status_code: int | None = None,
|
|
14
|
-
|
|
17
|
+
details: str | None = None,
|
|
15
18
|
) -> None:
|
|
16
|
-
|
|
19
|
+
if isinstance(error, str):
|
|
20
|
+
self.error = {"error": error}
|
|
21
|
+
if isinstance(error, dict):
|
|
22
|
+
self.error = error
|
|
23
|
+
self.error |= {"details": details} if details else {}
|
|
17
24
|
self.status_code = status_code or self.status_code
|
|
18
|
-
self.is_critical = is_critical
|
|
19
25
|
|
|
20
26
|
def get_error(self):
|
|
21
27
|
return self.error, self.status_code
|
|
@@ -29,13 +35,35 @@ class AuthError(BaseException):
|
|
|
29
35
|
pass
|
|
30
36
|
|
|
31
37
|
|
|
32
|
-
|
|
33
|
-
|
|
38
|
+
class PydanticValidationError(BaseException):
|
|
39
|
+
def __init__(self, details=None):
|
|
40
|
+
super().__init__("Validation Error", 400, details)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def _default_error(
|
|
44
|
+
request: HttpRequest, exc: BaseException, api: type[NinjaAPI]
|
|
34
45
|
) -> HttpResponse:
|
|
35
46
|
return api.create_response(request, exc.error, status=exc.status_code)
|
|
36
47
|
|
|
37
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
|
+
|
|
38
56
|
def set_api_exception_handlers(api: type[NinjaAPI]) -> None:
|
|
57
|
+
api.add_exception_handler(BaseException, partial(_default_error, api=api))
|
|
39
58
|
api.add_exception_handler(
|
|
40
|
-
|
|
59
|
+
ValidationError, partial(_pydantic_validation_error, api=api)
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_jose_error(jose_exc: JoseError) -> dict:
|
|
64
|
+
error_msg = {"error": jose_exc.error}
|
|
65
|
+
return (
|
|
66
|
+
error_msg | {"details": jose_exc.description}
|
|
67
|
+
if jose_exc.description
|
|
68
|
+
else error_msg
|
|
41
69
|
)
|
ninja_aio/models.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import base64
|
|
2
2
|
from typing import Any
|
|
3
3
|
|
|
4
|
-
from ninja
|
|
4
|
+
from ninja import Schema
|
|
5
5
|
from ninja.orm import create_schema
|
|
6
6
|
|
|
7
7
|
from django.db import models
|
|
@@ -27,21 +27,48 @@ class ModelUtil:
|
|
|
27
27
|
return self.model.get_fields("read")
|
|
28
28
|
return [field.name for field in self.model._meta.get_fields()]
|
|
29
29
|
|
|
30
|
+
@property
|
|
31
|
+
def model_name(self) -> str:
|
|
32
|
+
return self.model._meta.model_name
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def model_pk_name(self) -> str:
|
|
36
|
+
return self.model._meta.pk.attname
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def model_verbose_name_plural(self) -> str:
|
|
40
|
+
return self.model._meta.verbose_name_plural
|
|
41
|
+
|
|
30
42
|
def verbose_name_path_resolver(self) -> str:
|
|
31
|
-
return "-".join(self.
|
|
43
|
+
return "-".join(self.model_verbose_name_plural.split(" "))
|
|
32
44
|
|
|
33
45
|
def verbose_name_view_resolver(self) -> str:
|
|
34
|
-
return self.
|
|
46
|
+
return self.model_verbose_name_plural.replace(" ", "")
|
|
47
|
+
|
|
48
|
+
async def get_object(
|
|
49
|
+
self,
|
|
50
|
+
request: HttpRequest,
|
|
51
|
+
pk: int | str = None,
|
|
52
|
+
filters: dict = None,
|
|
53
|
+
getters: dict = None,
|
|
54
|
+
):
|
|
55
|
+
get_q = {self.model_pk_name: pk} if pk is not None else {}
|
|
56
|
+
if getters:
|
|
57
|
+
get_q |= getters
|
|
35
58
|
|
|
36
|
-
async def get_object(self, request: HttpRequest, pk: int | str):
|
|
37
|
-
q = {self.model._meta.pk.attname: pk}
|
|
38
59
|
obj_qs = self.model.objects.select_related()
|
|
39
60
|
if isinstance(self.model, ModelSerializerMeta):
|
|
40
61
|
obj_qs = await self.model.queryset_request(request)
|
|
62
|
+
|
|
63
|
+
obj_qs = obj_qs.prefetch_related(*self.get_reverse_relations())
|
|
64
|
+
if filters:
|
|
65
|
+
obj_qs = obj_qs.filter(**filters)
|
|
66
|
+
|
|
41
67
|
try:
|
|
42
|
-
obj = await obj_qs.
|
|
68
|
+
obj = await obj_qs.aget(**get_q)
|
|
43
69
|
except ObjectDoesNotExist:
|
|
44
|
-
raise SerializeError({self.
|
|
70
|
+
raise SerializeError({self.model_name: "not found"}, 404)
|
|
71
|
+
|
|
45
72
|
return obj
|
|
46
73
|
|
|
47
74
|
def get_reverse_relations(self) -> list[str]:
|
|
@@ -242,8 +269,11 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
242
269
|
exclude=excludes,
|
|
243
270
|
)
|
|
244
271
|
fields = cls.get_fields(s_type)
|
|
245
|
-
|
|
272
|
+
optionals = cls.get_optional_fields(s_type)
|
|
273
|
+
customs = cls.get_custom_fields(s_type) + optionals
|
|
246
274
|
excludes = cls.get_excluded_fields(s_type)
|
|
275
|
+
if not fields and not excludes:
|
|
276
|
+
fields = [f[0] for f in optionals]
|
|
247
277
|
return (
|
|
248
278
|
create_schema(
|
|
249
279
|
model=cls,
|
|
@@ -379,11 +409,11 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
379
409
|
(field, field_type, None)
|
|
380
410
|
for field, field_type in cls._get_fields(s_type, "optionals")
|
|
381
411
|
]
|
|
382
|
-
|
|
412
|
+
|
|
383
413
|
@classmethod
|
|
384
414
|
def get_excluded_fields(cls, s_type: type[S_TYPES]):
|
|
385
415
|
return cls._get_fields(s_type, "excludes")
|
|
386
|
-
|
|
416
|
+
|
|
387
417
|
@classmethod
|
|
388
418
|
def get_fields(cls, s_type: type[S_TYPES]):
|
|
389
419
|
return cls._get_fields(s_type, "fields")
|
ninja_aio/views.py
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
from typing import List
|
|
2
2
|
|
|
3
|
-
from ninja import NinjaAPI, Router, Schema, Path
|
|
3
|
+
from ninja import NinjaAPI, Router, Schema, Path, Query
|
|
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
|
-
from django.db.models import Model
|
|
7
|
+
from django.db.models import Model, QuerySet
|
|
8
8
|
from pydantic import create_model
|
|
9
9
|
|
|
10
10
|
from .models import ModelSerializer, ModelUtil
|
|
@@ -73,16 +73,19 @@ class APIViewSet:
|
|
|
73
73
|
schema_update: Schema | None = None
|
|
74
74
|
auths: list | None = NOT_SET
|
|
75
75
|
pagination_class: type[AsyncPaginationBase] = PageNumberPagination
|
|
76
|
+
query_params: dict[str, tuple[type, ...]] = {}
|
|
76
77
|
disable: list[type[VIEW_TYPES]] = []
|
|
77
78
|
|
|
78
79
|
def __init__(self) -> None:
|
|
79
|
-
self.router = Router(tags=[self.model._meta.model_name.capitalize()])
|
|
80
|
-
self.path = "/"
|
|
81
|
-
self.path_retrieve = f"{{{self.model._meta.pk.attname}}}/"
|
|
82
80
|
self.error_codes = ERROR_CODES
|
|
83
81
|
self.model_util = ModelUtil(self.model)
|
|
84
82
|
self.schema_out, self.schema_in, self.schema_update = self.get_schemas()
|
|
85
|
-
self.path_schema = self.
|
|
83
|
+
self.path_schema = self._generate_path_schema()
|
|
84
|
+
self.filters_schema = self._generate_filters_schema()
|
|
85
|
+
self.router_tag = self.model_util.model_name.capitalize()
|
|
86
|
+
self.router = Router(tags=[self.router_tag])
|
|
87
|
+
self.path = "/"
|
|
88
|
+
self.path_retrieve = f"{{{self.model_util.model_pk_name}}}/"
|
|
86
89
|
|
|
87
90
|
@property
|
|
88
91
|
def _crud_views(self):
|
|
@@ -98,11 +101,19 @@ class APIViewSet:
|
|
|
98
101
|
"delete": (None, self.delete_view),
|
|
99
102
|
}
|
|
100
103
|
|
|
101
|
-
def
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
return
|
|
104
|
+
def _generate_schema(self, fields: dict, name: str) -> Schema:
|
|
105
|
+
return create_model(f"{self.model_util.model_name}{name}", **fields)
|
|
106
|
+
|
|
107
|
+
def _generate_path_schema(self):
|
|
108
|
+
return self._generate_schema(
|
|
109
|
+
{self.model_util.model_pk_name: (int | str, ...)}, "PathSchema"
|
|
110
|
+
)
|
|
111
|
+
|
|
112
|
+
def _generate_filters_schema(self):
|
|
113
|
+
return self._generate_schema(self.query_params, "FiltersSchema")
|
|
114
|
+
|
|
115
|
+
def _get_pk(self, data: Schema):
|
|
116
|
+
return data.model_dump()[self.model_util.model_pk_name]
|
|
106
117
|
|
|
107
118
|
def get_schemas(self):
|
|
108
119
|
if isinstance(self.model, ModelSerializerMeta):
|
|
@@ -113,6 +124,16 @@ class APIViewSet:
|
|
|
113
124
|
)
|
|
114
125
|
return self.schema_out, self.schema_in, self.schema_update
|
|
115
126
|
|
|
127
|
+
async def query_params_handler(
|
|
128
|
+
self, queryset: QuerySet[ModelSerializer], filters: dict
|
|
129
|
+
):
|
|
130
|
+
"""
|
|
131
|
+
Override this method to handle request query params making queries to the database
|
|
132
|
+
based on filters or any other logic. This method should return a queryset. filters
|
|
133
|
+
are given already dumped by the schema.
|
|
134
|
+
"""
|
|
135
|
+
return queryset
|
|
136
|
+
|
|
116
137
|
def create_view(self):
|
|
117
138
|
@self.router.post(
|
|
118
139
|
self.path,
|
|
@@ -122,7 +143,7 @@ class APIViewSet:
|
|
|
122
143
|
async def create(request: HttpRequest, data: self.schema_in):
|
|
123
144
|
return 201, await self.model_util.create_s(request, data, self.schema_out)
|
|
124
145
|
|
|
125
|
-
create.__name__ = f"create_{self.
|
|
146
|
+
create.__name__ = f"create_{self.model_util.model_name}"
|
|
126
147
|
return create
|
|
127
148
|
|
|
128
149
|
def list_view(self):
|
|
@@ -135,13 +156,17 @@ class APIViewSet:
|
|
|
135
156
|
},
|
|
136
157
|
)
|
|
137
158
|
@paginate(self.pagination_class)
|
|
138
|
-
async def list(
|
|
159
|
+
async def list(
|
|
160
|
+
request: HttpRequest, filters: Query[self.filters_schema] = None
|
|
161
|
+
):
|
|
139
162
|
qs = self.model.objects.select_related()
|
|
140
163
|
if isinstance(self.model, ModelSerializerMeta):
|
|
141
164
|
qs = await self.model.queryset_request(request)
|
|
142
165
|
rels = self.model_util.get_reverse_relations()
|
|
143
166
|
if len(rels) > 0:
|
|
144
167
|
qs = qs.prefetch_related(*rels)
|
|
168
|
+
if filters is not None:
|
|
169
|
+
qs = await self.query_params_handler(qs, filters.model_dump())
|
|
145
170
|
objs = [
|
|
146
171
|
await self.model_util.read_s(request, obj, self.schema_out)
|
|
147
172
|
async for obj in qs.all()
|
|
@@ -158,10 +183,10 @@ class APIViewSet:
|
|
|
158
183
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
159
184
|
)
|
|
160
185
|
async def retrieve(request: HttpRequest, pk: Path[self.path_schema]):
|
|
161
|
-
obj = await self.model_util.get_object(request, pk)
|
|
186
|
+
obj = await self.model_util.get_object(request, self._get_pk(pk))
|
|
162
187
|
return await self.model_util.read_s(request, obj, self.schema_out)
|
|
163
188
|
|
|
164
|
-
retrieve.__name__ = f"retrieve_{self.
|
|
189
|
+
retrieve.__name__ = f"retrieve_{self.model_util.model_name}"
|
|
165
190
|
return retrieve
|
|
166
191
|
|
|
167
192
|
def update_view(self):
|
|
@@ -170,10 +195,14 @@ class APIViewSet:
|
|
|
170
195
|
auth=self.auths,
|
|
171
196
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
172
197
|
)
|
|
173
|
-
async def update(
|
|
174
|
-
|
|
198
|
+
async def update(
|
|
199
|
+
request: HttpRequest, data: self.schema_update, pk: Path[self.path_schema]
|
|
200
|
+
):
|
|
201
|
+
return await self.model_util.update_s(
|
|
202
|
+
request, data, self._get_pk(pk), self.schema_out
|
|
203
|
+
)
|
|
175
204
|
|
|
176
|
-
update.__name__ = f"update_{self.
|
|
205
|
+
update.__name__ = f"update_{self.model_util.model_name}"
|
|
177
206
|
return update
|
|
178
207
|
|
|
179
208
|
def delete_view(self):
|
|
@@ -183,9 +212,9 @@ class APIViewSet:
|
|
|
183
212
|
response={204: None, self.error_codes: GenericMessageSchema},
|
|
184
213
|
)
|
|
185
214
|
async def delete(request: HttpRequest, pk: Path[self.path_schema]):
|
|
186
|
-
return 204, await self.model_util.delete_s(request, pk)
|
|
215
|
+
return 204, await self.model_util.delete_s(request, self._get_pk(pk))
|
|
187
216
|
|
|
188
|
-
delete.__name__ = f"delete_{self.
|
|
217
|
+
delete.__name__ = f"delete_{self.model_util.model_name}"
|
|
189
218
|
return delete
|
|
190
219
|
|
|
191
220
|
def views(self):
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=ZragxlHPdmlSQb8pX6GdLzlHSdO-6qs4EelxyzrSMGw,119
|
|
2
|
-
ninja_aio/api.py,sha256=Fe6l3YCy7MW5TY4-Lbl80CFuK2NT2Y7tHfmqPk6Mqak,1735
|
|
3
|
-
ninja_aio/auth.py,sha256=3Pr8llYoCN59ZH3J_2qWmzjXxsy-rpQzXrVfwLfY25Q,1299
|
|
4
|
-
ninja_aio/exceptions.py,sha256=LP9GZpDk1fYMRRgGHSAstcCvgu5uSDLg8PPDANlZGTs,1008
|
|
5
|
-
ninja_aio/models.py,sha256=wCEQfzU3Q96rMwbkqBMjuaPipILRoX2eorId1hfsiR4,14569
|
|
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=p0wWVMw1TRtdCSHKfIWHPINGn3rkGWFPsRwMyg4HUOs,8121
|
|
11
|
-
django_ninja_aio_crud-0.5.0.dist-info/WHEEL,sha256=CpUCUxeHQbRN5UGRQHYRJorO5Af-Qy_fHMctcQ8DSGI,82
|
|
12
|
-
django_ninja_aio_crud-0.5.0.dist-info/METADATA,sha256=FdZKHgHjgaZqqrv9BNB801Q1lHjSlYaMFUA-cBA_SQs,11573
|
|
13
|
-
django_ninja_aio_crud-0.5.0.dist-info/RECORD,,
|
|
File without changes
|