django-ninja-aio-crud 2.0.0rc2__py3-none-any.whl → 2.0.0rc3__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.0.0rc2.dist-info → django_ninja_aio_crud-2.0.0rc3.dist-info}/METADATA +1 -1
- {django_ninja_aio_crud-2.0.0rc2.dist-info → django_ninja_aio_crud-2.0.0rc3.dist-info}/RECORD +6 -6
- ninja_aio/__init__.py +1 -1
- ninja_aio/helpers/api.py +110 -57
- {django_ninja_aio_crud-2.0.0rc2.dist-info → django_ninja_aio_crud-2.0.0rc3.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.0.0rc2.dist-info → django_ninja_aio_crud-2.0.0rc3.dist-info}/licenses/LICENSE +0 -0
{django_ninja_aio_crud-2.0.0rc2.dist-info → django_ninja_aio_crud-2.0.0rc3.dist-info}/RECORD
RENAMED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=
|
|
1
|
+
ninja_aio/__init__.py,sha256=JcpcShlXZ4BnQ_O8zXApC5ZgzwxHrruInN3QK3tZgps,123
|
|
2
2
|
ninja_aio/api.py,sha256=SS1TYUiFkdYjfJLVy6GI90GOzvIHzPEeL-UcqWFRHkM,1684
|
|
3
3
|
ninja_aio/auth.py,sha256=8jaEp7oEJvUUB9EuyE2fOYk-khyAaekT3i80E7AbgOA,5101
|
|
4
4
|
ninja_aio/decorators.py,sha256=gswkwl1zWSpW8VxGCe8MlgXcHMg6Y7V1f2ertey9Tjo,5522
|
|
@@ -9,13 +9,13 @@ ninja_aio/renders.py,sha256=5TdSQI8e4x3Gb2tAw1AaxrbU-asVjf2chWMr8x2Tt80,1485
|
|
|
9
9
|
ninja_aio/types.py,sha256=TJSGlA7bt4g9fvPhJ7gzH5tKbLagPmZUzfgttEOp4xs,468
|
|
10
10
|
ninja_aio/views.py,sha256=u6WVXvErF3DAOTONTH_EhSg-5qhFBhtR4L2lNrt00qo,16331
|
|
11
11
|
ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
ninja_aio/helpers/api.py,sha256=
|
|
12
|
+
ninja_aio/helpers/api.py,sha256=F6SlvNCEEAm-fzqpTB5r0O9gqhbP0dvyieNcG1JlKCw,20181
|
|
13
13
|
ninja_aio/helpers/query.py,sha256=tE8RjXvSig-WB_0LRQ0LqoE4G_HMHsu0Na5QzTNIm6U,4262
|
|
14
14
|
ninja_aio/schemas/__init__.py,sha256=iLBwHg0pmL9k_UkIui5Q8QIl_gO4fgxSv2JHxDzqnSI,549
|
|
15
15
|
ninja_aio/schemas/api.py,sha256=-VwXhBRhmMsZLIAmWJ-P7tB5klxXS75eukjabeKKYsc,360
|
|
16
16
|
ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
|
|
17
17
|
ninja_aio/schemas/helpers.py,sha256=al1G5-CdB9CF3eIPdZcUSE7vKRJiTU5g1ltoPhI2Onw,2622
|
|
18
|
-
django_ninja_aio_crud-2.0.
|
|
19
|
-
django_ninja_aio_crud-2.0.
|
|
20
|
-
django_ninja_aio_crud-2.0.
|
|
21
|
-
django_ninja_aio_crud-2.0.
|
|
18
|
+
django_ninja_aio_crud-2.0.0rc3.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
19
|
+
django_ninja_aio_crud-2.0.0rc3.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
20
|
+
django_ninja_aio_crud-2.0.0rc3.dist-info/METADATA,sha256=_srbK-mfXF3zuPPmP1YK2zssec21f2W4CnowtGgHgR4,8675
|
|
21
|
+
django_ninja_aio_crud-2.0.0rc3.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/helpers/api.py
CHANGED
|
@@ -38,27 +38,34 @@ class ManyToManyAPI:
|
|
|
38
38
|
Core behaviors:
|
|
39
39
|
- Dynamically generates per-relation filter schemas for query parameters.
|
|
40
40
|
- Supports custom per-relation query filtering handlers on the parent view set
|
|
41
|
-
|
|
41
|
+
via a `{related_name}_query_params_handler` coroutine for GET list filters.
|
|
42
|
+
- Supports custom per-relation object resolution for add/remove validation via
|
|
43
|
+
`{related_name}_query_handler(request, pk, instance)` used during POST to
|
|
44
|
+
resolve a single related object before mutation.
|
|
42
45
|
- Validates requested add/remove primary keys, producing granular success and
|
|
43
|
-
|
|
46
|
+
error feedback.
|
|
44
47
|
- Performs add/remove operations concurrently using asyncio.gather when both
|
|
45
|
-
|
|
48
|
+
types of operations are requested in the same call.
|
|
46
49
|
|
|
47
50
|
Attributes established at initialization:
|
|
48
51
|
relations: list of M2MRelationSchema defining each relation.
|
|
49
52
|
view_set: The parent APIViewSet instance from which router, pagination, model util,
|
|
50
|
-
|
|
53
|
+
and path schema are derived.
|
|
51
54
|
router: Ninja router used to register generated endpoints.
|
|
52
55
|
pagination_class: Pagination class used for GET related endpoints.
|
|
53
56
|
path_schema: Pydantic schema used to validate path parameters (e.g., primary key).
|
|
54
57
|
related_model_util: A ModelUtil instance cloned from the parent view set to access
|
|
55
|
-
|
|
58
|
+
base object retrieval helpers.
|
|
56
59
|
relations_filters_schemas: Mapping of related_name -> generated Pydantic filter schema.
|
|
57
60
|
|
|
58
61
|
Generated endpoint naming conventions:
|
|
59
62
|
GET -> get_{base_model_name}_{relation_path}
|
|
60
63
|
POST -> manage_{base_model_name}_{relation_path}
|
|
61
64
|
|
|
65
|
+
Endpoint registration details:
|
|
66
|
+
- GET: registered at `{retrieve_path}{relation_path}` with pagination; accepts Query filters.
|
|
67
|
+
- POST: registered at `{retrieve_path}{relation_path}/` (trailing slash) to manage add/remove.
|
|
68
|
+
|
|
62
69
|
All responses standardize success and error reporting for POST as:
|
|
63
70
|
{
|
|
64
71
|
"results": {"count": int, "details": [str, ...]},
|
|
@@ -66,25 +73,29 @@ class ManyToManyAPI:
|
|
|
66
73
|
}
|
|
67
74
|
|
|
68
75
|
Concurrency note:
|
|
69
|
-
Add and remove operations are executed concurrently when both lists are non-empty,
|
|
70
|
-
|
|
76
|
+
- Add and remove operations are executed concurrently when both lists are non-empty,
|
|
77
|
+
minimizing round-trip latency for bulk mutations.
|
|
78
|
+
- Uses related_manager.aadd(...) and related_manager.aremove(...) inside asyncio.gather.
|
|
71
79
|
|
|
72
80
|
Error semantics:
|
|
73
81
|
- Missing related objects: reported individually.
|
|
74
82
|
- Invalid operation context (e.g., removing objects not currently related or adding
|
|
75
|
-
|
|
83
|
+
objects already related) reported per primary key.
|
|
76
84
|
- Successful operations yield a corresponding success detail string per PK.
|
|
77
85
|
|
|
78
86
|
Security / auth:
|
|
79
87
|
- Each relation may optionally override auth via its schema; otherwise falls back
|
|
80
|
-
|
|
88
|
+
to a default configured on the instance (self.default_auth).
|
|
81
89
|
|
|
82
90
|
Pagination:
|
|
83
91
|
- Applied only to GET related endpoints via @paginate(self.pagination_class).
|
|
84
92
|
|
|
85
93
|
Extensibility:
|
|
86
94
|
- Provide custom query param handling by defining an async method on the parent
|
|
87
|
-
|
|
95
|
+
view set: `<related_name>_query_params_handler(self, queryset, filters_dict)`.
|
|
96
|
+
- Provide custom per-PK resolution for POST validation by defining an async method:
|
|
97
|
+
`<related_name>_query_handler(self, request, pk, instance)` returning a queryset,
|
|
98
|
+
from which .afirst() is used to resolve the single target object.
|
|
88
99
|
- Customize relation filtering schema via each relation's `filters` definition.
|
|
89
100
|
|
|
90
101
|
-----------------------------------------------------------------------
|
|
@@ -114,9 +125,9 @@ class ManyToManyAPI:
|
|
|
114
125
|
|
|
115
126
|
-----------------------------------------------------------------------
|
|
116
127
|
|
|
117
|
-
|
|
118
|
-
Retrieve an optional per-relation query handler
|
|
119
|
-
Naming convention: `<related_name
|
|
128
|
+
_get_query_params_handler(related_name)
|
|
129
|
+
Retrieve an optional per-relation query handler from the parent view set for GET list filters.
|
|
130
|
+
Naming convention: `<related_name}_query_params_handler`.
|
|
120
131
|
|
|
121
132
|
Parameters:
|
|
122
133
|
related_name (str): The relation's attribute name on the base model.
|
|
@@ -126,16 +137,32 @@ class ManyToManyAPI:
|
|
|
126
137
|
|
|
127
138
|
-----------------------------------------------------------------------
|
|
128
139
|
|
|
129
|
-
|
|
140
|
+
_get_query_handler(related_name)
|
|
141
|
+
Retrieve an optional per-relation single-object resolution handler from the parent view set
|
|
142
|
+
used during POST add/remove validation. If present, it receives `(request, pk, instance)` and
|
|
143
|
+
should return a queryset from which `.afirst()` will resolve the target object.
|
|
144
|
+
|
|
145
|
+
Parameters:
|
|
146
|
+
related_name (str): The relation's attribute name on the base model.
|
|
147
|
+
|
|
148
|
+
Returns:
|
|
149
|
+
Coroutine | None: Handler to resolve a single related object by pk.
|
|
150
|
+
|
|
151
|
+
-----------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
_check_m2m_objs(request, objs_pks, related_model, related_manager, related_name, instance, remove=False)
|
|
130
154
|
Validate requested primary keys for add/remove operations against the current
|
|
131
155
|
relation state. Performs existence checks and logical consistency (e.g., prevents
|
|
132
|
-
adding already-related objects or removing non-related objects).
|
|
156
|
+
adding already-related objects or removing non-related objects). Uses `_get_query_handler`
|
|
157
|
+
when available; otherwise falls back to ModelUtil(...).get_objects(...) to resolve by pk.
|
|
133
158
|
|
|
134
159
|
Parameters:
|
|
135
160
|
request (HttpRequest): Incoming request context (passed to ModelUtil for access control).
|
|
136
161
|
objs_pks (list): List of primary keys to add or remove.
|
|
137
|
-
|
|
162
|
+
related_model (ModelSerializer | Model): Model class or serializer used to resolve objects.
|
|
138
163
|
related_manager (QuerySet): Related manager for the base object's M2M field.
|
|
164
|
+
related_name (str): M2M field name on the base object.
|
|
165
|
+
instance (ModelSerializer | Model): Base object instance (owner of the relation).
|
|
139
166
|
remove (bool): If True, treat operation as removal validation.
|
|
140
167
|
|
|
141
168
|
Returns:
|
|
@@ -150,7 +177,7 @@ class ManyToManyAPI:
|
|
|
150
177
|
|
|
151
178
|
-----------------------------------------------------------------------
|
|
152
179
|
|
|
153
|
-
_collect_m2m(request, pks, model, related_manager, remove=False)
|
|
180
|
+
_collect_m2m(request, pks, model, related_manager, related_name, instance, remove=False)
|
|
154
181
|
Wrapper around _check_m2m_objs that short-circuits on empty PK lists.
|
|
155
182
|
|
|
156
183
|
Parameters:
|
|
@@ -158,6 +185,8 @@ class ManyToManyAPI:
|
|
|
158
185
|
pks (list): Primary keys proposed for mutation.
|
|
159
186
|
model (ModelSerializer | Model)
|
|
160
187
|
related_manager (QuerySet)
|
|
188
|
+
related_name (str)
|
|
189
|
+
instance (ModelSerializer | Model)
|
|
161
190
|
remove (bool): Operation type flag.
|
|
162
191
|
|
|
163
192
|
Returns:
|
|
@@ -165,38 +194,30 @@ class ManyToManyAPI:
|
|
|
165
194
|
|
|
166
195
|
-----------------------------------------------------------------------
|
|
167
196
|
|
|
197
|
+
_register_get_relation_view(...)
|
|
198
|
+
Registers the GET endpoint for listing related objects. Applies optional
|
|
199
|
+
query params handler for filtering. Uses pagination and serializes via `list_read_s`.
|
|
200
|
+
|
|
201
|
+
-----------------------------------------------------------------------
|
|
202
|
+
|
|
203
|
+
_register_manage_relation_view(...)
|
|
204
|
+
Registers the POST endpoint for adding/removing related objects. Validates via
|
|
205
|
+
`_collect_m2m` and executes mutations concurrently using `aadd`/`aremove` with
|
|
206
|
+
`asyncio.gather`. Aggregates per-PK results and errors into a standardized payload.
|
|
207
|
+
|
|
208
|
+
-----------------------------------------------------------------------
|
|
209
|
+
|
|
168
210
|
_build_views(relation)
|
|
169
211
|
Dynamically define and register the GET and/or POST endpoints for a single M2M
|
|
170
212
|
relation based on the relation's schema flags (get/add/remove). Builds filter
|
|
171
213
|
schemas, resolves path fragments, and binds handlers to the router with unique
|
|
172
214
|
operation IDs.
|
|
173
215
|
|
|
174
|
-
Parameters:
|
|
175
|
-
relation (M2MRelationSchema): Declarative specification for one M2M relation.
|
|
176
|
-
|
|
177
|
-
Side effects:
|
|
178
|
-
- Registers endpoints on self.router.
|
|
179
|
-
- Creates closures (get_related / manage_related) capturing relation context.
|
|
180
|
-
|
|
181
|
-
GET endpoint behavior:
|
|
182
|
-
- Retrieves base object via related_model_util.
|
|
183
|
-
- Fetches all related objects; applies optional query handler and filters.
|
|
184
|
-
- Serializes each related object with rel_util.read_s.
|
|
185
|
-
|
|
186
|
-
POST endpoint behavior:
|
|
187
|
-
- Parses add/remove PK lists.
|
|
188
|
-
- Validates objects via _collect_m2m.
|
|
189
|
-
- Performs asynchronous add/remove operations using aadd / aremove.
|
|
190
|
-
- Aggregates results and errors into standardized response payload.
|
|
191
|
-
|
|
192
216
|
-----------------------------------------------------------------------
|
|
193
217
|
|
|
194
218
|
_add_views()
|
|
195
219
|
Iterates over all declared relations and invokes _build_views to attach endpoints.
|
|
196
220
|
|
|
197
|
-
Side effects:
|
|
198
|
-
- Populates router with all required M2M endpoints.
|
|
199
|
-
|
|
200
221
|
-----------------------------------------------------------------------
|
|
201
222
|
|
|
202
223
|
Usage Example (conceptual):
|
|
@@ -241,30 +262,45 @@ class ManyToManyAPI:
|
|
|
241
262
|
for data in self.relations
|
|
242
263
|
}
|
|
243
264
|
|
|
244
|
-
def
|
|
265
|
+
def _get_query_params_handler(self, related_name: str) -> Coroutine | None:
|
|
245
266
|
return getattr(self.view_set, f"{related_name}_query_params_handler", None)
|
|
246
267
|
|
|
268
|
+
def _get_query_handler(self, related_name: str) -> Coroutine | None:
|
|
269
|
+
return getattr(self.view_set, f"{related_name}_query_handler", None)
|
|
270
|
+
|
|
247
271
|
async def _check_m2m_objs(
|
|
248
272
|
self,
|
|
249
273
|
request: HttpRequest,
|
|
250
274
|
objs_pks: list,
|
|
251
|
-
|
|
275
|
+
related_model: ModelSerializer | Model,
|
|
252
276
|
related_manager: QuerySet,
|
|
277
|
+
related_name: str,
|
|
278
|
+
instance: ModelSerializer | Model,
|
|
253
279
|
remove: bool = False,
|
|
254
280
|
):
|
|
255
281
|
"""
|
|
256
282
|
Validate requested add/remove pk list for M2M operations.
|
|
257
283
|
Returns (errors, details, objects_to_process).
|
|
284
|
+
Uses per-PK query handler if available, else falls back to ModelUtil lookup by pk.
|
|
258
285
|
"""
|
|
259
286
|
errors, objs_detail, objs = [], [], []
|
|
260
287
|
rel_objs = [rel_obj async for rel_obj in related_manager.select_related().all()]
|
|
261
|
-
rel_model_name =
|
|
288
|
+
rel_model_name = related_model._meta.verbose_name.capitalize()
|
|
262
289
|
for obj_pk in objs_pks:
|
|
263
|
-
|
|
264
|
-
await
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
290
|
+
if query_handler := self._get_query_handler(related_name):
|
|
291
|
+
rel_obj = await (
|
|
292
|
+
await query_handler(
|
|
293
|
+
request,
|
|
294
|
+
obj_pk,
|
|
295
|
+
instance,
|
|
296
|
+
)
|
|
297
|
+
).afirst()
|
|
298
|
+
else:
|
|
299
|
+
rel_obj = await (
|
|
300
|
+
await ModelUtil(related_model).get_objects(
|
|
301
|
+
request, query_data=ObjectsQuerySchema(filters={"pk": obj_pk})
|
|
302
|
+
)
|
|
303
|
+
).afirst()
|
|
268
304
|
if rel_obj is None:
|
|
269
305
|
errors.append(f"{rel_model_name} with pk {obj_pk} not found.")
|
|
270
306
|
continue
|
|
@@ -283,14 +319,22 @@ class ManyToManyAPI:
|
|
|
283
319
|
self,
|
|
284
320
|
request: HttpRequest,
|
|
285
321
|
pks: list,
|
|
286
|
-
|
|
322
|
+
reletad_model: ModelSerializer | Model,
|
|
287
323
|
related_manager: QuerySet,
|
|
288
|
-
|
|
324
|
+
related_name: str,
|
|
325
|
+
instance: ModelSerializer | Model,
|
|
326
|
+
remove: bool = False,
|
|
289
327
|
):
|
|
290
328
|
if not pks:
|
|
291
329
|
return ([], [], [])
|
|
292
330
|
return await self._check_m2m_objs(
|
|
293
|
-
request,
|
|
331
|
+
request,
|
|
332
|
+
pks,
|
|
333
|
+
reletad_model,
|
|
334
|
+
related_manager,
|
|
335
|
+
related_name,
|
|
336
|
+
instance,
|
|
337
|
+
remove=remove,
|
|
294
338
|
)
|
|
295
339
|
|
|
296
340
|
def _register_get_relation_view(
|
|
@@ -326,16 +370,14 @@ class ManyToManyAPI:
|
|
|
326
370
|
related_manager = getattr(obj, related_name)
|
|
327
371
|
related_qs = related_manager.all()
|
|
328
372
|
|
|
329
|
-
query_handler = self.
|
|
373
|
+
query_handler = self._get_query_params_handler(related_name)
|
|
330
374
|
if filters is not None and query_handler:
|
|
331
375
|
if asyncio.iscoroutinefunction(query_handler):
|
|
332
376
|
related_qs = await query_handler(related_qs, filters.model_dump())
|
|
333
377
|
else:
|
|
334
378
|
related_qs = query_handler(related_qs, filters.model_dump())
|
|
335
379
|
|
|
336
|
-
return await rel_util.list_read_s(
|
|
337
|
-
related_schema, request, related_qs
|
|
338
|
-
)
|
|
380
|
+
return await rel_util.list_read_s(related_schema, request, related_qs)
|
|
339
381
|
|
|
340
382
|
def _resolve_action_schema(self, add: bool, remove: bool):
|
|
341
383
|
return self.views_action_map[(add, remove)]
|
|
@@ -343,7 +385,7 @@ class ManyToManyAPI:
|
|
|
343
385
|
def _register_manage_relation_view(
|
|
344
386
|
self,
|
|
345
387
|
*,
|
|
346
|
-
|
|
388
|
+
related_model: ModelSerializer | Model,
|
|
347
389
|
related_name: str,
|
|
348
390
|
m2m_auth,
|
|
349
391
|
rel_util: ModelUtil,
|
|
@@ -380,10 +422,21 @@ class ManyToManyAPI:
|
|
|
380
422
|
remove_pks = getattr(data, "remove", []) if m2m_remove else []
|
|
381
423
|
|
|
382
424
|
add_errors, add_details, add_objs = await self._collect_m2m(
|
|
383
|
-
request,
|
|
425
|
+
request,
|
|
426
|
+
add_pks,
|
|
427
|
+
related_model,
|
|
428
|
+
related_manager,
|
|
429
|
+
related_name,
|
|
430
|
+
obj,
|
|
384
431
|
)
|
|
385
432
|
remove_errors, remove_details, remove_objs = await self._collect_m2m(
|
|
386
|
-
request,
|
|
433
|
+
request,
|
|
434
|
+
remove_pks,
|
|
435
|
+
related_model,
|
|
436
|
+
related_manager,
|
|
437
|
+
related_name,
|
|
438
|
+
obj,
|
|
439
|
+
remove=True,
|
|
387
440
|
)
|
|
388
441
|
|
|
389
442
|
tasks = []
|
|
@@ -423,7 +476,7 @@ class ManyToManyAPI:
|
|
|
423
476
|
|
|
424
477
|
if m2m_add or m2m_remove:
|
|
425
478
|
self._register_manage_relation_view(
|
|
426
|
-
|
|
479
|
+
related_model=model,
|
|
427
480
|
related_name=related_name,
|
|
428
481
|
m2m_auth=m2m_auth,
|
|
429
482
|
rel_util=rel_util,
|
|
File without changes
|
|
File without changes
|