django-ninja-aio-crud 2.5.0__py3-none-any.whl → 2.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.
Potentially problematic release.
This version of django-ninja-aio-crud might be problematic. Click here for more details.
- {django_ninja_aio_crud-2.5.0.dist-info → django_ninja_aio_crud-2.6.1.dist-info}/METADATA +1 -1
- {django_ninja_aio_crud-2.5.0.dist-info → django_ninja_aio_crud-2.6.1.dist-info}/RECORD +7 -7
- ninja_aio/__init__.py +1 -1
- ninja_aio/models/serializers.py +159 -86
- ninja_aio/views/api.py +3 -0
- {django_ninja_aio_crud-2.5.0.dist-info → django_ninja_aio_crud-2.6.1.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.5.0.dist-info → django_ninja_aio_crud-2.6.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=
|
|
1
|
+
ninja_aio/__init__.py,sha256=ww2hnE9C7lFXzVyNnpZ97wv8erYcXdxhjzu8PRiM8bc,119
|
|
2
2
|
ninja_aio/api.py,sha256=tuC7vdvn7s1GkCnSFy9Kn1zv0glZfYptRQVvo8ZRtGQ,2429
|
|
3
3
|
ninja_aio/auth.py,sha256=4sWdFPjKiQgUL1d_CSGDblVjnY5ptP6LQha6XXdluJA,9157
|
|
4
4
|
ninja_aio/exceptions.py,sha256=_3xFqfFCOfrrMhSA0xbMqgXy8R0UQjhXaExrFvaDAjY,3891
|
|
@@ -14,16 +14,16 @@ ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
14
14
|
ninja_aio/helpers/api.py,sha256=YMzuZ4-ZpUrJBQIabE26gb_GYwsH2rVosWRE95YfdPQ,20775
|
|
15
15
|
ninja_aio/helpers/query.py,sha256=lzaH-htswoJVRT-W736HGMkpMba1VmN98TBLv5cZx9Q,4549
|
|
16
16
|
ninja_aio/models/__init__.py,sha256=L3UQnQAlKoI3F7jinadL-Nn55hkPvnSRPYW0JtnbWFo,114
|
|
17
|
-
ninja_aio/models/serializers.py,sha256
|
|
17
|
+
ninja_aio/models/serializers.py,sha256=zV3gRI4isnCuLCAQbPhQlut3nKT0XuQOLy2ABiaJ6Y4,31372
|
|
18
18
|
ninja_aio/models/utils.py,sha256=P-YfbVyzUfxm_s1BrgSd6Zs0HIGdZ79PU1qM0Ud9-Xs,30492
|
|
19
19
|
ninja_aio/schemas/__init__.py,sha256=iLBwHg0pmL9k_UkIui5Q8QIl_gO4fgxSv2JHxDzqnSI,549
|
|
20
20
|
ninja_aio/schemas/api.py,sha256=-VwXhBRhmMsZLIAmWJ-P7tB5klxXS75eukjabeKKYsc,360
|
|
21
21
|
ninja_aio/schemas/generics.py,sha256=frjJsKJMAdM_NdNKv-9ddZNGxYy5PNzjIRGtuycgr-w,112
|
|
22
22
|
ninja_aio/schemas/helpers.py,sha256=W6IeHi5Tmbjh3FXwDYqjqlLBTVj5uTYq3_JVkNUWayo,7355
|
|
23
23
|
ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
|
|
24
|
-
ninja_aio/views/api.py,sha256=
|
|
24
|
+
ninja_aio/views/api.py,sha256=GRtjCvAv7jAp3TxpOirsbMVKpBd8hymSMILdE-JLxvI,21327
|
|
25
25
|
ninja_aio/views/mixins.py,sha256=Jh6BG8Cs823nurVlODlzCquTxKrLH7Pmo5udPqUGZek,11378
|
|
26
|
-
django_ninja_aio_crud-2.
|
|
27
|
-
django_ninja_aio_crud-2.
|
|
28
|
-
django_ninja_aio_crud-2.
|
|
29
|
-
django_ninja_aio_crud-2.
|
|
26
|
+
django_ninja_aio_crud-2.6.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
27
|
+
django_ninja_aio_crud-2.6.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
28
|
+
django_ninja_aio_crud-2.6.1.dist-info/METADATA,sha256=aBT8gpjs9TVS_jcs_c4m--5K8YFLjpAUpNPa0ib2T-0,9963
|
|
29
|
+
django_ninja_aio_crud-2.6.1.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/models/serializers.py
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from typing import Any, List, Optional
|
|
2
2
|
import warnings
|
|
3
|
+
import sys
|
|
3
4
|
|
|
4
5
|
from django.conf import settings
|
|
5
6
|
from ninja import Schema
|
|
@@ -67,11 +68,92 @@ class BaseSerializer:
|
|
|
67
68
|
# Subclasses provide implementation.
|
|
68
69
|
raise NotImplementedError
|
|
69
70
|
|
|
71
|
+
@classmethod
|
|
72
|
+
def _resolve_serializer_reference(cls, serializer_ref: str | type) -> type:
|
|
73
|
+
"""
|
|
74
|
+
Resolve a serializer reference that may be a string or a class.
|
|
75
|
+
|
|
76
|
+
This method performs lazy resolution, meaning it will attempt to resolve
|
|
77
|
+
string references only when called, allowing for forward references and
|
|
78
|
+
circular dependencies between serializers in the same module.
|
|
79
|
+
|
|
80
|
+
Parameters
|
|
81
|
+
----------
|
|
82
|
+
serializer_ref : str | type
|
|
83
|
+
Either a string reference to a serializer class name in the same module,
|
|
84
|
+
or an actual serializer class.
|
|
85
|
+
|
|
86
|
+
Returns
|
|
87
|
+
-------
|
|
88
|
+
type
|
|
89
|
+
The resolved serializer class.
|
|
90
|
+
|
|
91
|
+
Raises
|
|
92
|
+
------
|
|
93
|
+
ValueError
|
|
94
|
+
If the string reference cannot be resolved.
|
|
95
|
+
"""
|
|
96
|
+
# If it's already a class, return it directly
|
|
97
|
+
if not isinstance(serializer_ref, str):
|
|
98
|
+
return serializer_ref
|
|
99
|
+
|
|
100
|
+
# Get the module where the current serializer class is defined
|
|
101
|
+
module = sys.modules.get(cls.__module__)
|
|
102
|
+
|
|
103
|
+
if module is None:
|
|
104
|
+
raise ValueError(
|
|
105
|
+
f"Cannot resolve serializer reference '{serializer_ref}': "
|
|
106
|
+
f"module '{cls.__module__}' not found in sys.modules."
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# Try to get the serializer class from the module
|
|
110
|
+
serializer_class = getattr(module, serializer_ref, None)
|
|
111
|
+
|
|
112
|
+
if serializer_class is None:
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Cannot resolve serializer reference '{serializer_ref}' in module '{cls.__module__}'. "
|
|
115
|
+
f"Make sure the serializer class '{serializer_ref}' is defined in the same module as {cls.__name__}."
|
|
116
|
+
)
|
|
117
|
+
|
|
118
|
+
return serializer_class
|
|
119
|
+
|
|
70
120
|
@classmethod
|
|
71
121
|
def _get_relations_serializers(cls) -> dict[str, "Serializer"]:
|
|
72
122
|
# Optional in subclasses. Default to no explicit relation serializers.
|
|
73
123
|
return {}
|
|
74
124
|
|
|
125
|
+
@classmethod
|
|
126
|
+
def _resolve_relation_schema(cls, field_name: str, rel_model: models.Model):
|
|
127
|
+
"""
|
|
128
|
+
Resolve and generate schema for a related model.
|
|
129
|
+
|
|
130
|
+
Parameters
|
|
131
|
+
----------
|
|
132
|
+
field_name : str
|
|
133
|
+
Name of the relation field.
|
|
134
|
+
rel_model : models.Model
|
|
135
|
+
The related model class.
|
|
136
|
+
|
|
137
|
+
Returns
|
|
138
|
+
-------
|
|
139
|
+
Schema | None
|
|
140
|
+
Generated schema or None if cannot be resolved.
|
|
141
|
+
"""
|
|
142
|
+
# Check if related model is a ModelSerializer with readable fields
|
|
143
|
+
if isinstance(rel_model, ModelSerializerMeta):
|
|
144
|
+
if rel_model.get_fields("read") or rel_model.get_custom_fields("read"):
|
|
145
|
+
return rel_model.generate_related_s()
|
|
146
|
+
return None
|
|
147
|
+
|
|
148
|
+
# Fall back to explicit serializer mapping
|
|
149
|
+
rel_serializers = cls._get_relations_serializers() or {}
|
|
150
|
+
serializer_ref = rel_serializers.get(field_name)
|
|
151
|
+
if serializer_ref:
|
|
152
|
+
serializer = cls._resolve_serializer_reference(serializer_ref)
|
|
153
|
+
return serializer.generate_related_s()
|
|
154
|
+
|
|
155
|
+
return None
|
|
156
|
+
|
|
75
157
|
@classmethod
|
|
76
158
|
def _is_special_field(
|
|
77
159
|
cls, s_type: type[S_TYPES], field: str, f_type: type[F_TYPES]
|
|
@@ -160,18 +242,7 @@ class BaseSerializer:
|
|
|
160
242
|
rel_model = descriptor.related.related_model
|
|
161
243
|
many = False
|
|
162
244
|
|
|
163
|
-
schema =
|
|
164
|
-
if isinstance(rel_model, ModelSerializerMeta):
|
|
165
|
-
# Auto-include if related model exposes readable data
|
|
166
|
-
if rel_model.get_fields("read") or rel_model.get_custom_fields("read"):
|
|
167
|
-
schema = rel_model.generate_related_s()
|
|
168
|
-
else:
|
|
169
|
-
# Use explicit serializer when provided by subclasses
|
|
170
|
-
rel_serializers = cls._get_relations_serializers() or {}
|
|
171
|
-
serializer = rel_serializers.get(field_name)
|
|
172
|
-
if serializer:
|
|
173
|
-
schema = serializer.generate_related_s()
|
|
174
|
-
|
|
245
|
+
schema = cls._resolve_relation_schema(field_name, rel_model)
|
|
175
246
|
if not schema:
|
|
176
247
|
return None
|
|
177
248
|
|
|
@@ -187,21 +258,14 @@ class BaseSerializer:
|
|
|
187
258
|
"""
|
|
188
259
|
rel_model = descriptor.field.related_model
|
|
189
260
|
|
|
190
|
-
|
|
261
|
+
# Special case: ModelSerializer with no readable fields should be skipped entirely
|
|
191
262
|
if isinstance(rel_model, ModelSerializerMeta):
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
else:
|
|
196
|
-
# Explicit ModelSerializer with no readable fields -> skip entirely
|
|
263
|
+
if not (
|
|
264
|
+
rel_model.get_fields("read") or rel_model.get_custom_fields("read")
|
|
265
|
+
):
|
|
197
266
|
return None
|
|
198
|
-
else:
|
|
199
|
-
# Fall back to an explicitly provided serializer mapping
|
|
200
|
-
rel_serializers = cls._get_relations_serializers() or {}
|
|
201
|
-
serializer = rel_serializers.get(field_name)
|
|
202
|
-
if serializer:
|
|
203
|
-
schema = serializer.generate_related_s()
|
|
204
267
|
|
|
268
|
+
schema = cls._resolve_relation_schema(field_name, rel_model)
|
|
205
269
|
if not schema:
|
|
206
270
|
# Could not build a schema: treat as a plain field (serialize as-is)
|
|
207
271
|
return True
|
|
@@ -289,52 +353,55 @@ class BaseSerializer:
|
|
|
289
353
|
Handles In/Patch/Out/Related.
|
|
290
354
|
"""
|
|
291
355
|
model = cls._get_model()
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
356
|
+
|
|
357
|
+
# Handle special schema types with custom logic
|
|
358
|
+
if schema_type == "Out":
|
|
359
|
+
fields, reverse_rels, excludes, customs, optionals = (
|
|
360
|
+
cls.get_schema_out_data()
|
|
361
|
+
)
|
|
362
|
+
if not any([fields, reverse_rels, excludes, customs]):
|
|
363
|
+
return None
|
|
364
|
+
return create_schema(
|
|
365
|
+
model=model,
|
|
366
|
+
name=f"{model._meta.model_name}SchemaOut",
|
|
367
|
+
depth=depth,
|
|
368
|
+
fields=fields,
|
|
369
|
+
custom_fields=reverse_rels + customs + optionals,
|
|
370
|
+
exclude=excludes,
|
|
371
|
+
)
|
|
372
|
+
|
|
373
|
+
if schema_type == "Related":
|
|
374
|
+
fields, customs = cls.get_related_schema_data()
|
|
375
|
+
if not fields and not customs:
|
|
376
|
+
return None
|
|
377
|
+
return create_schema(
|
|
378
|
+
model=model,
|
|
379
|
+
name=f"{model._meta.model_name}SchemaRelated",
|
|
380
|
+
fields=fields,
|
|
381
|
+
custom_fields=customs,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
# Handle standard In/Patch schema types
|
|
385
|
+
s_type = "create" if schema_type == "In" else "update"
|
|
322
386
|
fields = cls.get_fields(s_type)
|
|
323
387
|
optionals = cls.get_optional_fields(s_type)
|
|
324
388
|
customs = cls.get_custom_fields(s_type) + optionals
|
|
325
389
|
excludes = cls.get_excluded_fields(s_type)
|
|
390
|
+
|
|
391
|
+
# If no explicit fields but have optionals, use optional field names as fields
|
|
326
392
|
if not fields and not excludes:
|
|
327
393
|
fields = [f[0] for f in optionals]
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
394
|
+
|
|
395
|
+
# Only create schema if we have something to include
|
|
396
|
+
if not any([fields, customs, excludes]):
|
|
397
|
+
return None
|
|
398
|
+
|
|
399
|
+
return create_schema(
|
|
400
|
+
model=model,
|
|
401
|
+
name=f"{model._meta.model_name}Schema{schema_type}",
|
|
402
|
+
fields=fields,
|
|
403
|
+
custom_fields=customs,
|
|
404
|
+
exclude=excludes,
|
|
338
405
|
)
|
|
339
406
|
|
|
340
407
|
@classmethod
|
|
@@ -493,6 +560,13 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
493
560
|
optionals: list[tuple[str, type]] = []
|
|
494
561
|
excludes: list[str] = []
|
|
495
562
|
|
|
563
|
+
# Serializer type to configuration class mapping
|
|
564
|
+
_SERIALIZER_CONFIG_MAP = {
|
|
565
|
+
"create": "CreateSerializer",
|
|
566
|
+
"update": "UpdateSerializer",
|
|
567
|
+
"read": "ReadSerializer",
|
|
568
|
+
}
|
|
569
|
+
|
|
496
570
|
@classmethod
|
|
497
571
|
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
498
572
|
"""
|
|
@@ -510,14 +584,11 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
510
584
|
list
|
|
511
585
|
Raw configuration list or empty list.
|
|
512
586
|
"""
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
case "read":
|
|
519
|
-
fields = getattr(cls.ReadSerializer, f_type, [])
|
|
520
|
-
return fields
|
|
587
|
+
config_class_name = cls._SERIALIZER_CONFIG_MAP.get(s_type)
|
|
588
|
+
if not config_class_name:
|
|
589
|
+
return []
|
|
590
|
+
config_class = getattr(cls, config_class_name)
|
|
591
|
+
return getattr(config_class, f_type, [])
|
|
521
592
|
|
|
522
593
|
@classmethod
|
|
523
594
|
def _get_model(cls) -> "ModelSerializer":
|
|
@@ -672,18 +743,22 @@ class Serializer(BaseSerializer):
|
|
|
672
743
|
schema components during read schema generation.
|
|
673
744
|
"""
|
|
674
745
|
|
|
746
|
+
# Serializer type to Meta schema attribute mapping
|
|
747
|
+
_SCHEMA_META_MAP = {
|
|
748
|
+
"create": "in",
|
|
749
|
+
"update": "update",
|
|
750
|
+
"read": "out",
|
|
751
|
+
}
|
|
752
|
+
|
|
675
753
|
def __init_subclass__(cls, **kwargs):
|
|
676
754
|
super().__init_subclass__(**kwargs)
|
|
677
755
|
from ninja_aio.models.utils import ModelUtil
|
|
678
756
|
from ninja_aio.helpers.query import QueryUtil
|
|
679
757
|
|
|
680
758
|
cls.model = cls._get_model()
|
|
681
|
-
cls.
|
|
682
|
-
cls.schema_out = cls.generate_read_s()
|
|
683
|
-
cls.schema_update = cls.generate_update_s()
|
|
684
|
-
cls.schema_related = cls.generate_related_s()
|
|
685
|
-
cls.util = ModelUtil(cls._get_model(), serializer_class=cls)
|
|
759
|
+
cls.util = ModelUtil(cls.model, serializer_class=cls)
|
|
686
760
|
cls.query_util = QueryUtil(cls)
|
|
761
|
+
cls._meta = cls.Meta
|
|
687
762
|
|
|
688
763
|
class Meta:
|
|
689
764
|
model: models.Model = None
|
|
@@ -729,16 +804,14 @@ class Serializer(BaseSerializer):
|
|
|
729
804
|
@classmethod
|
|
730
805
|
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
731
806
|
"""Internal accessor for raw configuration lists from Meta schemas."""
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
}.get(s_type)
|
|
807
|
+
schema_key = cls._SCHEMA_META_MAP.get(s_type)
|
|
808
|
+
if not schema_key:
|
|
809
|
+
return []
|
|
810
|
+
schema = cls._get_schema_meta(schema_key)
|
|
737
811
|
if not schema:
|
|
738
812
|
return []
|
|
739
813
|
return getattr(schema, f_type, []) or []
|
|
740
814
|
|
|
741
|
-
|
|
742
815
|
@classmethod
|
|
743
816
|
async def queryset_request(cls, request: HttpRequest):
|
|
744
817
|
return cls.query_util.apply_queryset_optimizations(
|
|
@@ -835,7 +908,7 @@ class Serializer(BaseSerializer):
|
|
|
835
908
|
dict
|
|
836
909
|
Serialized data.
|
|
837
910
|
"""
|
|
838
|
-
return await self.
|
|
911
|
+
return await self.util.read_s(schema=self.generate_read_s(), instance=instance)
|
|
839
912
|
|
|
840
913
|
async def models_dump(
|
|
841
914
|
self, instances: models.QuerySet[models.Model]
|
|
@@ -853,8 +926,8 @@ class Serializer(BaseSerializer):
|
|
|
853
926
|
list[dict]
|
|
854
927
|
List of serialized data.
|
|
855
928
|
"""
|
|
856
|
-
return await self.
|
|
857
|
-
schema=self.
|
|
929
|
+
return await self.util.list_read_s(
|
|
930
|
+
schema=self.generate_read_s(), instances=instances
|
|
858
931
|
)
|
|
859
932
|
|
|
860
933
|
def after_save(self, instance: models.Model):
|
ninja_aio/views/api.py
CHANGED
|
@@ -254,6 +254,9 @@ class APIViewSet(API):
|
|
|
254
254
|
self.api = api or self.api
|
|
255
255
|
self.error_codes = ERROR_CODES
|
|
256
256
|
self.model = model or self.model
|
|
257
|
+
self.serializer: serializers.Serializer | None = (
|
|
258
|
+
None if self.serializer_class is None else self.serializer_class()
|
|
259
|
+
)
|
|
257
260
|
self.model_util = (
|
|
258
261
|
ModelUtil(self.model, serializer_class=self.serializer_class)
|
|
259
262
|
if not isinstance(self.model, ModelSerializerMeta)
|
|
File without changes
|
{django_ninja_aio_crud-2.5.0.dist-info → django_ninja_aio_crud-2.6.1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|