django-ninja-aio-crud 2.16.2__py3-none-any.whl → 2.18.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.18.0.dist-info/METADATA +425 -0
- {django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/RECORD +6 -6
- ninja_aio/__init__.py +1 -1
- ninja_aio/models/serializers.py +637 -74
- django_ninja_aio_crud-2.16.2.dist-info/METADATA +0 -379
- {django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/WHEEL +0 -0
- {django_ninja_aio_crud-2.16.2.dist-info → django_ninja_aio_crud-2.18.0.dist-info}/licenses/LICENSE +0 -0
ninja_aio/models/serializers.py
CHANGED
|
@@ -25,6 +25,7 @@ from django.db.models.fields.related_descriptors import (
|
|
|
25
25
|
ForwardOneToOneDescriptor,
|
|
26
26
|
)
|
|
27
27
|
from pydantic import BeforeValidator, Field
|
|
28
|
+
from pydantic._internal._decorators import PydanticDescriptorProxy
|
|
28
29
|
|
|
29
30
|
from ninja_aio.types import (
|
|
30
31
|
S_TYPES,
|
|
@@ -102,14 +103,116 @@ class BaseSerializer:
|
|
|
102
103
|
queryset_request = ModelQuerySetSchema()
|
|
103
104
|
extras: list[ModelQuerySetExtraSchema] = []
|
|
104
105
|
|
|
106
|
+
@classmethod
|
|
107
|
+
def _collect_validators(cls, source_class) -> dict:
|
|
108
|
+
"""
|
|
109
|
+
Collect Pydantic validator descriptors from a class.
|
|
110
|
+
|
|
111
|
+
Iterates over the class attributes looking for ``PydanticDescriptorProxy``
|
|
112
|
+
instances (created by ``@field_validator`` and ``@model_validator`` decorators).
|
|
113
|
+
|
|
114
|
+
Parameters
|
|
115
|
+
----------
|
|
116
|
+
source_class : type | None
|
|
117
|
+
The class to scan for validators.
|
|
118
|
+
|
|
119
|
+
Returns
|
|
120
|
+
-------
|
|
121
|
+
dict
|
|
122
|
+
Mapping of attribute name to ``PydanticDescriptorProxy`` instance.
|
|
123
|
+
"""
|
|
124
|
+
validators = {}
|
|
125
|
+
if source_class is None:
|
|
126
|
+
return validators
|
|
127
|
+
for attr_name, attr_value in vars(source_class).items():
|
|
128
|
+
if isinstance(attr_value, PydanticDescriptorProxy):
|
|
129
|
+
validators[attr_name] = attr_value
|
|
130
|
+
return validators
|
|
131
|
+
|
|
132
|
+
@classmethod
|
|
133
|
+
def _apply_validators(cls, schema, validators: dict):
|
|
134
|
+
"""
|
|
135
|
+
Create a subclass of the given schema with validators attached.
|
|
136
|
+
|
|
137
|
+
Pydantic discovers validators via ``PydanticDescriptorProxy`` instances
|
|
138
|
+
during class creation, so placing them on a subclass is sufficient.
|
|
139
|
+
|
|
140
|
+
Parameters
|
|
141
|
+
----------
|
|
142
|
+
schema : Schema | None
|
|
143
|
+
The base schema class generated by ``create_schema``.
|
|
144
|
+
validators : dict
|
|
145
|
+
Mapping of validator names to ``PydanticDescriptorProxy`` instances.
|
|
146
|
+
|
|
147
|
+
Returns
|
|
148
|
+
-------
|
|
149
|
+
Schema | None
|
|
150
|
+
A subclass with validators applied, or the original schema if no
|
|
151
|
+
validators are provided.
|
|
152
|
+
"""
|
|
153
|
+
if not schema or not validators:
|
|
154
|
+
return schema
|
|
155
|
+
return type(schema.__name__, (schema,), validators)
|
|
156
|
+
|
|
157
|
+
@classmethod
|
|
158
|
+
def _get_validators(cls, schema_type: type[SCHEMA_TYPES]) -> dict:
|
|
159
|
+
"""
|
|
160
|
+
Return collected validators for the given schema type.
|
|
161
|
+
|
|
162
|
+
Subclasses must implement this to map schema types to the appropriate
|
|
163
|
+
validator source class.
|
|
164
|
+
|
|
165
|
+
Parameters
|
|
166
|
+
----------
|
|
167
|
+
schema_type : SCHEMA_TYPES
|
|
168
|
+
One of ``"In"``, ``"Patch"``, ``"Out"``, ``"Detail"``, or ``"Related"``.
|
|
169
|
+
|
|
170
|
+
Returns
|
|
171
|
+
-------
|
|
172
|
+
dict
|
|
173
|
+
Mapping of validator names to ``PydanticDescriptorProxy`` instances.
|
|
174
|
+
"""
|
|
175
|
+
return {}
|
|
176
|
+
|
|
105
177
|
@classmethod
|
|
106
178
|
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
107
|
-
|
|
179
|
+
"""
|
|
180
|
+
Return raw configuration list for the given serializer/field category.
|
|
181
|
+
|
|
182
|
+
Parameters
|
|
183
|
+
----------
|
|
184
|
+
s_type : S_TYPES
|
|
185
|
+
Serializer type (``"create"`` | ``"update"`` | ``"read"`` | ``"detail"``).
|
|
186
|
+
f_type : F_TYPES
|
|
187
|
+
Field category (``"fields"`` | ``"optionals"`` | ``"customs"`` | ``"excludes"``).
|
|
188
|
+
|
|
189
|
+
Returns
|
|
190
|
+
-------
|
|
191
|
+
list
|
|
192
|
+
Raw configuration list for the requested category.
|
|
193
|
+
|
|
194
|
+
Raises
|
|
195
|
+
------
|
|
196
|
+
NotImplementedError
|
|
197
|
+
Subclasses must provide an implementation.
|
|
198
|
+
"""
|
|
108
199
|
raise NotImplementedError
|
|
109
200
|
|
|
110
201
|
@classmethod
|
|
111
202
|
def _get_model(cls) -> models.Model:
|
|
112
|
-
|
|
203
|
+
"""
|
|
204
|
+
Return the Django model class associated with this serializer.
|
|
205
|
+
|
|
206
|
+
Returns
|
|
207
|
+
-------
|
|
208
|
+
models.Model
|
|
209
|
+
The Django model class.
|
|
210
|
+
|
|
211
|
+
Raises
|
|
212
|
+
------
|
|
213
|
+
NotImplementedError
|
|
214
|
+
Subclasses must provide an implementation.
|
|
215
|
+
"""
|
|
113
216
|
raise NotImplementedError
|
|
114
217
|
|
|
115
218
|
@classmethod
|
|
@@ -246,12 +349,32 @@ class BaseSerializer:
|
|
|
246
349
|
|
|
247
350
|
@classmethod
|
|
248
351
|
def _get_relations_serializers(cls) -> dict[str, "Serializer"]:
|
|
249
|
-
|
|
352
|
+
"""
|
|
353
|
+
Return mapping of relation field names to their serializer classes.
|
|
354
|
+
|
|
355
|
+
Subclasses may override to provide explicit serializer mappings for
|
|
356
|
+
relation fields used during read schema generation.
|
|
357
|
+
|
|
358
|
+
Returns
|
|
359
|
+
-------
|
|
360
|
+
dict[str, Serializer]
|
|
361
|
+
Mapping of field name to serializer class. Empty by default.
|
|
362
|
+
"""
|
|
250
363
|
return {}
|
|
251
364
|
|
|
252
365
|
@classmethod
|
|
253
366
|
def _get_relations_as_id(cls) -> list[str]:
|
|
254
|
-
|
|
367
|
+
"""
|
|
368
|
+
Return relation field names that should be serialized as IDs.
|
|
369
|
+
|
|
370
|
+
Subclasses may override to specify which relation fields should be
|
|
371
|
+
represented as primary key values instead of nested objects.
|
|
372
|
+
|
|
373
|
+
Returns
|
|
374
|
+
-------
|
|
375
|
+
list[str]
|
|
376
|
+
Field names to serialize as IDs. Empty by default.
|
|
377
|
+
"""
|
|
255
378
|
return []
|
|
256
379
|
|
|
257
380
|
@classmethod
|
|
@@ -305,7 +428,9 @@ class BaseSerializer:
|
|
|
305
428
|
"""
|
|
306
429
|
# Auto-resolve ModelSerializer with readable fields
|
|
307
430
|
if isinstance(rel_model, ModelSerializerMeta):
|
|
308
|
-
has_readable_fields = rel_model.get_fields(
|
|
431
|
+
has_readable_fields = rel_model.get_fields(
|
|
432
|
+
"read"
|
|
433
|
+
) or rel_model.get_custom_fields("read")
|
|
309
434
|
return rel_model.generate_related_s() if has_readable_fields else None
|
|
310
435
|
|
|
311
436
|
# Resolve from explicit serializer mapping
|
|
@@ -328,20 +453,53 @@ class BaseSerializer:
|
|
|
328
453
|
def _is_special_field(
|
|
329
454
|
cls, s_type: type[S_TYPES], field: str, f_type: type[F_TYPES]
|
|
330
455
|
) -> bool:
|
|
331
|
-
"""
|
|
456
|
+
"""
|
|
457
|
+
Check whether a field appears in the given category for a serializer type.
|
|
458
|
+
|
|
459
|
+
Parameters
|
|
460
|
+
----------
|
|
461
|
+
s_type : S_TYPES
|
|
462
|
+
Serializer type (``"create"`` | ``"update"`` | ``"read"`` | ``"detail"``).
|
|
463
|
+
field : str
|
|
464
|
+
The field name to look up.
|
|
465
|
+
f_type : F_TYPES
|
|
466
|
+
Field category (``"fields"`` | ``"optionals"`` | ``"customs"`` | ``"excludes"``).
|
|
467
|
+
|
|
468
|
+
Returns
|
|
469
|
+
-------
|
|
470
|
+
bool
|
|
471
|
+
``True`` if the field is found in the specified category.
|
|
472
|
+
"""
|
|
332
473
|
special_fields = cls._get_fields(s_type, f_type)
|
|
333
474
|
return any(field in special_f for special_f in special_fields)
|
|
334
475
|
|
|
335
476
|
@classmethod
|
|
336
477
|
def get_custom_fields(cls, s_type: type[S_TYPES]) -> list[tuple[str, type, Any]]:
|
|
337
478
|
"""
|
|
338
|
-
Normalize declared custom field specs into (name, py_type, default).
|
|
479
|
+
Normalize declared custom field specs into ``(name, py_type, default)`` tuples.
|
|
480
|
+
|
|
339
481
|
Accepted tuple shapes:
|
|
340
|
-
|
|
341
|
-
- (name, py_type)
|
|
482
|
+
|
|
483
|
+
- ``(name, py_type, default)`` -- field with explicit default.
|
|
484
|
+
- ``(name, py_type)`` -- required field (default set to ``Ellipsis``).
|
|
485
|
+
|
|
486
|
+
Parameters
|
|
487
|
+
----------
|
|
488
|
+
s_type : S_TYPES
|
|
489
|
+
Serializer type whose custom fields to retrieve.
|
|
490
|
+
|
|
491
|
+
Returns
|
|
492
|
+
-------
|
|
493
|
+
list[tuple[str, type, Any]]
|
|
494
|
+
Normalized list of ``(name, type, default)`` tuples.
|
|
495
|
+
|
|
496
|
+
Raises
|
|
497
|
+
------
|
|
498
|
+
ValueError
|
|
499
|
+
If a custom field spec is not a tuple or has an invalid length.
|
|
342
500
|
"""
|
|
343
501
|
raw_customs = cls._get_fields(s_type, "customs") or []
|
|
344
|
-
normalized: list[tuple[str,
|
|
502
|
+
normalized: list[tuple[str, Any, Any]] = []
|
|
345
503
|
for spec in raw_customs:
|
|
346
504
|
if not isinstance(spec, tuple):
|
|
347
505
|
raise ValueError(f"Custom field spec must be a tuple, got {type(spec)}")
|
|
@@ -360,7 +518,19 @@ class BaseSerializer:
|
|
|
360
518
|
|
|
361
519
|
@classmethod
|
|
362
520
|
def get_optional_fields(cls, s_type: type[S_TYPES]):
|
|
363
|
-
"""
|
|
521
|
+
"""
|
|
522
|
+
Return optional field specs normalized to ``(name, type, None)`` tuples.
|
|
523
|
+
|
|
524
|
+
Parameters
|
|
525
|
+
----------
|
|
526
|
+
s_type : S_TYPES
|
|
527
|
+
Serializer type whose optional fields to retrieve.
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
-------
|
|
531
|
+
list[tuple[str, type, None]]
|
|
532
|
+
Normalized list where each entry defaults to ``None``.
|
|
533
|
+
"""
|
|
364
534
|
return [
|
|
365
535
|
(field, field_type, None)
|
|
366
536
|
for field, field_type in cls._get_fields(s_type, "optionals")
|
|
@@ -368,24 +538,108 @@ class BaseSerializer:
|
|
|
368
538
|
|
|
369
539
|
@classmethod
|
|
370
540
|
def get_excluded_fields(cls, s_type: S_TYPES):
|
|
371
|
-
"""
|
|
541
|
+
"""
|
|
542
|
+
Return excluded field names for the given serializer type.
|
|
543
|
+
|
|
544
|
+
Parameters
|
|
545
|
+
----------
|
|
546
|
+
s_type : S_TYPES
|
|
547
|
+
Serializer type whose exclusions to retrieve.
|
|
548
|
+
|
|
549
|
+
Returns
|
|
550
|
+
-------
|
|
551
|
+
list[str]
|
|
552
|
+
Field names excluded from schema generation.
|
|
553
|
+
"""
|
|
372
554
|
return cls._get_fields(s_type, "excludes")
|
|
373
555
|
|
|
374
556
|
@classmethod
|
|
375
557
|
def get_fields(cls, s_type: S_TYPES):
|
|
376
|
-
"""
|
|
377
|
-
|
|
558
|
+
"""
|
|
559
|
+
Return explicit declared field names for the serializer type.
|
|
560
|
+
|
|
561
|
+
Filters out inline custom field tuples from the fields list, returning
|
|
562
|
+
only string field names.
|
|
563
|
+
|
|
564
|
+
Parameters
|
|
565
|
+
----------
|
|
566
|
+
s_type : S_TYPES
|
|
567
|
+
Serializer type whose fields to retrieve.
|
|
568
|
+
|
|
569
|
+
Returns
|
|
570
|
+
-------
|
|
571
|
+
list[str]
|
|
572
|
+
Model field names (excludes inline custom tuples).
|
|
573
|
+
"""
|
|
574
|
+
fields = cls._get_fields(s_type, "fields")
|
|
575
|
+
# Filter out inline custom field tuples, return only string field names
|
|
576
|
+
return [f for f in fields if isinstance(f, str)]
|
|
577
|
+
|
|
578
|
+
@classmethod
|
|
579
|
+
def get_inline_customs(cls, s_type: S_TYPES) -> list[tuple[str, Any, Any]]:
|
|
580
|
+
"""
|
|
581
|
+
Return inline custom field tuples declared directly in the fields list.
|
|
582
|
+
|
|
583
|
+
These are tuples in the format (name, type, default) or (name, type) mixed
|
|
584
|
+
with regular string field names in the fields list.
|
|
585
|
+
|
|
586
|
+
Returns
|
|
587
|
+
-------
|
|
588
|
+
list[tuple[str, Any, Any]]
|
|
589
|
+
Normalized list of (name, type, default) tuples.
|
|
590
|
+
"""
|
|
591
|
+
fields = cls._get_fields(s_type, "fields")
|
|
592
|
+
inline_customs: list[tuple[str, Any, Any]] = []
|
|
593
|
+
for spec in fields:
|
|
594
|
+
if isinstance(spec, tuple):
|
|
595
|
+
match len(spec):
|
|
596
|
+
case 3:
|
|
597
|
+
inline_customs.append(spec)
|
|
598
|
+
case 2:
|
|
599
|
+
name, py_type = spec
|
|
600
|
+
inline_customs.append((name, py_type, ...))
|
|
601
|
+
case _:
|
|
602
|
+
raise ValueError(
|
|
603
|
+
f"Inline custom field tuple must have length 2 or 3 (name, type[, default]); got {len(spec)}"
|
|
604
|
+
)
|
|
605
|
+
return inline_customs
|
|
378
606
|
|
|
379
607
|
@classmethod
|
|
380
608
|
def is_custom(cls, field: str) -> bool:
|
|
381
|
-
"""
|
|
609
|
+
"""
|
|
610
|
+
Check if a field is declared as a custom input in create or update.
|
|
611
|
+
|
|
612
|
+
Parameters
|
|
613
|
+
----------
|
|
614
|
+
field : str
|
|
615
|
+
The field name to check.
|
|
616
|
+
|
|
617
|
+
Returns
|
|
618
|
+
-------
|
|
619
|
+
bool
|
|
620
|
+
``True`` if the field appears in the customs category for either
|
|
621
|
+
``create`` or ``update`` serializer types.
|
|
622
|
+
"""
|
|
382
623
|
return cls._is_special_field(
|
|
383
624
|
"create", field, "customs"
|
|
384
625
|
) or cls._is_special_field("update", field, "customs")
|
|
385
626
|
|
|
386
627
|
@classmethod
|
|
387
628
|
def is_optional(cls, field: str) -> bool:
|
|
388
|
-
"""
|
|
629
|
+
"""
|
|
630
|
+
Check if a field is declared as optional in create or update.
|
|
631
|
+
|
|
632
|
+
Parameters
|
|
633
|
+
----------
|
|
634
|
+
field : str
|
|
635
|
+
The field name to check.
|
|
636
|
+
|
|
637
|
+
Returns
|
|
638
|
+
-------
|
|
639
|
+
bool
|
|
640
|
+
``True`` if the field appears in the optionals category for either
|
|
641
|
+
``create`` or ``update`` serializer types.
|
|
642
|
+
"""
|
|
389
643
|
return cls._is_special_field(
|
|
390
644
|
"create", field, "optionals"
|
|
391
645
|
) or cls._is_special_field("update", field, "optionals")
|
|
@@ -430,10 +684,15 @@ class BaseSerializer:
|
|
|
430
684
|
# Handle relations_as_id for reverse relations
|
|
431
685
|
if field_name in relations_as_id:
|
|
432
686
|
from ninja_aio.models.utils import ModelUtil
|
|
687
|
+
|
|
433
688
|
pk_field_type = ModelUtil(rel_model).pk_field_type
|
|
434
689
|
if many:
|
|
435
690
|
# For many relations, use PkFromModel to extract PKs from model instances
|
|
436
|
-
return (
|
|
691
|
+
return (
|
|
692
|
+
field_name,
|
|
693
|
+
list[PkFromModel[pk_field_type]],
|
|
694
|
+
Field(default_factory=list),
|
|
695
|
+
)
|
|
437
696
|
else:
|
|
438
697
|
# For single reverse relations (ReverseOneToOne), extract pk
|
|
439
698
|
return (field_name, PkFromModel[pk_field_type] | None, None)
|
|
@@ -472,6 +731,7 @@ class BaseSerializer:
|
|
|
472
731
|
# Handle relations_as_id: serialize as the raw FK ID
|
|
473
732
|
if field_name in relations_as_id:
|
|
474
733
|
from ninja_aio.models.utils import ModelUtil
|
|
734
|
+
|
|
475
735
|
pk_field_type = ModelUtil(rel_model).pk_field_type
|
|
476
736
|
# Use PkFromModel to extract pk from the related instance during serialization
|
|
477
737
|
return (field_name, PkFromModel[pk_field_type] | None, None)
|
|
@@ -492,7 +752,20 @@ class BaseSerializer:
|
|
|
492
752
|
|
|
493
753
|
@classmethod
|
|
494
754
|
def _is_reverse_relation(cls, field_obj) -> bool:
|
|
495
|
-
"""
|
|
755
|
+
"""
|
|
756
|
+
Check if a field descriptor represents a reverse relation.
|
|
757
|
+
|
|
758
|
+
Parameters
|
|
759
|
+
----------
|
|
760
|
+
field_obj : Any
|
|
761
|
+
Django model field descriptor.
|
|
762
|
+
|
|
763
|
+
Returns
|
|
764
|
+
-------
|
|
765
|
+
bool
|
|
766
|
+
``True`` if the descriptor is a ``ManyToManyDescriptor``,
|
|
767
|
+
``ReverseManyToOneDescriptor``, or ``ReverseOneToOneDescriptor``.
|
|
768
|
+
"""
|
|
496
769
|
return isinstance(
|
|
497
770
|
field_obj,
|
|
498
771
|
(
|
|
@@ -504,14 +777,39 @@ class BaseSerializer:
|
|
|
504
777
|
|
|
505
778
|
@classmethod
|
|
506
779
|
def _is_forward_relation(cls, field_obj) -> bool:
|
|
507
|
-
"""
|
|
780
|
+
"""
|
|
781
|
+
Check if a field descriptor represents a forward relation.
|
|
782
|
+
|
|
783
|
+
Parameters
|
|
784
|
+
----------
|
|
785
|
+
field_obj : Any
|
|
786
|
+
Django model field descriptor.
|
|
787
|
+
|
|
788
|
+
Returns
|
|
789
|
+
-------
|
|
790
|
+
bool
|
|
791
|
+
``True`` if the descriptor is a ``ForwardOneToOneDescriptor``
|
|
792
|
+
or ``ForwardManyToOneDescriptor``.
|
|
793
|
+
"""
|
|
508
794
|
return isinstance(
|
|
509
795
|
field_obj, (ForwardOneToOneDescriptor, ForwardManyToOneDescriptor)
|
|
510
796
|
)
|
|
511
797
|
|
|
512
798
|
@classmethod
|
|
513
799
|
def _warn_missing_relation_serializer(cls, field_name: str, model) -> None:
|
|
514
|
-
"""
|
|
800
|
+
"""
|
|
801
|
+
Emit a warning for reverse relations without an explicit serializer mapping.
|
|
802
|
+
|
|
803
|
+
Only warns when the related model is not a ``ModelSerializer`` and the
|
|
804
|
+
``NINJA_AIO_RAISE_SERIALIZATION_WARNINGS`` setting is enabled (default).
|
|
805
|
+
|
|
806
|
+
Parameters
|
|
807
|
+
----------
|
|
808
|
+
field_name : str
|
|
809
|
+
Name of the reverse relation field.
|
|
810
|
+
model : type
|
|
811
|
+
The Django model class owning the field.
|
|
812
|
+
"""
|
|
515
813
|
if not isinstance(model, ModelSerializerMeta) and getattr(
|
|
516
814
|
settings, "NINJA_AIO_RAISE_SERIALIZATION_WARNINGS", True
|
|
517
815
|
):
|
|
@@ -614,11 +912,18 @@ class BaseSerializer:
|
|
|
614
912
|
if forward:
|
|
615
913
|
forward_rels.append(forward)
|
|
616
914
|
|
|
915
|
+
# Combine explicit customs, inline customs, and forward relation schemas
|
|
916
|
+
all_customs = (
|
|
917
|
+
cls.get_custom_fields(fields_type)
|
|
918
|
+
+ cls.get_inline_customs(fields_type)
|
|
919
|
+
+ forward_rels
|
|
920
|
+
)
|
|
921
|
+
|
|
617
922
|
return (
|
|
618
923
|
fields,
|
|
619
924
|
reverse_rels,
|
|
620
925
|
cls.get_excluded_fields(fields_type),
|
|
621
|
-
|
|
926
|
+
all_customs,
|
|
622
927
|
cls.get_optional_fields(fields_type),
|
|
623
928
|
)
|
|
624
929
|
|
|
@@ -629,10 +934,26 @@ class BaseSerializer:
|
|
|
629
934
|
depth: int = None,
|
|
630
935
|
) -> Schema:
|
|
631
936
|
"""
|
|
632
|
-
Core schema factory bridging configuration to ninja.orm.create_schema
|
|
633
|
-
|
|
937
|
+
Core schema factory bridging serializer configuration to ``ninja.orm.create_schema``.
|
|
938
|
+
|
|
939
|
+
Dispatches to the appropriate field/custom/exclude gathering logic based
|
|
940
|
+
on the requested schema type and delegates to Django Ninja's
|
|
941
|
+
``create_schema`` for the actual Pydantic model construction.
|
|
942
|
+
|
|
943
|
+
Parameters
|
|
944
|
+
----------
|
|
945
|
+
schema_type : SCHEMA_TYPES
|
|
946
|
+
One of ``"In"``, ``"Patch"``, ``"Out"``, ``"Detail"``, or ``"Related"``.
|
|
947
|
+
depth : int, optional
|
|
948
|
+
Nesting depth for related model schemas (used by ``Out`` and ``Detail``).
|
|
949
|
+
|
|
950
|
+
Returns
|
|
951
|
+
-------
|
|
952
|
+
Schema | None
|
|
953
|
+
Generated Pydantic schema, or ``None`` if no fields are configured.
|
|
634
954
|
"""
|
|
635
955
|
model = cls._get_model()
|
|
956
|
+
validators = cls._get_validators(schema_type)
|
|
636
957
|
|
|
637
958
|
# Handle special schema types with custom logic
|
|
638
959
|
if schema_type == "Out" or schema_type == "Detail":
|
|
@@ -642,7 +963,7 @@ class BaseSerializer:
|
|
|
642
963
|
if not any([fields, reverse_rels, excludes, customs]):
|
|
643
964
|
return None
|
|
644
965
|
schema_name = "SchemaOut" if schema_type == "Out" else "DetailSchemaOut"
|
|
645
|
-
|
|
966
|
+
schema = create_schema(
|
|
646
967
|
model=model,
|
|
647
968
|
name=f"{model._meta.model_name}{schema_name}",
|
|
648
969
|
depth=depth,
|
|
@@ -650,23 +971,27 @@ class BaseSerializer:
|
|
|
650
971
|
custom_fields=reverse_rels + customs + optionals,
|
|
651
972
|
exclude=excludes,
|
|
652
973
|
)
|
|
974
|
+
return cls._apply_validators(schema, validators)
|
|
653
975
|
|
|
654
976
|
if schema_type == "Related":
|
|
655
977
|
fields, customs = cls.get_related_schema_data()
|
|
656
978
|
if not fields and not customs:
|
|
657
979
|
return None
|
|
658
|
-
|
|
980
|
+
schema = create_schema(
|
|
659
981
|
model=model,
|
|
660
982
|
name=f"{model._meta.model_name}SchemaRelated",
|
|
661
983
|
fields=fields,
|
|
662
984
|
custom_fields=customs,
|
|
663
985
|
)
|
|
986
|
+
return cls._apply_validators(schema, validators)
|
|
664
987
|
|
|
665
988
|
# Handle standard In/Patch schema types
|
|
666
989
|
s_type = "create" if schema_type == "In" else "update"
|
|
667
990
|
fields = cls.get_fields(s_type)
|
|
668
991
|
optionals = cls.get_optional_fields(s_type)
|
|
669
|
-
customs =
|
|
992
|
+
customs = (
|
|
993
|
+
cls.get_custom_fields(s_type) + optionals + cls.get_inline_customs(s_type)
|
|
994
|
+
)
|
|
670
995
|
excludes = cls.get_excluded_fields(s_type)
|
|
671
996
|
|
|
672
997
|
# If no explicit fields and no excludes specified
|
|
@@ -686,29 +1011,32 @@ class BaseSerializer:
|
|
|
686
1011
|
if not any([fields, customs, excludes]):
|
|
687
1012
|
return None
|
|
688
1013
|
|
|
689
|
-
|
|
1014
|
+
schema = create_schema(
|
|
690
1015
|
model=model,
|
|
691
1016
|
name=f"{model._meta.model_name}Schema{schema_type}",
|
|
692
1017
|
fields=fields,
|
|
693
1018
|
custom_fields=customs,
|
|
694
1019
|
exclude=excludes,
|
|
695
1020
|
)
|
|
1021
|
+
return cls._apply_validators(schema, validators)
|
|
696
1022
|
|
|
697
1023
|
@classmethod
|
|
698
1024
|
def get_related_schema_data(cls):
|
|
699
1025
|
"""
|
|
700
1026
|
Build field/custom lists for 'Related' schema, flattening non-relational fields.
|
|
1027
|
+
|
|
1028
|
+
Custom fields (both explicit and inline) are always included since they
|
|
1029
|
+
are computed/synthetic and not relation descriptors.
|
|
701
1030
|
"""
|
|
702
1031
|
fields = cls.get_fields("read")
|
|
703
|
-
|
|
704
|
-
name: (value, default)
|
|
705
|
-
for name, value, default in cls.get_custom_fields("read")
|
|
706
|
-
}
|
|
707
|
-
_related_fields = []
|
|
1032
|
+
customs = cls.get_custom_fields("read") + cls.get_inline_customs("read")
|
|
708
1033
|
model = cls._get_model()
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
1034
|
+
|
|
1035
|
+
# Filter out relation fields from model fields
|
|
1036
|
+
non_relation_fields = []
|
|
1037
|
+
for f in fields:
|
|
1038
|
+
field_obj = getattr(model, f, None)
|
|
1039
|
+
if field_obj is None or not isinstance(
|
|
712
1040
|
field_obj,
|
|
713
1041
|
(
|
|
714
1042
|
ManyToManyDescriptor,
|
|
@@ -718,38 +1046,88 @@ class BaseSerializer:
|
|
|
718
1046
|
ForwardOneToOneDescriptor,
|
|
719
1047
|
),
|
|
720
1048
|
):
|
|
721
|
-
|
|
722
|
-
|
|
1049
|
+
non_relation_fields.append(f)
|
|
1050
|
+
|
|
1051
|
+
# No fields or customs means nothing to include
|
|
1052
|
+
if not non_relation_fields and not customs:
|
|
723
1053
|
return None, None
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
]
|
|
727
|
-
related_fields = [f for f in _related_fields if f not in custom_f]
|
|
728
|
-
return related_fields, custom_related_fields
|
|
1054
|
+
|
|
1055
|
+
return non_relation_fields, customs
|
|
729
1056
|
|
|
730
1057
|
@classmethod
|
|
731
1058
|
def generate_read_s(cls, depth: int = 1) -> Schema:
|
|
732
|
-
"""
|
|
1059
|
+
"""
|
|
1060
|
+
Generate the read (Out) schema for list responses.
|
|
1061
|
+
|
|
1062
|
+
Parameters
|
|
1063
|
+
----------
|
|
1064
|
+
depth : int, optional
|
|
1065
|
+
Nesting depth for related models. Defaults to ``1``.
|
|
1066
|
+
|
|
1067
|
+
Returns
|
|
1068
|
+
-------
|
|
1069
|
+
Schema | None
|
|
1070
|
+
Generated Pydantic schema, or ``None`` if no read fields are configured.
|
|
1071
|
+
"""
|
|
733
1072
|
return cls._generate_model_schema("Out", depth)
|
|
734
1073
|
|
|
735
1074
|
@classmethod
|
|
736
1075
|
def generate_detail_s(cls, depth: int = 1) -> Schema:
|
|
737
|
-
"""
|
|
1076
|
+
"""
|
|
1077
|
+
Generate the detail (single-object) read schema.
|
|
1078
|
+
|
|
1079
|
+
Falls back to the standard read schema if no detail-specific
|
|
1080
|
+
configuration is defined.
|
|
1081
|
+
|
|
1082
|
+
Parameters
|
|
1083
|
+
----------
|
|
1084
|
+
depth : int, optional
|
|
1085
|
+
Nesting depth for related models. Defaults to ``1``.
|
|
1086
|
+
|
|
1087
|
+
Returns
|
|
1088
|
+
-------
|
|
1089
|
+
Schema
|
|
1090
|
+
Generated Pydantic schema (never ``None``; falls back to read schema).
|
|
1091
|
+
"""
|
|
738
1092
|
return cls._generate_model_schema("Detail", depth) or cls.generate_read_s(depth)
|
|
739
1093
|
|
|
740
1094
|
@classmethod
|
|
741
1095
|
def generate_create_s(cls) -> Schema:
|
|
742
|
-
"""
|
|
1096
|
+
"""
|
|
1097
|
+
Generate the create (In) schema for input validation.
|
|
1098
|
+
|
|
1099
|
+
Returns
|
|
1100
|
+
-------
|
|
1101
|
+
Schema | None
|
|
1102
|
+
Generated Pydantic schema, or ``None`` if no create fields are configured.
|
|
1103
|
+
"""
|
|
743
1104
|
return cls._generate_model_schema("In")
|
|
744
1105
|
|
|
745
1106
|
@classmethod
|
|
746
1107
|
def generate_update_s(cls) -> Schema:
|
|
747
|
-
"""
|
|
1108
|
+
"""
|
|
1109
|
+
Generate the update (Patch) schema for partial updates.
|
|
1110
|
+
|
|
1111
|
+
Returns
|
|
1112
|
+
-------
|
|
1113
|
+
Schema | None
|
|
1114
|
+
Generated Pydantic schema, or ``None`` if no update fields are configured.
|
|
1115
|
+
"""
|
|
748
1116
|
return cls._generate_model_schema("Patch")
|
|
749
1117
|
|
|
750
1118
|
@classmethod
|
|
751
1119
|
def generate_related_s(cls) -> Schema:
|
|
752
|
-
"""
|
|
1120
|
+
"""
|
|
1121
|
+
Generate the related (nested) schema for embedding in parent schemas.
|
|
1122
|
+
|
|
1123
|
+
Includes only non-relational model fields and custom fields, preventing
|
|
1124
|
+
infinite nesting of related objects.
|
|
1125
|
+
|
|
1126
|
+
Returns
|
|
1127
|
+
-------
|
|
1128
|
+
Schema | None
|
|
1129
|
+
Generated Pydantic schema, or ``None`` if no fields are configured.
|
|
1130
|
+
"""
|
|
753
1131
|
return cls._generate_model_schema("Related")
|
|
754
1132
|
|
|
755
1133
|
@classmethod
|
|
@@ -810,9 +1188,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
810
1188
|
Disallowed model fields on create (e.g., id, timestamps).
|
|
811
1189
|
"""
|
|
812
1190
|
|
|
813
|
-
fields: list[str] = []
|
|
814
|
-
customs: list[tuple[str,
|
|
815
|
-
optionals: list[tuple[str,
|
|
1191
|
+
fields: list[str | tuple[str, Any, Any] | tuple[str, Any]] = []
|
|
1192
|
+
customs: list[tuple[str, Any, Any] | tuple[str, Any]] = []
|
|
1193
|
+
optionals: list[tuple[str, Any]] = []
|
|
816
1194
|
excludes: list[str] = []
|
|
817
1195
|
|
|
818
1196
|
class ReadSerializer:
|
|
@@ -832,9 +1210,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
832
1210
|
Relation fields to serialize as IDs instead of nested objects.
|
|
833
1211
|
"""
|
|
834
1212
|
|
|
835
|
-
fields: list[str] = []
|
|
836
|
-
customs: list[tuple[str,
|
|
837
|
-
optionals: list[tuple[str,
|
|
1213
|
+
fields: list[str | tuple[str, Any, Any] | tuple[str, Any]] = []
|
|
1214
|
+
customs: list[tuple[str, Any, Any] | tuple[str, Any]] = []
|
|
1215
|
+
optionals: list[tuple[str, Any]] = []
|
|
838
1216
|
excludes: list[str] = []
|
|
839
1217
|
relations_as_id: list[str] = []
|
|
840
1218
|
|
|
@@ -853,9 +1231,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
853
1231
|
Optional output fields.
|
|
854
1232
|
"""
|
|
855
1233
|
|
|
856
|
-
fields: list[str] = []
|
|
857
|
-
customs: list[tuple[str,
|
|
858
|
-
optionals: list[tuple[str,
|
|
1234
|
+
fields: list[str | tuple[str, Any, Any] | tuple[str, Any]] = []
|
|
1235
|
+
customs: list[tuple[str, Any, Any] | tuple[str, Any]] = []
|
|
1236
|
+
optionals: list[tuple[str, Any]] = []
|
|
859
1237
|
excludes: list[str] = []
|
|
860
1238
|
|
|
861
1239
|
class UpdateSerializer:
|
|
@@ -873,9 +1251,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
873
1251
|
Immutable / blocked fields.
|
|
874
1252
|
"""
|
|
875
1253
|
|
|
876
|
-
fields: list[str] = []
|
|
877
|
-
customs: list[tuple[str,
|
|
878
|
-
optionals: list[tuple[str,
|
|
1254
|
+
fields: list[str | tuple[str, Any, Any]] = []
|
|
1255
|
+
customs: list[tuple[str, Any, Any]] = []
|
|
1256
|
+
optionals: list[tuple[str, Any]] = []
|
|
879
1257
|
excludes: list[str] = []
|
|
880
1258
|
|
|
881
1259
|
# Serializer type to configuration class mapping
|
|
@@ -886,9 +1264,47 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
886
1264
|
"detail": "DetailSerializer",
|
|
887
1265
|
}
|
|
888
1266
|
|
|
1267
|
+
# Schema type to serializer type mapping for validator resolution
|
|
1268
|
+
_SCHEMA_TO_S_TYPE = {
|
|
1269
|
+
"In": "create",
|
|
1270
|
+
"Patch": "update",
|
|
1271
|
+
"Out": "read",
|
|
1272
|
+
"Detail": "detail",
|
|
1273
|
+
"Related": "read",
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
@classmethod
|
|
1277
|
+
def _get_validators(cls, schema_type: type[SCHEMA_TYPES]) -> dict:
|
|
1278
|
+
"""
|
|
1279
|
+
Collect validators from the inner serializer class for the given schema type.
|
|
1280
|
+
|
|
1281
|
+
Parameters
|
|
1282
|
+
----------
|
|
1283
|
+
schema_type : SCHEMA_TYPES
|
|
1284
|
+
One of ``"In"``, ``"Patch"``, ``"Out"``, ``"Detail"``, or ``"Related"``.
|
|
1285
|
+
|
|
1286
|
+
Returns
|
|
1287
|
+
-------
|
|
1288
|
+
dict
|
|
1289
|
+
Mapping of validator names to ``PydanticDescriptorProxy`` instances.
|
|
1290
|
+
"""
|
|
1291
|
+
s_type = cls._SCHEMA_TO_S_TYPE.get(schema_type)
|
|
1292
|
+
config_name = cls._SERIALIZER_CONFIG_MAP.get(s_type)
|
|
1293
|
+
config_class = getattr(cls, config_name, None) if config_name else None
|
|
1294
|
+
return cls._collect_validators(config_class)
|
|
1295
|
+
|
|
889
1296
|
@classmethod
|
|
890
1297
|
def _get_relations_as_id(cls) -> list[str]:
|
|
891
|
-
"""
|
|
1298
|
+
"""
|
|
1299
|
+
Return relation fields to serialize as primary key values.
|
|
1300
|
+
|
|
1301
|
+
Reads the ``relations_as_id`` attribute from ``ReadSerializer``.
|
|
1302
|
+
|
|
1303
|
+
Returns
|
|
1304
|
+
-------
|
|
1305
|
+
list[str]
|
|
1306
|
+
Field names whose related objects should be serialized as IDs.
|
|
1307
|
+
"""
|
|
892
1308
|
return getattr(cls.ReadSerializer, "relations_as_id", [])
|
|
893
1309
|
|
|
894
1310
|
@classmethod
|
|
@@ -919,17 +1335,30 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
|
|
|
919
1335
|
|
|
920
1336
|
@classmethod
|
|
921
1337
|
def _get_model(cls) -> "ModelSerializer":
|
|
922
|
-
"""
|
|
1338
|
+
"""
|
|
1339
|
+
Return the model class itself.
|
|
1340
|
+
|
|
1341
|
+
Since ``ModelSerializer`` is mixed directly into the model, the model
|
|
1342
|
+
class is the serializer class.
|
|
1343
|
+
|
|
1344
|
+
Returns
|
|
1345
|
+
-------
|
|
1346
|
+
ModelSerializer
|
|
1347
|
+
The model/serializer class.
|
|
1348
|
+
"""
|
|
923
1349
|
return cls
|
|
924
1350
|
|
|
925
1351
|
@classmethod
|
|
926
1352
|
def verbose_name_path_resolver(cls) -> str:
|
|
927
1353
|
"""
|
|
928
|
-
Slugify plural verbose name for URL path segment.
|
|
1354
|
+
Slugify the plural verbose name for use as a URL path segment.
|
|
1355
|
+
|
|
1356
|
+
Replaces spaces with hyphens in the model's ``verbose_name_plural``.
|
|
929
1357
|
|
|
930
1358
|
Returns
|
|
931
1359
|
-------
|
|
932
1360
|
str
|
|
1361
|
+
Hyphen-separated URL-safe path segment.
|
|
933
1362
|
"""
|
|
934
1363
|
return "-".join(cls._meta.verbose_name_plural.split(" "))
|
|
935
1364
|
|
|
@@ -1044,20 +1473,24 @@ class SchemaModelConfig(Schema):
|
|
|
1044
1473
|
Configuration container for declarative schema definitions.
|
|
1045
1474
|
Attributes
|
|
1046
1475
|
----------
|
|
1047
|
-
fields : Optional[List[str]]
|
|
1048
|
-
Explicit model fields to include.
|
|
1476
|
+
fields : Optional[List[str | tuple]]
|
|
1477
|
+
Explicit model fields to include. Can also contain inline custom field tuples:
|
|
1478
|
+
- 2-tuple: (name, type) - required field
|
|
1479
|
+
- 3-tuple: (name, type, default) - optional field with default
|
|
1049
1480
|
optionals : Optional[List[tuple[str, Any]]]
|
|
1050
1481
|
Optional model fields. Type can be any valid type annotation including Union.
|
|
1051
1482
|
exclude : Optional[List[str]]
|
|
1052
1483
|
Model fields to exclude.
|
|
1053
|
-
customs : Optional[List[tuple[str, Any, Any]]]
|
|
1484
|
+
customs : Optional[List[tuple[str, Any, Any] | tuple[str, Any]]]
|
|
1054
1485
|
Custom / synthetic fields. Type can be any valid type annotation including Union.
|
|
1486
|
+
- 2-tuple: (name, type) - required field
|
|
1487
|
+
- 3-tuple: (name, type, default) - optional field with default
|
|
1055
1488
|
"""
|
|
1056
1489
|
|
|
1057
|
-
fields: Optional[List[str]] = None
|
|
1490
|
+
fields: Optional[List[str | tuple[str, Any, Any] | tuple[str, Any]]] = None
|
|
1058
1491
|
optionals: Optional[List[tuple[str, Any]]] = None
|
|
1059
1492
|
exclude: Optional[List[str]] = None
|
|
1060
|
-
customs: Optional[List[tuple[str, Any, Any]]] = None
|
|
1493
|
+
customs: Optional[List[tuple[str, Any, Any] | tuple[str, Any]]] = None
|
|
1061
1494
|
|
|
1062
1495
|
|
|
1063
1496
|
class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
@@ -1078,6 +1511,15 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1078
1511
|
"detail": "detail",
|
|
1079
1512
|
}
|
|
1080
1513
|
|
|
1514
|
+
# Schema type to validators inner class mapping
|
|
1515
|
+
_VALIDATORS_CLASS_MAP = {
|
|
1516
|
+
"In": "CreateValidators",
|
|
1517
|
+
"Patch": "UpdateValidators",
|
|
1518
|
+
"Out": "ReadValidators",
|
|
1519
|
+
"Detail": "DetailValidators",
|
|
1520
|
+
"Related": "ReadValidators",
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1081
1523
|
def __init_subclass__(cls, **kwargs):
|
|
1082
1524
|
super().__init_subclass__(**kwargs)
|
|
1083
1525
|
from ninja_aio.models.utils import ModelUtil
|
|
@@ -1097,26 +1539,118 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1097
1539
|
relations_serializers: dict[str, "Serializer"] = {}
|
|
1098
1540
|
relations_as_id: list[str] = []
|
|
1099
1541
|
|
|
1542
|
+
def _parse_payload(self, payload: dict[str, Any] | Schema) -> dict[str, Any]:
|
|
1543
|
+
"""
|
|
1544
|
+
Parse and return the input payload.
|
|
1545
|
+
|
|
1546
|
+
Can be overridden to implement custom parsing logic.
|
|
1547
|
+
|
|
1548
|
+
Parameters
|
|
1549
|
+
----------
|
|
1550
|
+
payload : dict | Schema
|
|
1551
|
+
Input data.
|
|
1552
|
+
|
|
1553
|
+
Returns
|
|
1554
|
+
-------
|
|
1555
|
+
dict
|
|
1556
|
+
Parsed payload.
|
|
1557
|
+
"""
|
|
1558
|
+
return payload.model_dump() if isinstance(payload, Schema) else payload
|
|
1559
|
+
|
|
1560
|
+
@classmethod
|
|
1561
|
+
def _get_validators(cls, schema_type: type[SCHEMA_TYPES]) -> dict:
|
|
1562
|
+
"""
|
|
1563
|
+
Collect validators from the inner validators class for the given schema type.
|
|
1564
|
+
|
|
1565
|
+
Looks for inner classes named ``CreateValidators``, ``ReadValidators``,
|
|
1566
|
+
``UpdateValidators``, or ``DetailValidators`` on the serializer.
|
|
1567
|
+
|
|
1568
|
+
Parameters
|
|
1569
|
+
----------
|
|
1570
|
+
schema_type : SCHEMA_TYPES
|
|
1571
|
+
One of ``"In"``, ``"Patch"``, ``"Out"``, ``"Detail"``, or ``"Related"``.
|
|
1572
|
+
|
|
1573
|
+
Returns
|
|
1574
|
+
-------
|
|
1575
|
+
dict
|
|
1576
|
+
Mapping of validator names to ``PydanticDescriptorProxy`` instances.
|
|
1577
|
+
"""
|
|
1578
|
+
class_name = cls._VALIDATORS_CLASS_MAP.get(schema_type)
|
|
1579
|
+
validators_class = getattr(cls, class_name, None) if class_name else None
|
|
1580
|
+
return cls._collect_validators(validators_class)
|
|
1581
|
+
|
|
1100
1582
|
@classmethod
|
|
1101
1583
|
def _get_relations_as_id(cls) -> list[str]:
|
|
1584
|
+
"""
|
|
1585
|
+
Return relation fields to serialize as primary key values.
|
|
1586
|
+
|
|
1587
|
+
Reads the ``relations_as_id`` attribute from ``Meta``.
|
|
1588
|
+
|
|
1589
|
+
Returns
|
|
1590
|
+
-------
|
|
1591
|
+
list[str]
|
|
1592
|
+
Field names whose related objects should be serialized as IDs.
|
|
1593
|
+
"""
|
|
1102
1594
|
relations_as_id = cls._get_meta_data("relations_as_id")
|
|
1103
1595
|
return relations_as_id or []
|
|
1104
1596
|
|
|
1105
1597
|
@classmethod
|
|
1106
1598
|
def _get_meta_data(cls, attr_name: str) -> Any:
|
|
1599
|
+
"""
|
|
1600
|
+
Retrieve an attribute from the nested ``Meta`` class.
|
|
1601
|
+
|
|
1602
|
+
Parameters
|
|
1603
|
+
----------
|
|
1604
|
+
attr_name : str
|
|
1605
|
+
Name of the ``Meta`` attribute to look up.
|
|
1606
|
+
|
|
1607
|
+
Returns
|
|
1608
|
+
-------
|
|
1609
|
+
Any
|
|
1610
|
+
The attribute value, or ``None`` if not defined.
|
|
1611
|
+
"""
|
|
1107
1612
|
return getattr(cls.Meta, attr_name, None)
|
|
1108
1613
|
|
|
1109
1614
|
@classmethod
|
|
1110
1615
|
def _get_model(cls) -> models.Model:
|
|
1616
|
+
"""
|
|
1617
|
+
Return the Django model class from ``Meta.model``.
|
|
1618
|
+
|
|
1619
|
+
Returns
|
|
1620
|
+
-------
|
|
1621
|
+
models.Model
|
|
1622
|
+
The validated Django model class.
|
|
1623
|
+
"""
|
|
1111
1624
|
return cls._validate_model()
|
|
1112
1625
|
|
|
1113
1626
|
@classmethod
|
|
1114
1627
|
def _get_relations_serializers(cls) -> dict[str, "Serializer"]:
|
|
1628
|
+
"""
|
|
1629
|
+
Return the explicit relation-to-serializer mapping from ``Meta``.
|
|
1630
|
+
|
|
1631
|
+
Returns
|
|
1632
|
+
-------
|
|
1633
|
+
dict[str, Serializer]
|
|
1634
|
+
Mapping of relation field names to serializer classes.
|
|
1635
|
+
"""
|
|
1115
1636
|
relations_serializers = cls._get_meta_data("relations_serializers")
|
|
1116
1637
|
return relations_serializers or {}
|
|
1117
1638
|
|
|
1118
1639
|
@classmethod
|
|
1119
1640
|
def _get_schema_meta(cls, schema_type: str) -> SchemaModelConfig | None:
|
|
1641
|
+
"""
|
|
1642
|
+
Retrieve the ``SchemaModelConfig`` for the given schema type.
|
|
1643
|
+
|
|
1644
|
+
Parameters
|
|
1645
|
+
----------
|
|
1646
|
+
schema_type : str
|
|
1647
|
+
One of ``"in"``, ``"out"``, ``"update"``, or ``"detail"``.
|
|
1648
|
+
|
|
1649
|
+
Returns
|
|
1650
|
+
-------
|
|
1651
|
+
SchemaModelConfig | None
|
|
1652
|
+
The configuration object, or ``None`` if not defined.
|
|
1653
|
+
"""
|
|
1120
1654
|
match schema_type:
|
|
1121
1655
|
case "in":
|
|
1122
1656
|
return cls._get_meta_data("schema_in")
|
|
@@ -1131,6 +1665,19 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1131
1665
|
|
|
1132
1666
|
@classmethod
|
|
1133
1667
|
def _validate_model(cls):
|
|
1668
|
+
"""
|
|
1669
|
+
Validate and return the model defined in ``Meta.model``.
|
|
1670
|
+
|
|
1671
|
+
Returns
|
|
1672
|
+
-------
|
|
1673
|
+
models.Model
|
|
1674
|
+
The validated Django model class.
|
|
1675
|
+
|
|
1676
|
+
Raises
|
|
1677
|
+
------
|
|
1678
|
+
ValueError
|
|
1679
|
+
If ``Meta.model`` is not defined or is not a Django model subclass.
|
|
1680
|
+
"""
|
|
1134
1681
|
model = cls._get_meta_data("model")
|
|
1135
1682
|
if not model:
|
|
1136
1683
|
raise ValueError("Meta.model must be defined for Serializer.")
|
|
@@ -1140,7 +1687,23 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1140
1687
|
|
|
1141
1688
|
@classmethod
|
|
1142
1689
|
def _get_fields(cls, s_type: type[S_TYPES], f_type: type[F_TYPES]):
|
|
1143
|
-
"""
|
|
1690
|
+
"""
|
|
1691
|
+
Return raw configuration list from the Meta schema for the given categories.
|
|
1692
|
+
|
|
1693
|
+
Falls back to the ``out`` schema when ``detail`` is requested but not defined.
|
|
1694
|
+
|
|
1695
|
+
Parameters
|
|
1696
|
+
----------
|
|
1697
|
+
s_type : S_TYPES
|
|
1698
|
+
Serializer type (``"create"`` | ``"update"`` | ``"read"`` | ``"detail"``).
|
|
1699
|
+
f_type : F_TYPES
|
|
1700
|
+
Field category (``"fields"`` | ``"optionals"`` | ``"customs"`` | ``"excludes"``).
|
|
1701
|
+
|
|
1702
|
+
Returns
|
|
1703
|
+
-------
|
|
1704
|
+
list
|
|
1705
|
+
Raw configuration list, or empty list if not configured.
|
|
1706
|
+
"""
|
|
1144
1707
|
schema_key = cls._SCHEMA_META_MAP.get(s_type)
|
|
1145
1708
|
if not schema_key:
|
|
1146
1709
|
return []
|
|
@@ -1195,13 +1758,13 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1195
1758
|
self.after_save(instance)
|
|
1196
1759
|
return instance
|
|
1197
1760
|
|
|
1198
|
-
async def create(self, payload: dict[str, Any]) -> models.Model:
|
|
1761
|
+
async def create(self, payload: dict[str, Any] | Schema) -> models.Model:
|
|
1199
1762
|
"""
|
|
1200
1763
|
Create a new model instance from the provided payload.
|
|
1201
1764
|
|
|
1202
1765
|
Parameters
|
|
1203
1766
|
----------
|
|
1204
|
-
payload : dict
|
|
1767
|
+
payload : dict | Schema
|
|
1205
1768
|
Input data.
|
|
1206
1769
|
|
|
1207
1770
|
Returns
|
|
@@ -1209,11 +1772,11 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1209
1772
|
models.Model
|
|
1210
1773
|
Created model instance.
|
|
1211
1774
|
"""
|
|
1212
|
-
instance: models.Model = self.model(**payload)
|
|
1775
|
+
instance: models.Model = self.model(**self._parse_payload(payload))
|
|
1213
1776
|
return await self.save(instance)
|
|
1214
1777
|
|
|
1215
1778
|
async def update(
|
|
1216
|
-
self, instance: models.Model, payload: dict[str, Any]
|
|
1779
|
+
self, instance: models.Model, payload: dict[str, Any] | Schema
|
|
1217
1780
|
) -> models.Model:
|
|
1218
1781
|
"""
|
|
1219
1782
|
Update an existing model instance with the provided payload.
|
|
@@ -1222,7 +1785,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1222
1785
|
----------
|
|
1223
1786
|
instance : models.Model
|
|
1224
1787
|
The model instance to update.
|
|
1225
|
-
payload : dict
|
|
1788
|
+
payload : dict | Schema
|
|
1226
1789
|
Input data.
|
|
1227
1790
|
|
|
1228
1791
|
Returns
|
|
@@ -1230,7 +1793,7 @@ class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
|
1230
1793
|
models.Model
|
|
1231
1794
|
Updated model instance.
|
|
1232
1795
|
"""
|
|
1233
|
-
for attr, value in payload.items():
|
|
1796
|
+
for attr, value in self._parse_payload(payload).items():
|
|
1234
1797
|
setattr(instance, attr, value)
|
|
1235
1798
|
return await self.save(instance)
|
|
1236
1799
|
|