django-ninja-aio-crud 0.10.2__py3-none-any.whl → 2.4.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-2.4.0.dist-info/METADATA +382 -0
- django_ninja_aio_crud-2.4.0.dist-info/RECORD +29 -0
- ninja_aio/__init__.py +1 -1
- ninja_aio/api.py +24 -2
- ninja_aio/auth.py +186 -4
- ninja_aio/decorators/__init__.py +23 -0
- ninja_aio/decorators/operations.py +9 -0
- ninja_aio/decorators/views.py +219 -0
- ninja_aio/exceptions.py +36 -1
- ninja_aio/factory/__init__.py +3 -0
- ninja_aio/factory/operations.py +296 -0
- ninja_aio/helpers/__init__.py +0 -0
- ninja_aio/helpers/api.py +506 -0
- ninja_aio/helpers/query.py +108 -0
- ninja_aio/models/__init__.py +4 -0
- ninja_aio/models/serializers.py +738 -0
- ninja_aio/models/utils.py +894 -0
- ninja_aio/renders.py +26 -26
- ninja_aio/schemas/__init__.py +23 -0
- ninja_aio/{schemas.py → schemas/api.py} +0 -5
- ninja_aio/schemas/generics.py +5 -0
- ninja_aio/schemas/helpers.py +170 -0
- ninja_aio/types.py +3 -1
- ninja_aio/views/__init__.py +3 -0
- ninja_aio/views/api.py +582 -0
- ninja_aio/views/mixins.py +275 -0
- django_ninja_aio_crud-0.10.2.dist-info/METADATA +0 -526
- django_ninja_aio_crud-0.10.2.dist-info/RECORD +0 -14
- ninja_aio/models.py +0 -549
- ninja_aio/views.py +0 -522
- {django_ninja_aio_crud-0.10.2.dist-info → django_ninja_aio_crud-2.4.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-0.10.2.dist-info → django_ninja_aio_crud-2.4.0.dist-info}/licenses/LICENSE +0 -0
ninja_aio/views.py
DELETED
|
@@ -1,522 +0,0 @@
|
|
|
1
|
-
import asyncio
|
|
2
|
-
from typing import List
|
|
3
|
-
|
|
4
|
-
from ninja import NinjaAPI, Router, Schema, Path, Query
|
|
5
|
-
from ninja.constants import NOT_SET
|
|
6
|
-
from ninja.pagination import paginate, AsyncPaginationBase, PageNumberPagination
|
|
7
|
-
from django.http import HttpRequest
|
|
8
|
-
from django.db.models import Model, QuerySet
|
|
9
|
-
from pydantic import create_model
|
|
10
|
-
|
|
11
|
-
from .models import ModelSerializer, ModelUtil
|
|
12
|
-
from .schemas import (
|
|
13
|
-
GenericMessageSchema,
|
|
14
|
-
M2MSchemaOut,
|
|
15
|
-
M2MSchemaIn,
|
|
16
|
-
M2MAddSchemaIn,
|
|
17
|
-
M2MRemoveSchemaIn,
|
|
18
|
-
)
|
|
19
|
-
from .types import ModelSerializerMeta, VIEW_TYPES
|
|
20
|
-
|
|
21
|
-
ERROR_CODES = frozenset({400, 401, 404, 428})
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
class APIView:
|
|
25
|
-
api: NinjaAPI
|
|
26
|
-
router_tag: str
|
|
27
|
-
api_route_path: str
|
|
28
|
-
auth: list | None = NOT_SET
|
|
29
|
-
|
|
30
|
-
def __init__(self) -> None:
|
|
31
|
-
self.router = Router(tags=[self.router_tag])
|
|
32
|
-
self.error_codes = ERROR_CODES
|
|
33
|
-
|
|
34
|
-
def views(self):
|
|
35
|
-
"""
|
|
36
|
-
Override this method to add your custom views. For example:
|
|
37
|
-
@self.router.get(some_path, response=some_schema)
|
|
38
|
-
async def some_method(request, *args, **kwargs):
|
|
39
|
-
pass
|
|
40
|
-
|
|
41
|
-
You can add multilple views just doing:
|
|
42
|
-
|
|
43
|
-
@self.router.get(some_path, response=some_schema)
|
|
44
|
-
async def some_method(request, *args, **kwargs):
|
|
45
|
-
pass
|
|
46
|
-
|
|
47
|
-
@self.router.post(some_path, response=some_schema)
|
|
48
|
-
async def some_method(request, *args, **kwargs):
|
|
49
|
-
pass
|
|
50
|
-
|
|
51
|
-
If you provided a list of auths you can chose which of your views
|
|
52
|
-
should be authenticated:
|
|
53
|
-
|
|
54
|
-
AUTHENTICATED VIEW:
|
|
55
|
-
|
|
56
|
-
@self.router.get(some_path, response=some_schema, auth=self.auth)
|
|
57
|
-
async def some_method(request, *args, **kwargs):
|
|
58
|
-
pass
|
|
59
|
-
|
|
60
|
-
NOT AUTHENTICATED VIEW:
|
|
61
|
-
|
|
62
|
-
@self.router.post(some_path, response=some_schema)
|
|
63
|
-
async def some_method(request, *args, **kwargs):
|
|
64
|
-
pass
|
|
65
|
-
"""
|
|
66
|
-
|
|
67
|
-
def _add_views(self):
|
|
68
|
-
self.views()
|
|
69
|
-
return self.router
|
|
70
|
-
|
|
71
|
-
def add_views_to_route(self):
|
|
72
|
-
return self.api.add_router(f"{self.api_route_path}", self._add_views())
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
class APIViewSet:
|
|
76
|
-
"""
|
|
77
|
-
A base class for creating API views with CRUD operations.
|
|
78
|
-
|
|
79
|
-
This class provides methods for creating, listing, retrieving, updating,
|
|
80
|
-
and deleting objects of a specified model. It supports pagination,
|
|
81
|
-
authentication, and custom query parameters.
|
|
82
|
-
|
|
83
|
-
## Attributes:
|
|
84
|
-
- **model** (`ModelSerializer | Model`): The model for CRUD operations.
|
|
85
|
-
- **api** (`NinjaAPI`): The API instance to which the views are added.
|
|
86
|
-
- **schema_in** (`Schema | None`): Schema for input data in create/update operations.
|
|
87
|
-
- **schema_out** (`Schema | None`): Schema for output data in list/retrieve operations.
|
|
88
|
-
- **schema_update** (`Schema | None`): Schema for update operations.
|
|
89
|
-
- **auth** (`list | None`): Authentication classes for the views.
|
|
90
|
-
- **get_auth** (`list | None`): Authentication for GET requests.
|
|
91
|
-
- **post_auth** (`list | None`): Authentication for POST requests.
|
|
92
|
-
- **patch_auth** (`list | None`): Authentication for PATCH requests.
|
|
93
|
-
- **delete_auth** (`list | None`): Authentication for DELETE requests.
|
|
94
|
-
- **pagination_class** (`type[AsyncPaginationBase]`): Pagination class to use.
|
|
95
|
-
- **query_params** (`dict[str, tuple[type, ...]]`): Query parameters for filtering.
|
|
96
|
-
- **disable** (`list[type[VIEW_TYPES]]`): List of view types to disable.
|
|
97
|
-
- **api_route_path** (`str`): Base path for the API route.
|
|
98
|
-
- **list_docs** (`str`): Documentation for the list view.
|
|
99
|
-
- **create_docs** (`str`): Documentation for the create view.
|
|
100
|
-
- **retrieve_docs** (`str`): Documentation for the retrieve view.
|
|
101
|
-
- **update_docs** (`str`): Documentation for the update view.
|
|
102
|
-
- **delete_docs** (`str`): Documentation for the delete view.
|
|
103
|
-
- **m2m_relations** (`tuple[ModelSerializer | Model, str]`): Many-to-many relations to manage.
|
|
104
|
-
- **m2m_add** (`bool`): Enable add operation for M2M relations.
|
|
105
|
-
- **m2m_remove** (`bool`): Enable remove operation for M2M relations.
|
|
106
|
-
- **m2m_get** (`bool`): Enable get operation for M2M relations.
|
|
107
|
-
- **m2m_auth** (`list | None`): Authentication for M2M views.
|
|
108
|
-
|
|
109
|
-
## Notes:
|
|
110
|
-
If the model is a ModelSerializer instance, schemas are generated
|
|
111
|
-
automatically based on Create, Read, and Update serializers.
|
|
112
|
-
Override the `views` method to add custom views.
|
|
113
|
-
Override the `query_params_handler` method to handle query params
|
|
114
|
-
and return a filtered queryset.
|
|
115
|
-
|
|
116
|
-
## Methods:
|
|
117
|
-
- **create_view**: Creates a new object.
|
|
118
|
-
- **list_view**: Lists all objects.
|
|
119
|
-
- **retrieve_view**: Retrieves an object by its primary key.
|
|
120
|
-
- **update_view**: Updates an object by its primary key.
|
|
121
|
-
- **delete_view**: Deletes an object by its primary key.
|
|
122
|
-
- **views**: Override to add custom views.
|
|
123
|
-
- **add_views_to_route**: Adds the views to the API route.
|
|
124
|
-
|
|
125
|
-
## Example:
|
|
126
|
-
class MyModelViewSet(APIViewSet):
|
|
127
|
-
model = MyModel # Your Django model
|
|
128
|
-
api = my_api_instance # Your NinjaAPI instance
|
|
129
|
-
|
|
130
|
-
MyModelViewSet().add_views_to_route()
|
|
131
|
-
"""
|
|
132
|
-
|
|
133
|
-
model: ModelSerializer | Model
|
|
134
|
-
api: NinjaAPI
|
|
135
|
-
schema_in: Schema | None = None
|
|
136
|
-
schema_out: Schema | None = None
|
|
137
|
-
schema_update: Schema | None = None
|
|
138
|
-
auth: list | None = NOT_SET
|
|
139
|
-
get_auth: list | None = NOT_SET
|
|
140
|
-
post_auth: list | None = NOT_SET
|
|
141
|
-
patch_auth: list | None = NOT_SET
|
|
142
|
-
delete_auth: list | None = NOT_SET
|
|
143
|
-
pagination_class: type[AsyncPaginationBase] = PageNumberPagination
|
|
144
|
-
query_params: dict[str, tuple[type, ...]] = {}
|
|
145
|
-
disable: list[type[VIEW_TYPES]] = []
|
|
146
|
-
api_route_path: str = ""
|
|
147
|
-
list_docs = "List all objects."
|
|
148
|
-
create_docs = "Create a new object."
|
|
149
|
-
retrieve_docs = "Retrieve a specific object by its primary key."
|
|
150
|
-
update_docs = "Update an object by its primary key."
|
|
151
|
-
delete_docs = "Delete an object by its primary key."
|
|
152
|
-
m2m_relations: tuple[ModelSerializer | Model, str] = []
|
|
153
|
-
m2m_add = True
|
|
154
|
-
m2m_remove = True
|
|
155
|
-
m2m_get = True
|
|
156
|
-
m2m_auth: list | None = NOT_SET
|
|
157
|
-
|
|
158
|
-
def __init__(self) -> None:
|
|
159
|
-
self.error_codes = ERROR_CODES
|
|
160
|
-
self.model_util = ModelUtil(self.model)
|
|
161
|
-
self.schema_out, self.schema_in, self.schema_update = self.get_schemas()
|
|
162
|
-
self.path_schema = self._generate_path_schema()
|
|
163
|
-
self.filters_schema = self._generate_filters_schema()
|
|
164
|
-
self.model_verbose_name = self.model._meta.verbose_name.capitalize()
|
|
165
|
-
self.router_tag = self.model_verbose_name
|
|
166
|
-
self.router = Router(tags=[self.router_tag])
|
|
167
|
-
self.path = "/"
|
|
168
|
-
self.get_path = ""
|
|
169
|
-
self.path_retrieve = f"{{{self.model_util.model_pk_name}}}/"
|
|
170
|
-
self.get_path_retrieve = f"{{{self.model_util.model_pk_name}}}"
|
|
171
|
-
self.api_route_path = (
|
|
172
|
-
self.api_route_path or self.model_util.verbose_name_path_resolver()
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
@property
|
|
176
|
-
def _crud_views(self):
|
|
177
|
-
"""
|
|
178
|
-
key: view type (create, list, retrieve, update, delete or all)
|
|
179
|
-
value: tuple with schema and view method
|
|
180
|
-
"""
|
|
181
|
-
return {
|
|
182
|
-
"create": (self.schema_in, self.create_view),
|
|
183
|
-
"list": (self.schema_out, self.list_view),
|
|
184
|
-
"retrieve": (self.schema_out, self.retrieve_view),
|
|
185
|
-
"update": (self.schema_update, self.update_view),
|
|
186
|
-
"delete": (None, self.delete_view),
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
def _auth_view(self, view_type: str):
|
|
190
|
-
auth = getattr(self, f"{view_type}_auth", None)
|
|
191
|
-
return auth if auth is not NOT_SET else self.auth
|
|
192
|
-
|
|
193
|
-
def get_view_auth(self):
|
|
194
|
-
return self._auth_view("get")
|
|
195
|
-
|
|
196
|
-
def post_view_auth(self):
|
|
197
|
-
return self._auth_view("post")
|
|
198
|
-
|
|
199
|
-
def patch_view_auth(self):
|
|
200
|
-
return self._auth_view("patch")
|
|
201
|
-
|
|
202
|
-
def delete_view_auth(self):
|
|
203
|
-
return self._auth_view("delete")
|
|
204
|
-
|
|
205
|
-
def _generate_schema(self, fields: dict, name: str) -> Schema:
|
|
206
|
-
return create_model(f"{self.model_util.model_name}{name}", **fields)
|
|
207
|
-
|
|
208
|
-
def _generate_path_schema(self):
|
|
209
|
-
return self._generate_schema(
|
|
210
|
-
{self.model_util.model_pk_name: (int | str, ...)}, "PathSchema"
|
|
211
|
-
)
|
|
212
|
-
|
|
213
|
-
def _generate_filters_schema(self):
|
|
214
|
-
return self._generate_schema(self.query_params, "FiltersSchema")
|
|
215
|
-
|
|
216
|
-
def _get_pk(self, data: Schema):
|
|
217
|
-
return data.model_dump()[self.model_util.model_pk_name]
|
|
218
|
-
|
|
219
|
-
def get_schemas(self):
|
|
220
|
-
if isinstance(self.model, ModelSerializerMeta):
|
|
221
|
-
return (
|
|
222
|
-
self.model.generate_read_s(),
|
|
223
|
-
self.model.generate_create_s(),
|
|
224
|
-
self.model.generate_update_s(),
|
|
225
|
-
)
|
|
226
|
-
return self.schema_out, self.schema_in, self.schema_update
|
|
227
|
-
|
|
228
|
-
async def query_params_handler(
|
|
229
|
-
self, queryset: QuerySet[ModelSerializer], filters: dict
|
|
230
|
-
):
|
|
231
|
-
"""
|
|
232
|
-
Override this method to handle request query params making queries to the database
|
|
233
|
-
based on filters or any other logic. This method should return a queryset. filters
|
|
234
|
-
are given already dumped by the schema.
|
|
235
|
-
"""
|
|
236
|
-
return queryset
|
|
237
|
-
|
|
238
|
-
def create_view(self):
|
|
239
|
-
@self.router.post(
|
|
240
|
-
self.path,
|
|
241
|
-
auth=self.post_view_auth(),
|
|
242
|
-
summary=f"Create {self.model._meta.verbose_name.capitalize()}",
|
|
243
|
-
description=self.create_docs,
|
|
244
|
-
response={201: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
245
|
-
)
|
|
246
|
-
async def create(request: HttpRequest, data: self.schema_in): # type: ignore
|
|
247
|
-
return 201, await self.model_util.create_s(request, data, self.schema_out)
|
|
248
|
-
|
|
249
|
-
create.__name__ = f"create_{self.model_util.model_name}"
|
|
250
|
-
return create
|
|
251
|
-
|
|
252
|
-
def list_view(self):
|
|
253
|
-
@self.router.get(
|
|
254
|
-
self.get_path,
|
|
255
|
-
auth=self.get_view_auth(),
|
|
256
|
-
summary=f"List {self.model._meta.verbose_name_plural.capitalize()}",
|
|
257
|
-
description=self.list_docs,
|
|
258
|
-
response={
|
|
259
|
-
200: List[self.schema_out],
|
|
260
|
-
self.error_codes: GenericMessageSchema,
|
|
261
|
-
},
|
|
262
|
-
)
|
|
263
|
-
@paginate(self.pagination_class)
|
|
264
|
-
async def list(
|
|
265
|
-
request: HttpRequest,
|
|
266
|
-
filters: Query[self.filters_schema] = None, # type: ignore
|
|
267
|
-
):
|
|
268
|
-
qs = self.model.objects.select_related()
|
|
269
|
-
if isinstance(self.model, ModelSerializerMeta):
|
|
270
|
-
qs = await self.model.queryset_request(request)
|
|
271
|
-
rels = self.model_util.get_reverse_relations()
|
|
272
|
-
if len(rels) > 0:
|
|
273
|
-
qs = qs.prefetch_related(*rels)
|
|
274
|
-
if filters is not None:
|
|
275
|
-
qs = await self.query_params_handler(qs, filters.model_dump())
|
|
276
|
-
objs = [
|
|
277
|
-
await self.model_util.read_s(request, obj, self.schema_out)
|
|
278
|
-
async for obj in qs.all()
|
|
279
|
-
]
|
|
280
|
-
return objs
|
|
281
|
-
|
|
282
|
-
list.__name__ = f"list_{self.model_util.verbose_name_view_resolver()}"
|
|
283
|
-
return list
|
|
284
|
-
|
|
285
|
-
def retrieve_view(self):
|
|
286
|
-
@self.router.get(
|
|
287
|
-
self.get_path_retrieve,
|
|
288
|
-
auth=self.get_view_auth(),
|
|
289
|
-
summary=f"Retrieve {self.model._meta.verbose_name.capitalize()}",
|
|
290
|
-
description=self.retrieve_docs,
|
|
291
|
-
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
292
|
-
)
|
|
293
|
-
async def retrieve(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
294
|
-
obj = await self.model_util.get_object(request, self._get_pk(pk))
|
|
295
|
-
return await self.model_util.read_s(request, obj, self.schema_out)
|
|
296
|
-
|
|
297
|
-
retrieve.__name__ = f"retrieve_{self.model_util.model_name}"
|
|
298
|
-
return retrieve
|
|
299
|
-
|
|
300
|
-
def update_view(self):
|
|
301
|
-
@self.router.patch(
|
|
302
|
-
self.path_retrieve,
|
|
303
|
-
auth=self.patch_view_auth(),
|
|
304
|
-
summary=f"Update {self.model._meta.verbose_name.capitalize()}",
|
|
305
|
-
description=self.update_docs,
|
|
306
|
-
response={200: self.schema_out, self.error_codes: GenericMessageSchema},
|
|
307
|
-
)
|
|
308
|
-
async def update(
|
|
309
|
-
request: HttpRequest,
|
|
310
|
-
data: self.schema_update, # type: ignore
|
|
311
|
-
pk: Path[self.path_schema], # type: ignore
|
|
312
|
-
):
|
|
313
|
-
return await self.model_util.update_s(
|
|
314
|
-
request, data, self._get_pk(pk), self.schema_out
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
update.__name__ = f"update_{self.model_util.model_name}"
|
|
318
|
-
return update
|
|
319
|
-
|
|
320
|
-
def delete_view(self):
|
|
321
|
-
@self.router.delete(
|
|
322
|
-
self.path_retrieve,
|
|
323
|
-
auth=self.delete_view_auth(),
|
|
324
|
-
summary=f"Delete {self.model._meta.verbose_name.capitalize()}",
|
|
325
|
-
description=self.delete_docs,
|
|
326
|
-
response={204: None, self.error_codes: GenericMessageSchema},
|
|
327
|
-
)
|
|
328
|
-
async def delete(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
329
|
-
return 204, await self.model_util.delete_s(request, self._get_pk(pk))
|
|
330
|
-
|
|
331
|
-
delete.__name__ = f"delete_{self.model_util.model_name}"
|
|
332
|
-
return delete
|
|
333
|
-
|
|
334
|
-
def views(self):
|
|
335
|
-
"""
|
|
336
|
-
Override this method to add your custom views. For example:
|
|
337
|
-
@self.router.get(some_path, response=some_schema)
|
|
338
|
-
async def some_method(request, *args, **kwargs):
|
|
339
|
-
pass
|
|
340
|
-
|
|
341
|
-
You can add multilple views just doing:
|
|
342
|
-
|
|
343
|
-
@self.router.get(some_path, response=some_schema)
|
|
344
|
-
async def some_method(request, *args, **kwargs):
|
|
345
|
-
pass
|
|
346
|
-
|
|
347
|
-
@self.router.post(some_path, response=some_schema)
|
|
348
|
-
async def some_method(request, *args, **kwargs):
|
|
349
|
-
pass
|
|
350
|
-
|
|
351
|
-
If you provided a list of auths you can chose which of your views
|
|
352
|
-
should be authenticated:
|
|
353
|
-
|
|
354
|
-
AUTHENTICATED VIEW:
|
|
355
|
-
|
|
356
|
-
@self.router.get(some_path, response=some_schema, auth=self.auth)
|
|
357
|
-
async def some_method(request, *args, **kwargs):
|
|
358
|
-
pass
|
|
359
|
-
|
|
360
|
-
NOT AUTHENTICATED VIEW:
|
|
361
|
-
|
|
362
|
-
@self.router.post(some_path, response=some_schema)
|
|
363
|
-
async def some_method(request, *args, **kwargs):
|
|
364
|
-
pass
|
|
365
|
-
"""
|
|
366
|
-
|
|
367
|
-
async def _check_m2m_objs(
|
|
368
|
-
self,
|
|
369
|
-
request: HttpRequest,
|
|
370
|
-
objs_pks: list,
|
|
371
|
-
model: ModelSerializer | Model,
|
|
372
|
-
related_manager: QuerySet,
|
|
373
|
-
remove: bool = False,
|
|
374
|
-
):
|
|
375
|
-
errors, objs_detail, objs = [], [], []
|
|
376
|
-
rel_objs = [rel_obj async for rel_obj in related_manager.select_related().all()]
|
|
377
|
-
rel_model_name = model._meta.verbose_name.capitalize()
|
|
378
|
-
for obj_pk in objs_pks:
|
|
379
|
-
rel_obj = await (
|
|
380
|
-
await ModelUtil(model).get_object(request, filters={"pk": obj_pk})
|
|
381
|
-
).afirst()
|
|
382
|
-
if rel_obj is None:
|
|
383
|
-
errors.append(f"{rel_model_name} with pk {obj_pk} not found.")
|
|
384
|
-
continue
|
|
385
|
-
if remove ^ (rel_obj in rel_objs):
|
|
386
|
-
errors.append(
|
|
387
|
-
f"{rel_model_name} with id {obj_pk} is {'not ' if remove else ''} in {self.model_util.model_name}"
|
|
388
|
-
)
|
|
389
|
-
continue
|
|
390
|
-
objs.append(rel_obj)
|
|
391
|
-
objs_detail.append(
|
|
392
|
-
f"{rel_model_name} with id {obj_pk} successfully {'removed' if remove else 'added'}"
|
|
393
|
-
)
|
|
394
|
-
return errors, objs_detail, objs
|
|
395
|
-
|
|
396
|
-
def _m2m_views(self):
|
|
397
|
-
for model, related_name in self.m2m_relations:
|
|
398
|
-
rel_util = ModelUtil(model)
|
|
399
|
-
rel_path = rel_util.verbose_name_path_resolver()
|
|
400
|
-
if self.m2m_get:
|
|
401
|
-
|
|
402
|
-
@self.router.get(
|
|
403
|
-
f"{self.path_retrieve}{rel_path}",
|
|
404
|
-
response={
|
|
405
|
-
200: List[model.generate_related_s(),],
|
|
406
|
-
self.error_codes: GenericMessageSchema,
|
|
407
|
-
},
|
|
408
|
-
auth=self.m2m_auth,
|
|
409
|
-
summary=f"Get {rel_util.model._meta.verbose_name_plural.capitalize()}",
|
|
410
|
-
description=f"Get all related {rel_util.model._meta.verbose_name_plural.capitalize()}",
|
|
411
|
-
)
|
|
412
|
-
@paginate(self.pagination_class)
|
|
413
|
-
async def get_related(request: HttpRequest, pk: Path[self.path_schema]): # type: ignore
|
|
414
|
-
obj = await self.model_util.get_object(request, self._get_pk(pk))
|
|
415
|
-
related_manager = getattr(obj, related_name)
|
|
416
|
-
related_qs = related_manager.all()
|
|
417
|
-
related_objs = [
|
|
418
|
-
await rel_util.read_s(
|
|
419
|
-
request, rel_obj, model.generate_related_s()
|
|
420
|
-
)
|
|
421
|
-
async for rel_obj in related_qs
|
|
422
|
-
]
|
|
423
|
-
return related_objs
|
|
424
|
-
|
|
425
|
-
get_related.__name__ = f"get_{self.model_util.model_name}_{rel_path}"
|
|
426
|
-
|
|
427
|
-
if self.m2m_add or self.m2m_remove:
|
|
428
|
-
summary = f"{'Add or Remove' if self.m2m_add and self.m2m_remove else 'Add' if self.m2m_add else 'Remove'} {rel_util.model._meta.verbose_name_plural.capitalize()}"
|
|
429
|
-
description = f"{'Add or remove' if self.m2m_add and self.m2m_remove else 'Add' if self.m2m_add else 'Remove'} {rel_util.model._meta.verbose_name_plural.capitalize()}"
|
|
430
|
-
schema_in = (
|
|
431
|
-
M2MSchemaIn
|
|
432
|
-
if self.m2m_add and self.m2m_remove
|
|
433
|
-
else M2MAddSchemaIn
|
|
434
|
-
if self.m2m_add
|
|
435
|
-
else M2MRemoveSchemaIn
|
|
436
|
-
)
|
|
437
|
-
|
|
438
|
-
@self.router.post(
|
|
439
|
-
f"{self.path_retrieve}{rel_path}/",
|
|
440
|
-
response={
|
|
441
|
-
200: M2MSchemaOut,
|
|
442
|
-
self.error_codes: GenericMessageSchema,
|
|
443
|
-
},
|
|
444
|
-
auth=self.m2m_auth,
|
|
445
|
-
summary=summary,
|
|
446
|
-
description=description,
|
|
447
|
-
)
|
|
448
|
-
async def manage_related(
|
|
449
|
-
request: HttpRequest,
|
|
450
|
-
pk: Path[self.path_schema], # type: ignore
|
|
451
|
-
data: schema_in, # type: ignore
|
|
452
|
-
):
|
|
453
|
-
obj = await self.model_util.get_object(
|
|
454
|
-
request, self._get_pk(pk)
|
|
455
|
-
)
|
|
456
|
-
related_manager: QuerySet = getattr(obj, related_name)
|
|
457
|
-
add_errors, add_details, add_objs = [], [], []
|
|
458
|
-
remove_errors, remove_details, remove_objs = [], [], []
|
|
459
|
-
|
|
460
|
-
if self.m2m_add and hasattr(data, "add"):
|
|
461
|
-
(
|
|
462
|
-
add_errors,
|
|
463
|
-
add_details,
|
|
464
|
-
add_objs,
|
|
465
|
-
) = await self._check_m2m_objs(
|
|
466
|
-
request, data.add, model, related_manager
|
|
467
|
-
)
|
|
468
|
-
if self.m2m_remove and hasattr(data, "remove"):
|
|
469
|
-
(
|
|
470
|
-
remove_errors,
|
|
471
|
-
remove_details,
|
|
472
|
-
remove_objs,
|
|
473
|
-
) = await self._check_m2m_objs(
|
|
474
|
-
request,
|
|
475
|
-
data.remove,
|
|
476
|
-
model,
|
|
477
|
-
related_manager,
|
|
478
|
-
remove=True,
|
|
479
|
-
)
|
|
480
|
-
|
|
481
|
-
await asyncio.gather(
|
|
482
|
-
related_manager.aadd(*add_objs),
|
|
483
|
-
related_manager.aremove(*remove_objs),
|
|
484
|
-
)
|
|
485
|
-
results = add_details + remove_details
|
|
486
|
-
errors = add_errors + remove_errors
|
|
487
|
-
|
|
488
|
-
return {
|
|
489
|
-
"results": {
|
|
490
|
-
"count": len(results),
|
|
491
|
-
"details": results,
|
|
492
|
-
},
|
|
493
|
-
"errors": {
|
|
494
|
-
"count": len(errors),
|
|
495
|
-
"details": errors,
|
|
496
|
-
},
|
|
497
|
-
}
|
|
498
|
-
|
|
499
|
-
manage_related.__name__ = (
|
|
500
|
-
f"manage_{self.model_util.model_name}_{rel_path}"
|
|
501
|
-
)
|
|
502
|
-
|
|
503
|
-
def _add_views(self):
|
|
504
|
-
if "all" in self.disable:
|
|
505
|
-
if self.m2m_relations:
|
|
506
|
-
self._m2m_views()
|
|
507
|
-
self.views()
|
|
508
|
-
return self.router
|
|
509
|
-
|
|
510
|
-
for views_type, (schema, view) in self._crud_views.items():
|
|
511
|
-
if views_type not in self.disable and (
|
|
512
|
-
schema is not None or views_type == "delete"
|
|
513
|
-
):
|
|
514
|
-
view()
|
|
515
|
-
|
|
516
|
-
self.views()
|
|
517
|
-
if self.m2m_relations:
|
|
518
|
-
self._m2m_views()
|
|
519
|
-
return self.router
|
|
520
|
-
|
|
521
|
-
def add_views_to_route(self):
|
|
522
|
-
return self.api.add_router(f"{self.api_route_path}", self._add_views())
|
|
File without changes
|
{django_ninja_aio_crud-0.10.2.dist-info → django_ninja_aio_crud-2.4.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|