django-ninja-aio-crud 2.13.0__py3-none-any.whl → 2.14.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.13.0.dist-info → django_ninja_aio_crud-2.14.0.dist-info}/METADATA +1 -1
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.14.0.dist-info}/RECORD +6 -6
- ninja_aio/__init__.py +1 -1
- ninja_aio/models/serializers.py +128 -13
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.14.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.13.0.dist-info → django_ninja_aio_crud-2.14.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
ninja_aio/__init__.py,sha256=
|
|
1
|
+
ninja_aio/__init__.py,sha256=cdoKMOyGNe8UDjxTJQBqIMhJ_-OLa8J5FBMGB-6C-6E,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=Bv5Guqc_OuOGPos6A8HD0EdfGJZI_NQ9mmHhKb8jyZI,42114
|
|
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.14.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
|
|
28
|
+
django_ninja_aio_crud-2.14.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
|
29
|
+
django_ninja_aio_crud-2.14.0.dist-info/METADATA,sha256=M2D8ZIAziKDs1Xo2AibR1QzcTXXe2Tlxeh3h7CYi6bo,9964
|
|
30
|
+
django_ninja_aio_crud-2.14.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,17 @@ 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
|
+
# Annotated type for extracting PK from model instances during serialization
|
|
50
|
+
PkFromModel = Annotated[int, BeforeValidator(_extract_pk)]
|
|
51
|
+
|
|
52
|
+
|
|
31
53
|
class BaseSerializer:
|
|
32
54
|
"""
|
|
33
55
|
BaseSerializer
|
|
@@ -212,6 +234,11 @@ class BaseSerializer:
|
|
|
212
234
|
# Optional in subclasses. Default to no explicit relation serializers.
|
|
213
235
|
return {}
|
|
214
236
|
|
|
237
|
+
@classmethod
|
|
238
|
+
def _get_relations_as_id(cls) -> list[str]:
|
|
239
|
+
# Optional in subclasses. Default to no relations as ID.
|
|
240
|
+
return []
|
|
241
|
+
|
|
215
242
|
@classmethod
|
|
216
243
|
def _generate_union_schema(cls, resolved_union: Any) -> Any:
|
|
217
244
|
"""
|
|
@@ -350,10 +377,25 @@ class BaseSerializer:
|
|
|
350
377
|
) or cls._is_special_field("update", field, "optionals")
|
|
351
378
|
|
|
352
379
|
@classmethod
|
|
353
|
-
def _build_schema_reverse_rel(
|
|
380
|
+
def _build_schema_reverse_rel(
|
|
381
|
+
cls, field_name: str, descriptor: Any, relations_as_id: list[str]
|
|
382
|
+
):
|
|
354
383
|
"""
|
|
355
384
|
Build a reverse relation schema component for 'Out' schema generation.
|
|
356
|
-
|
|
385
|
+
|
|
386
|
+
Parameters
|
|
387
|
+
----------
|
|
388
|
+
field_name : str
|
|
389
|
+
Name of the relation field.
|
|
390
|
+
descriptor : Any
|
|
391
|
+
Django field descriptor (ManyToManyDescriptor, ReverseManyToOneDescriptor, etc.).
|
|
392
|
+
relations_as_id : list[str]
|
|
393
|
+
Pre-fetched list of fields to serialize as IDs.
|
|
394
|
+
|
|
395
|
+
Returns
|
|
396
|
+
-------
|
|
397
|
+
tuple | None
|
|
398
|
+
Custom field tuple for schema generation, or None to skip.
|
|
357
399
|
"""
|
|
358
400
|
# Resolve related model and cardinality
|
|
359
401
|
if isinstance(descriptor, ManyToManyDescriptor):
|
|
@@ -371,6 +413,15 @@ class BaseSerializer:
|
|
|
371
413
|
rel_model = descriptor.related.related_model
|
|
372
414
|
many = False
|
|
373
415
|
|
|
416
|
+
# Handle relations_as_id for reverse relations
|
|
417
|
+
if field_name in relations_as_id:
|
|
418
|
+
if many:
|
|
419
|
+
# For many relations, use PkFromModel to extract PKs from model instances
|
|
420
|
+
return (field_name, list[PkFromModel], Field(default_factory=list))
|
|
421
|
+
else:
|
|
422
|
+
# For single reverse relations (ReverseOneToOne), extract pk
|
|
423
|
+
return (field_name, PkFromModel | None, None)
|
|
424
|
+
|
|
374
425
|
schema = cls._resolve_relation_schema(field_name, rel_model)
|
|
375
426
|
if not schema:
|
|
376
427
|
return None
|
|
@@ -379,14 +430,34 @@ class BaseSerializer:
|
|
|
379
430
|
return (field_name, rel_schema_type | None, None)
|
|
380
431
|
|
|
381
432
|
@classmethod
|
|
382
|
-
def _build_schema_forward_rel(
|
|
433
|
+
def _build_schema_forward_rel(
|
|
434
|
+
cls, field_name: str, descriptor: Any, relations_as_id: list[str]
|
|
435
|
+
):
|
|
383
436
|
"""
|
|
384
437
|
Build a forward relation schema component for 'Out' schema generation.
|
|
385
|
-
|
|
386
|
-
|
|
438
|
+
|
|
439
|
+
Parameters
|
|
440
|
+
----------
|
|
441
|
+
field_name : str
|
|
442
|
+
Name of the relation field.
|
|
443
|
+
descriptor : Any
|
|
444
|
+
Django field descriptor (ForwardOneToOneDescriptor, ForwardManyToOneDescriptor).
|
|
445
|
+
relations_as_id : list[str]
|
|
446
|
+
Pre-fetched list of fields to serialize as IDs.
|
|
447
|
+
|
|
448
|
+
Returns
|
|
449
|
+
-------
|
|
450
|
+
True | tuple | None
|
|
451
|
+
True to treat as plain field, a custom field tuple to include relation schema,
|
|
452
|
+
or None to skip entirely.
|
|
387
453
|
"""
|
|
388
454
|
rel_model = descriptor.field.related_model
|
|
389
455
|
|
|
456
|
+
# Handle relations_as_id: serialize as the raw FK ID
|
|
457
|
+
if field_name in relations_as_id:
|
|
458
|
+
# Use PkFromModel to extract pk from the related instance during serialization
|
|
459
|
+
return (field_name, PkFromModel | None, None)
|
|
460
|
+
|
|
390
461
|
# Special case: ModelSerializer with no readable fields should be skipped entirely
|
|
391
462
|
if isinstance(rel_model, ModelSerializerMeta):
|
|
392
463
|
if not (
|
|
@@ -441,23 +512,44 @@ class BaseSerializer:
|
|
|
441
512
|
field_name: str,
|
|
442
513
|
model,
|
|
443
514
|
relations_serializers: dict,
|
|
515
|
+
relations_as_id: list[str],
|
|
444
516
|
) -> tuple[str | None, tuple | None, tuple | None]:
|
|
445
517
|
"""
|
|
446
518
|
Process a single field and determine its classification.
|
|
447
519
|
|
|
448
|
-
|
|
520
|
+
Parameters
|
|
521
|
+
----------
|
|
522
|
+
field_name : str
|
|
523
|
+
Name of the field to process.
|
|
524
|
+
model : Model
|
|
525
|
+
Django model class.
|
|
526
|
+
relations_serializers : dict
|
|
527
|
+
Mapping of relation field names to serializer classes.
|
|
528
|
+
relations_as_id : list[str]
|
|
529
|
+
Pre-fetched list of fields to serialize as IDs.
|
|
530
|
+
|
|
531
|
+
Returns
|
|
532
|
+
-------
|
|
533
|
+
tuple
|
|
449
534
|
(plain_field, reverse_rel, forward_rel) - only one will be non-None
|
|
450
535
|
"""
|
|
451
536
|
field_obj = getattr(model, field_name)
|
|
452
537
|
|
|
453
538
|
if cls._is_reverse_relation(field_obj):
|
|
454
|
-
if
|
|
539
|
+
if (
|
|
540
|
+
field_name not in relations_serializers
|
|
541
|
+
and field_name not in relations_as_id
|
|
542
|
+
):
|
|
455
543
|
cls._warn_missing_relation_serializer(field_name, model)
|
|
456
|
-
rel_tuple = cls._build_schema_reverse_rel(
|
|
544
|
+
rel_tuple = cls._build_schema_reverse_rel(
|
|
545
|
+
field_name, field_obj, relations_as_id
|
|
546
|
+
)
|
|
457
547
|
return (None, rel_tuple, None)
|
|
458
548
|
|
|
459
549
|
if cls._is_forward_relation(field_obj):
|
|
460
|
-
rel_tuple = cls._build_schema_forward_rel(
|
|
550
|
+
rel_tuple = cls._build_schema_forward_rel(
|
|
551
|
+
field_name, field_obj, relations_as_id
|
|
552
|
+
)
|
|
461
553
|
if rel_tuple is True:
|
|
462
554
|
return (field_name, None, None)
|
|
463
555
|
return (None, None, rel_tuple)
|
|
@@ -469,8 +561,15 @@ class BaseSerializer:
|
|
|
469
561
|
"""
|
|
470
562
|
Collect components for output schema generation (Out or Detail).
|
|
471
563
|
|
|
472
|
-
|
|
473
|
-
|
|
564
|
+
Parameters
|
|
565
|
+
----------
|
|
566
|
+
schema_type : Literal["Out", "Detail"]
|
|
567
|
+
Type of schema to generate.
|
|
568
|
+
|
|
569
|
+
Returns
|
|
570
|
+
-------
|
|
571
|
+
tuple
|
|
572
|
+
(fields, reverse_rels, excludes, customs_with_forward_rels, optionals)
|
|
474
573
|
"""
|
|
475
574
|
if schema_type not in ("Out", "Detail"):
|
|
476
575
|
raise ValueError(
|
|
@@ -480,6 +579,8 @@ class BaseSerializer:
|
|
|
480
579
|
fields_type = "read" if schema_type == "Out" else "detail"
|
|
481
580
|
model = cls._get_model()
|
|
482
581
|
relations_serializers = cls._get_relations_serializers() or {}
|
|
582
|
+
# Fetch once to avoid repeated method calls during field processing
|
|
583
|
+
relations_as_id = cls._get_relations_as_id()
|
|
483
584
|
|
|
484
585
|
fields: list[str] = []
|
|
485
586
|
reverse_rels: list[tuple] = []
|
|
@@ -487,7 +588,7 @@ class BaseSerializer:
|
|
|
487
588
|
|
|
488
589
|
for field_name in cls.get_fields(fields_type):
|
|
489
590
|
plain, reverse, forward = cls._process_field(
|
|
490
|
-
field_name, model, relations_serializers
|
|
591
|
+
field_name, model, relations_serializers, relations_as_id
|
|
491
592
|
)
|
|
492
593
|
if plain:
|
|
493
594
|
fields.append(plain)
|
|
@@ -701,12 +802,15 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
701
802
|
Computed / synthetic output attributes.
|
|
702
803
|
optionals : list[tuple[str, type]]
|
|
703
804
|
Optional output fields.
|
|
805
|
+
relations_as_id : list[str]
|
|
806
|
+
Relation fields to serialize as IDs instead of nested objects.
|
|
704
807
|
"""
|
|
705
808
|
|
|
706
809
|
fields: list[str] = []
|
|
707
810
|
customs: list[tuple[str, type, Any]] = []
|
|
708
811
|
optionals: list[tuple[str, type]] = []
|
|
709
812
|
excludes: list[str] = []
|
|
813
|
+
relations_as_id: list[str] = []
|
|
710
814
|
|
|
711
815
|
class DetailSerializer:
|
|
712
816
|
"""Configuration describing detail (single object) read schema.
|
|
@@ -756,6 +860,11 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
756
860
|
"detail": "DetailSerializer",
|
|
757
861
|
}
|
|
758
862
|
|
|
863
|
+
@classmethod
|
|
864
|
+
def _get_relations_as_id(cls) -> list[str]:
|
|
865
|
+
"""Return relation fields to serialize as IDs instead of nested objects."""
|
|
866
|
+
return getattr(cls.ReadSerializer, "relations_as_id", [])
|
|
867
|
+
|
|
759
868
|
@classmethod
|
|
760
869
|
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
761
870
|
"""
|
|
@@ -960,6 +1069,12 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
960
1069
|
schema_update: Optional[SchemaModelConfig] = None
|
|
961
1070
|
schema_detail: Optional[SchemaModelConfig] = None
|
|
962
1071
|
relations_serializers: dict[str, "Serializer"] = {}
|
|
1072
|
+
relations_as_id: list[str] = []
|
|
1073
|
+
|
|
1074
|
+
@classmethod
|
|
1075
|
+
def _get_relations_as_id(cls) -> list[str]:
|
|
1076
|
+
relations_as_id = cls._get_meta_data("relations_as_id")
|
|
1077
|
+
return relations_as_id or []
|
|
963
1078
|
|
|
964
1079
|
@classmethod
|
|
965
1080
|
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.14.0.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|