django-ninja-aio-crud 2.13.0__py3-none-any.whl → 2.15.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.
Potentially problematic release.
This version of django-ninja-aio-crud might be problematic. Click here for more details.
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.15.0.dist-info}/METADATA +1 -1
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.15.0.dist-info}/RECORD +6 -6
- ninja_aio/__init__.py +1 -1
- ninja_aio/models/serializers.py +147 -13
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.15.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.15.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=
|
|
1
|
+
ninja_aio/__init__.py,sha256=1ZReAODrup4BI5sHOAuanMqhDqfl36W8gVvhBrf5IZ0,120
|
|
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,7 +14,7 @@ ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
|
|
|
14
14
|
ninja_aio/helpers/api.py,sha256=O2nGvP3VSsG-AReQRn90yDH8vS3kMKh125j-ikwgGoQ,20987
|
|
15
15
|
ninja_aio/helpers/query.py,sha256=Lqv4nrWYr543tC5K-SEcBottLID8cb83aDc26i2Wxj4,5053
|
|
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=tm2ACQO-n877NTYN0YR-6qVZrZQpWgPDh0EeOZo_TWY,42824
|
|
18
18
|
ninja_aio/models/utils.py,sha256=lAXtc3YY7_n4f0jIacX4DSXhUOzMy7y5MsBnInNxtfk,32874
|
|
19
19
|
ninja_aio/schemas/__init__.py,sha256=dHILiYBKMb51lDcyQdiXRw_0nzqM7Lu81UX2hv7kEfo,837
|
|
20
20
|
ninja_aio/schemas/api.py,sha256=dGUpJXR1iAf93QNR4kYj1uqIkTjiMfXultCotY6GtaQ,361
|
|
@@ -24,7 +24,7 @@ ninja_aio/schemas/helpers.py,sha256=h4zQRf21NVLMQbIVH-psAE4FICUBc857EqngblEy7og,
|
|
|
24
24
|
ninja_aio/views/__init__.py,sha256=DEzjWA6y3WF0V10nNF8eEurLNEodgxKzyFd09AqVp3s,148
|
|
25
25
|
ninja_aio/views/api.py,sha256=AAqkj0xT8J3PmJvsbluZ33cfrmrXJHiV9ARe2BqnfQ8,22492
|
|
26
26
|
ninja_aio/views/mixins.py,sha256=Zl6J8gbVagwT85bzDuKyJTk3iFxxFgX0YgYkjiUxZGg,17040
|
|
27
|
-
django_ninja_aio_crud-2.
|
|
28
|
-
django_ninja_aio_crud-2.
|
|
29
|
-
django_ninja_aio_crud-2.
|
|
30
|
-
django_ninja_aio_crud-2.
|
|
27
|
+
django_ninja_aio_crud-2.15.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
28
|
+
django_ninja_aio_crud-2.15.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
29
|
+
django_ninja_aio_crud-2.15.0.dist-info/METADATA,sha256=m89g4FLCQgQvwCkSoJLxzsUwCuf4ESwyqKJLX31qKAo,9964
|
|
30
|
+
django_ninja_aio_crud-2.15.0.dist-info/RECORD,,
|
ninja_aio/__init__.py
CHANGED
ninja_aio/models/serializers.py
CHANGED
|
@@ -1,4 +1,14 @@
|
|
|
1
|
-
from typing import
|
|
1
|
+
from typing import (
|
|
2
|
+
Annotated,
|
|
3
|
+
Any,
|
|
4
|
+
List,
|
|
5
|
+
Literal,
|
|
6
|
+
Optional,
|
|
7
|
+
Union,
|
|
8
|
+
get_args,
|
|
9
|
+
get_origin,
|
|
10
|
+
ForwardRef,
|
|
11
|
+
)
|
|
2
12
|
import warnings
|
|
3
13
|
import sys
|
|
4
14
|
|
|
@@ -14,6 +24,7 @@ from django.db.models.fields.related_descriptors import (
|
|
|
14
24
|
ForwardManyToOneDescriptor,
|
|
15
25
|
ForwardOneToOneDescriptor,
|
|
16
26
|
)
|
|
27
|
+
from pydantic import BeforeValidator, Field
|
|
17
28
|
|
|
18
29
|
from ninja_aio.types import (
|
|
19
30
|
S_TYPES,
|
|
@@ -28,6 +39,32 @@ from ninja_aio.schemas.helpers import (
|
|
|
28
39
|
)
|
|
29
40
|
|
|
30
41
|
|
|
42
|
+
def _extract_pk(v: Any) -> Any:
|
|
43
|
+
"""Extract primary key from a model instance or return value as-is."""
|
|
44
|
+
if hasattr(v, "pk"):
|
|
45
|
+
return v.pk
|
|
46
|
+
return v
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
class PkFromModel:
|
|
50
|
+
"""Subscriptable type for extracting PK from model instances during serialization.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
PkFromModel[int] -> for integer PKs
|
|
54
|
+
PkFromModel[str] -> for string PKs
|
|
55
|
+
PkFromModel[UUID] -> for UUID PKs
|
|
56
|
+
PkFromModel -> defaults to int (backwards compatible)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
_default = Annotated[int, BeforeValidator(_extract_pk)]
|
|
60
|
+
|
|
61
|
+
def __class_getitem__(cls, pk_type: type) -> type:
|
|
62
|
+
return Annotated[pk_type, BeforeValidator(_extract_pk)]
|
|
63
|
+
|
|
64
|
+
def __new__(cls):
|
|
65
|
+
return cls._default
|
|
66
|
+
|
|
67
|
+
|
|
31
68
|
class BaseSerializer:
|
|
32
69
|
"""
|
|
33
70
|
BaseSerializer
|
|
@@ -212,6 +249,11 @@ class BaseSerializer:
|
|
|
212
249
|
# Optional in subclasses. Default to no explicit relation serializers.
|
|
213
250
|
return {}
|
|
214
251
|
|
|
252
|
+
@classmethod
|
|
253
|
+
def _get_relations_as_id(cls) -> list[str]:
|
|
254
|
+
# Optional in subclasses. Default to no relations as ID.
|
|
255
|
+
return []
|
|
256
|
+
|
|
215
257
|
@classmethod
|
|
216
258
|
def _generate_union_schema(cls, resolved_union: Any) -> Any:
|
|
217
259
|
"""
|
|
@@ -350,10 +392,25 @@ class BaseSerializer:
|
|
|
350
392
|
) or cls._is_special_field("update", field, "optionals")
|
|
351
393
|
|
|
352
394
|
@classmethod
|
|
353
|
-
def _build_schema_reverse_rel(
|
|
395
|
+
def _build_schema_reverse_rel(
|
|
396
|
+
cls, field_name: str, descriptor: Any, relations_as_id: list[str]
|
|
397
|
+
):
|
|
354
398
|
"""
|
|
355
399
|
Build a reverse relation schema component for 'Out' schema generation.
|
|
356
|
-
|
|
400
|
+
|
|
401
|
+
Parameters
|
|
402
|
+
----------
|
|
403
|
+
field_name : str
|
|
404
|
+
Name of the relation field.
|
|
405
|
+
descriptor : Any
|
|
406
|
+
Django field descriptor (ManyToManyDescriptor, ReverseManyToOneDescriptor, etc.).
|
|
407
|
+
relations_as_id : list[str]
|
|
408
|
+
Pre-fetched list of fields to serialize as IDs.
|
|
409
|
+
|
|
410
|
+
Returns
|
|
411
|
+
-------
|
|
412
|
+
tuple | None
|
|
413
|
+
Custom field tuple for schema generation, or None to skip.
|
|
357
414
|
"""
|
|
358
415
|
# Resolve related model and cardinality
|
|
359
416
|
if isinstance(descriptor, ManyToManyDescriptor):
|
|
@@ -371,6 +428,17 @@ class BaseSerializer:
|
|
|
371
428
|
rel_model = descriptor.related.related_model
|
|
372
429
|
many = False
|
|
373
430
|
|
|
431
|
+
# Handle relations_as_id for reverse relations
|
|
432
|
+
if field_name in relations_as_id:
|
|
433
|
+
from ninja_aio.models.utils import ModelUtil
|
|
434
|
+
pk_field_type = ModelUtil(rel_model).pk_field_type
|
|
435
|
+
if many:
|
|
436
|
+
# For many relations, use PkFromModel to extract PKs from model instances
|
|
437
|
+
return (field_name, list[PkFromModel[pk_field_type]], Field(default_factory=list))
|
|
438
|
+
else:
|
|
439
|
+
# For single reverse relations (ReverseOneToOne), extract pk
|
|
440
|
+
return (field_name, PkFromModel[pk_field_type] | None, None)
|
|
441
|
+
|
|
374
442
|
schema = cls._resolve_relation_schema(field_name, rel_model)
|
|
375
443
|
if not schema:
|
|
376
444
|
return None
|
|
@@ -379,14 +447,36 @@ class BaseSerializer:
|
|
|
379
447
|
return (field_name, rel_schema_type | None, None)
|
|
380
448
|
|
|
381
449
|
@classmethod
|
|
382
|
-
def _build_schema_forward_rel(
|
|
450
|
+
def _build_schema_forward_rel(
|
|
451
|
+
cls, field_name: str, descriptor: Any, relations_as_id: list[str]
|
|
452
|
+
):
|
|
383
453
|
"""
|
|
384
454
|
Build a forward relation schema component for 'Out' schema generation.
|
|
385
|
-
|
|
386
|
-
|
|
455
|
+
|
|
456
|
+
Parameters
|
|
457
|
+
----------
|
|
458
|
+
field_name : str
|
|
459
|
+
Name of the relation field.
|
|
460
|
+
descriptor : Any
|
|
461
|
+
Django field descriptor (ForwardOneToOneDescriptor, ForwardManyToOneDescriptor).
|
|
462
|
+
relations_as_id : list[str]
|
|
463
|
+
Pre-fetched list of fields to serialize as IDs.
|
|
464
|
+
|
|
465
|
+
Returns
|
|
466
|
+
-------
|
|
467
|
+
True | tuple | None
|
|
468
|
+
True to treat as plain field, a custom field tuple to include relation schema,
|
|
469
|
+
or None to skip entirely.
|
|
387
470
|
"""
|
|
388
471
|
rel_model = descriptor.field.related_model
|
|
389
472
|
|
|
473
|
+
# Handle relations_as_id: serialize as the raw FK ID
|
|
474
|
+
if field_name in relations_as_id:
|
|
475
|
+
from ninja_aio.models.utils import ModelUtil
|
|
476
|
+
pk_field_type = ModelUtil(rel_model).pk_field_type
|
|
477
|
+
# Use PkFromModel to extract pk from the related instance during serialization
|
|
478
|
+
return (field_name, PkFromModel[pk_field_type] | None, None)
|
|
479
|
+
|
|
390
480
|
# Special case: ModelSerializer with no readable fields should be skipped entirely
|
|
391
481
|
if isinstance(rel_model, ModelSerializerMeta):
|
|
392
482
|
if not (
|
|
@@ -441,23 +531,44 @@ class BaseSerializer:
|
|
|
441
531
|
field_name: str,
|
|
442
532
|
model,
|
|
443
533
|
relations_serializers: dict,
|
|
534
|
+
relations_as_id: list[str],
|
|
444
535
|
) -> tuple[str | None, tuple | None, tuple | None]:
|
|
445
536
|
"""
|
|
446
537
|
Process a single field and determine its classification.
|
|
447
538
|
|
|
448
|
-
|
|
539
|
+
Parameters
|
|
540
|
+
----------
|
|
541
|
+
field_name : str
|
|
542
|
+
Name of the field to process.
|
|
543
|
+
model : Model
|
|
544
|
+
Django model class.
|
|
545
|
+
relations_serializers : dict
|
|
546
|
+
Mapping of relation field names to serializer classes.
|
|
547
|
+
relations_as_id : list[str]
|
|
548
|
+
Pre-fetched list of fields to serialize as IDs.
|
|
549
|
+
|
|
550
|
+
Returns
|
|
551
|
+
-------
|
|
552
|
+
tuple
|
|
449
553
|
(plain_field, reverse_rel, forward_rel) - only one will be non-None
|
|
450
554
|
"""
|
|
451
555
|
field_obj = getattr(model, field_name)
|
|
452
556
|
|
|
453
557
|
if cls._is_reverse_relation(field_obj):
|
|
454
|
-
if
|
|
558
|
+
if (
|
|
559
|
+
field_name not in relations_serializers
|
|
560
|
+
and field_name not in relations_as_id
|
|
561
|
+
):
|
|
455
562
|
cls._warn_missing_relation_serializer(field_name, model)
|
|
456
|
-
rel_tuple = cls._build_schema_reverse_rel(
|
|
563
|
+
rel_tuple = cls._build_schema_reverse_rel(
|
|
564
|
+
field_name, field_obj, relations_as_id
|
|
565
|
+
)
|
|
457
566
|
return (None, rel_tuple, None)
|
|
458
567
|
|
|
459
568
|
if cls._is_forward_relation(field_obj):
|
|
460
|
-
rel_tuple = cls._build_schema_forward_rel(
|
|
569
|
+
rel_tuple = cls._build_schema_forward_rel(
|
|
570
|
+
field_name, field_obj, relations_as_id
|
|
571
|
+
)
|
|
461
572
|
if rel_tuple is True:
|
|
462
573
|
return (field_name, None, None)
|
|
463
574
|
return (None, None, rel_tuple)
|
|
@@ -469,8 +580,15 @@ class BaseSerializer:
|
|
|
469
580
|
"""
|
|
470
581
|
Collect components for output schema generation (Out or Detail).
|
|
471
582
|
|
|
472
|
-
|
|
473
|
-
|
|
583
|
+
Parameters
|
|
584
|
+
----------
|
|
585
|
+
schema_type : Literal["Out", "Detail"]
|
|
586
|
+
Type of schema to generate.
|
|
587
|
+
|
|
588
|
+
Returns
|
|
589
|
+
-------
|
|
590
|
+
tuple
|
|
591
|
+
(fields, reverse_rels, excludes, customs_with_forward_rels, optionals)
|
|
474
592
|
"""
|
|
475
593
|
if schema_type not in ("Out", "Detail"):
|
|
476
594
|
raise ValueError(
|
|
@@ -480,6 +598,8 @@ class BaseSerializer:
|
|
|
480
598
|
fields_type = "read" if schema_type == "Out" else "detail"
|
|
481
599
|
model = cls._get_model()
|
|
482
600
|
relations_serializers = cls._get_relations_serializers() or {}
|
|
601
|
+
# Fetch once to avoid repeated method calls during field processing
|
|
602
|
+
relations_as_id = cls._get_relations_as_id()
|
|
483
603
|
|
|
484
604
|
fields: list[str] = []
|
|
485
605
|
reverse_rels: list[tuple] = []
|
|
@@ -487,7 +607,7 @@ class BaseSerializer:
|
|
|
487
607
|
|
|
488
608
|
for field_name in cls.get_fields(fields_type):
|
|
489
609
|
plain, reverse, forward = cls._process_field(
|
|
490
|
-
field_name, model, relations_serializers
|
|
610
|
+
field_name, model, relations_serializers, relations_as_id
|
|
491
611
|
)
|
|
492
612
|
if plain:
|
|
493
613
|
fields.append(plain)
|
|
@@ -701,12 +821,15 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
701
821
|
Computed / synthetic output attributes.
|
|
702
822
|
optionals : list[tuple[str, type]]
|
|
703
823
|
Optional output fields.
|
|
824
|
+
relations_as_id : list[str]
|
|
825
|
+
Relation fields to serialize as IDs instead of nested objects.
|
|
704
826
|
"""
|
|
705
827
|
|
|
706
828
|
fields: list[str] = []
|
|
707
829
|
customs: list[tuple[str, type, Any]] = []
|
|
708
830
|
optionals: list[tuple[str, type]] = []
|
|
709
831
|
excludes: list[str] = []
|
|
832
|
+
relations_as_id: list[str] = []
|
|
710
833
|
|
|
711
834
|
class DetailSerializer:
|
|
712
835
|
"""Configuration describing detail (single object) read schema.
|
|
@@ -756,6 +879,11 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
756
879
|
"detail": "DetailSerializer",
|
|
757
880
|
}
|
|
758
881
|
|
|
882
|
+
@classmethod
|
|
883
|
+
def _get_relations_as_id(cls) -> list[str]:
|
|
884
|
+
"""Return relation fields to serialize as IDs instead of nested objects."""
|
|
885
|
+
return getattr(cls.ReadSerializer, "relations_as_id", [])
|
|
886
|
+
|
|
759
887
|
@classmethod
|
|
760
888
|
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
761
889
|
"""
|
|
@@ -960,6 +1088,12 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
960
1088
|
schema_update: Optional[SchemaModelConfig] = None
|
|
961
1089
|
schema_detail: Optional[SchemaModelConfig] = None
|
|
962
1090
|
relations_serializers: dict[str, "Serializer"] = {}
|
|
1091
|
+
relations_as_id: list[str] = []
|
|
1092
|
+
|
|
1093
|
+
@classmethod
|
|
1094
|
+
def _get_relations_as_id(cls) -> list[str]:
|
|
1095
|
+
relations_as_id = cls._get_meta_data("relations_as_id")
|
|
1096
|
+
return relations_as_id or []
|
|
963
1097
|
|
|
964
1098
|
@classmethod
|
|
965
1099
|
def _get_meta_data(cls, attr_name: str) -> Any:
|
|
File without changes
|
{django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.15.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|