django-ninja-aio-crud 0.5.0__py3-none-any.whl → 0.6.0__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.0.dist-info}/METADATA +38 -4
- django_ninja_aio_crud-0.6.0.dist-info/RECORD +13 -0
- ninja_aio/__init__.py +1 -1
- ninja_aio/auth.py +8 -12
- ninja_aio/exceptions.py +19 -7
- ninja_aio/models.py +36 -9
- ninja_aio/views.py +38 -17
- 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.0.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.0
|
|
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
|
|
@@ -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=-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,,
|
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,4 +1,6 @@
|
|
|
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
|
|
4
6
|
|
|
@@ -11,11 +13,14 @@ class BaseException(Exception):
|
|
|
11
13
|
self,
|
|
12
14
|
error: str | dict = None,
|
|
13
15
|
status_code: int | None = None,
|
|
14
|
-
|
|
16
|
+
details: str | None = None,
|
|
15
17
|
) -> None:
|
|
16
|
-
|
|
18
|
+
if isinstance(error, str):
|
|
19
|
+
self.error = {"error": error}
|
|
20
|
+
if isinstance(error, dict):
|
|
21
|
+
self.error = error
|
|
22
|
+
self.error |= {"details": details} if details else {}
|
|
17
23
|
self.status_code = status_code or self.status_code
|
|
18
|
-
self.is_critical = is_critical
|
|
19
24
|
|
|
20
25
|
def get_error(self):
|
|
21
26
|
return self.error, self.status_code
|
|
@@ -29,13 +34,20 @@ class AuthError(BaseException):
|
|
|
29
34
|
pass
|
|
30
35
|
|
|
31
36
|
|
|
32
|
-
def
|
|
33
|
-
request: HttpRequest, exc:
|
|
37
|
+
def _default_error(
|
|
38
|
+
request: HttpRequest, exc: BaseException, api: type[NinjaAPI]
|
|
34
39
|
) -> HttpResponse:
|
|
35
40
|
return api.create_response(request, exc.error, status=exc.status_code)
|
|
36
41
|
|
|
37
42
|
|
|
38
43
|
def set_api_exception_handlers(api: type[NinjaAPI]) -> None:
|
|
39
|
-
api.add_exception_handler(
|
|
40
|
-
|
|
44
|
+
api.add_exception_handler(BaseException, partial(_default_error, api=api))
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_jose_error(jose_exc: JoseError) -> dict:
|
|
48
|
+
error_msg = {"error": jose_exc.error}
|
|
49
|
+
return (
|
|
50
|
+
error_msg | {"details": jose_exc.description}
|
|
51
|
+
if jose_exc.description
|
|
52
|
+
else error_msg
|
|
41
53
|
)
|
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]:
|
|
@@ -379,11 +406,11 @@ class ModelSerializer(models.Model, metaclass=ModelSerializerMeta):
|
|
|
379
406
|
(field, field_type, None)
|
|
380
407
|
for field, field_type in cls._get_fields(s_type, "optionals")
|
|
381
408
|
]
|
|
382
|
-
|
|
409
|
+
|
|
383
410
|
@classmethod
|
|
384
411
|
def get_excluded_fields(cls, s_type: type[S_TYPES]):
|
|
385
412
|
return cls._get_fields(s_type, "excludes")
|
|
386
|
-
|
|
413
|
+
|
|
387
414
|
@classmethod
|
|
388
415
|
def get_fields(cls, s_type: type[S_TYPES]):
|
|
389
416
|
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,16 @@ 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")
|
|
106
114
|
|
|
107
115
|
def get_schemas(self):
|
|
108
116
|
if isinstance(self.model, ModelSerializerMeta):
|
|
@@ -113,6 +121,16 @@ class APIViewSet:
|
|
|
113
121
|
)
|
|
114
122
|
return self.schema_out, self.schema_in, self.schema_update
|
|
115
123
|
|
|
124
|
+
async def query_params_handler(
|
|
125
|
+
self, queryset: QuerySet[ModelSerializer], filters: dict
|
|
126
|
+
):
|
|
127
|
+
"""
|
|
128
|
+
Override this method to handle request query params making queries to the database
|
|
129
|
+
based on filters or any other logic. This method should return a queryset. filters
|
|
130
|
+
are given already dumped by the schema.
|
|
131
|
+
"""
|
|
132
|
+
return queryset
|
|
133
|
+
|
|
116
134
|
def create_view(self):
|
|
117
135
|
@self.router.post(
|
|
118
136
|
self.path,
|
|
@@ -122,7 +140,7 @@ class APIViewSet:
|
|
|
122
140
|
async def create(request: HttpRequest, data: self.schema_in):
|
|
123
141
|
return 201, await self.model_util.create_s(request, data, self.schema_out)
|
|
124
142
|
|
|
125
|
-
create.__name__ = f"create_{self.
|
|
143
|
+
create.__name__ = f"create_{self.model_util.model_name}"
|
|
126
144
|
return create
|
|
127
145
|
|
|
128
146
|
def list_view(self):
|
|
@@ -135,13 +153,14 @@ class APIViewSet:
|
|
|
135
153
|
},
|
|
136
154
|
)
|
|
137
155
|
@paginate(self.pagination_class)
|
|
138
|
-
async def list(request: HttpRequest):
|
|
156
|
+
async def list(request: HttpRequest, filters: Query[self.filters_schema]):
|
|
139
157
|
qs = self.model.objects.select_related()
|
|
140
158
|
if isinstance(self.model, ModelSerializerMeta):
|
|
141
159
|
qs = await self.model.queryset_request(request)
|
|
142
160
|
rels = self.model_util.get_reverse_relations()
|
|
143
161
|
if len(rels) > 0:
|
|
144
162
|
qs = qs.prefetch_related(*rels)
|
|
163
|
+
qs = await self.query_params_handler(qs, filters.model_dump())
|
|
145
164
|
objs = [
|
|
146
165
|
await self.model_util.read_s(request, obj, self.schema_out)
|
|
147
166
|
async for obj in qs.all()
|
|
@@ -161,7 +180,7 @@ class APIViewSet:
|
|
|
161
180
|
obj = await self.model_util.get_object(request, pk)
|
|
162
181
|
return await self.model_util.read_s(request, obj, self.schema_out)
|
|
163
182
|
|
|
164
|
-
retrieve.__name__ = f"retrieve_{self.
|
|
183
|
+
retrieve.__name__ = f"retrieve_{self.model_util.model_name}"
|
|
165
184
|
return retrieve
|
|
166
185
|
|
|
167
186
|
def update_view(self):
|
|
@@ -170,10 +189,12 @@ class APIViewSet:
|
|
|
170
189
|
auth=self.auths,
|
|
171
190
|
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
172
191
|
)
|
|
173
|
-
async def update(
|
|
192
|
+
async def update(
|
|
193
|
+
request: HttpRequest, data: self.schema_update, pk: Path[self.path_schema]
|
|
194
|
+
):
|
|
174
195
|
return await self.model_util.update_s(request, data, pk, self.schema_out)
|
|
175
196
|
|
|
176
|
-
update.__name__ = f"update_{self.
|
|
197
|
+
update.__name__ = f"update_{self.model_util.model_name}"
|
|
177
198
|
return update
|
|
178
199
|
|
|
179
200
|
def delete_view(self):
|
|
@@ -185,7 +206,7 @@ class APIViewSet:
|
|
|
185
206
|
async def delete(request: HttpRequest, pk: Path[self.path_schema]):
|
|
186
207
|
return 204, await self.model_util.delete_s(request, pk)
|
|
187
208
|
|
|
188
|
-
delete.__name__ = f"delete_{self.
|
|
209
|
+
delete.__name__ = f"delete_{self.model_util.model_name}"
|
|
189
210
|
return delete
|
|
190
211
|
|
|
191
212
|
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
|