django-ninja-aio-crud 2.16.1__py3-none-any.whl → 2.17.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: django-ninja-aio-crud
3
- Version: 2.16.1
3
+ Version: 2.17.0
4
4
  Summary: Django Ninja AIO CRUD - Rest Framework
5
5
  Author: Giuseppe Casillo
6
6
  Requires-Python: >=3.10, <3.15
@@ -1,4 +1,4 @@
1
- ninja_aio/__init__.py,sha256=AweoPuranPjCdlZhcv8GL_euzUirxFwM8SdGzLvsPMc,120
1
+ ninja_aio/__init__.py,sha256=2QH5WmYL3ouCf-JfuHrYIk6jk3nDnu1ClpC1bPe5XWw,120
2
2
  ninja_aio/api.py,sha256=tuC7vdvn7s1GkCnSFy9Kn1zv0glZfYptRQVvo8ZRtGQ,2429
3
3
  ninja_aio/auth.py,sha256=f4yk45fLi36Qctu0A0zgHTFedb9yk3ewq5rOMpoPYIE,9035
4
4
  ninja_aio/exceptions.py,sha256=w8QWmVlg88iJvBrNODSDBHSsy8nNpwngaCGWRnkXoPo,3899
@@ -14,7 +14,7 @@ ninja_aio/helpers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU
14
14
  ninja_aio/helpers/api.py,sha256=va_HvZVBFm1KxwQhH4u09U4F1JS5JrQuRpRmPTHJt7w,21326
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=FMGXWyuwOhTy1kODJ62G2j5us52yWhTQqqsaKy5VQeA,43191
17
+ ninja_aio/models/serializers.py,sha256=IJvBdn4i3nXjD5GSKvEN3SXBrcOAZCVzQ-yMkWft9II,45342
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=CpubwNXsZHtu8jddliyQybF1epwZ-GO60vHIuF5AR1Y,
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.16.1.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
28
- django_ninja_aio_crud-2.16.1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
29
- django_ninja_aio_crud-2.16.1.dist-info/METADATA,sha256=GGBBrtgiM_sGtd95ySROK-3VImety1OoCTnrggFzeb0,9964
30
- django_ninja_aio_crud-2.16.1.dist-info/RECORD,,
27
+ django_ninja_aio_crud-2.17.0.dist-info/licenses/LICENSE,sha256=yrDAYcm0gRp_Qyzo3GQa4BjYjWRkAhGC8QRva__RYq0,1073
28
+ django_ninja_aio_crud-2.17.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
29
+ django_ninja_aio_crud-2.17.0.dist-info/METADATA,sha256=rU_PLwW8kGegOPLJmCnC07HdNT111NqrqsxJZIv8Rj4,9964
30
+ django_ninja_aio_crud-2.17.0.dist-info/RECORD,,
ninja_aio/__init__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """Django Ninja AIO CRUD - Rest Framework"""
2
2
 
3
- __version__ = "2.16.1"
3
+ __version__ = "2.17.0"
4
4
 
5
5
  from .api import NinjaAIO
6
6
 
@@ -305,7 +305,9 @@ class BaseSerializer:
305
305
  """
306
306
  # Auto-resolve ModelSerializer with readable fields
307
307
  if isinstance(rel_model, ModelSerializerMeta):
308
- has_readable_fields = rel_model.get_fields("read") or rel_model.get_custom_fields("read")
308
+ has_readable_fields = rel_model.get_fields(
309
+ "read"
310
+ ) or rel_model.get_custom_fields("read")
309
311
  return rel_model.generate_related_s() if has_readable_fields else None
310
312
 
311
313
  # Resolve from explicit serializer mapping
@@ -341,7 +343,7 @@ class BaseSerializer:
341
343
  - (name, py_type) -> default Ellipsis (required)
342
344
  """
343
345
  raw_customs = cls._get_fields(s_type, "customs") or []
344
- normalized: list[tuple[str, type, Any]] = []
346
+ normalized: list[tuple[str, Any, Any]] = []
345
347
  for spec in raw_customs:
346
348
  if not isinstance(spec, tuple):
347
349
  raise ValueError(f"Custom field spec must be a tuple, got {type(spec)}")
@@ -373,8 +375,39 @@ class BaseSerializer:
373
375
 
374
376
  @classmethod
375
377
  def get_fields(cls, s_type: S_TYPES):
376
- """Return explicit declared fields for the serializer type."""
377
- return cls._get_fields(s_type, "fields")
378
+ """Return explicit declared field names for the serializer type (excludes inline customs)."""
379
+ fields = cls._get_fields(s_type, "fields")
380
+ # Filter out inline custom field tuples, return only string field names
381
+ return [f for f in fields if isinstance(f, str)]
382
+
383
+ @classmethod
384
+ def get_inline_customs(cls, s_type: S_TYPES) -> list[tuple[str, Any, Any]]:
385
+ """
386
+ Return inline custom field tuples declared directly in the fields list.
387
+
388
+ These are tuples in the format (name, type, default) or (name, type) mixed
389
+ with regular string field names in the fields list.
390
+
391
+ Returns
392
+ -------
393
+ list[tuple[str, Any, Any]]
394
+ Normalized list of (name, type, default) tuples.
395
+ """
396
+ fields = cls._get_fields(s_type, "fields")
397
+ inline_customs: list[tuple[str, Any, Any]] = []
398
+ for spec in fields:
399
+ if isinstance(spec, tuple):
400
+ match len(spec):
401
+ case 3:
402
+ inline_customs.append(spec)
403
+ case 2:
404
+ name, py_type = spec
405
+ inline_customs.append((name, py_type, ...))
406
+ case _:
407
+ raise ValueError(
408
+ f"Inline custom field tuple must have length 2 or 3 (name, type[, default]); got {len(spec)}"
409
+ )
410
+ return inline_customs
378
411
 
379
412
  @classmethod
380
413
  def is_custom(cls, field: str) -> bool:
@@ -430,10 +463,15 @@ class BaseSerializer:
430
463
  # Handle relations_as_id for reverse relations
431
464
  if field_name in relations_as_id:
432
465
  from ninja_aio.models.utils import ModelUtil
466
+
433
467
  pk_field_type = ModelUtil(rel_model).pk_field_type
434
468
  if many:
435
469
  # For many relations, use PkFromModel to extract PKs from model instances
436
- return (field_name, list[PkFromModel[pk_field_type]], Field(default_factory=list))
470
+ return (
471
+ field_name,
472
+ list[PkFromModel[pk_field_type]],
473
+ Field(default_factory=list),
474
+ )
437
475
  else:
438
476
  # For single reverse relations (ReverseOneToOne), extract pk
439
477
  return (field_name, PkFromModel[pk_field_type] | None, None)
@@ -472,6 +510,7 @@ class BaseSerializer:
472
510
  # Handle relations_as_id: serialize as the raw FK ID
473
511
  if field_name in relations_as_id:
474
512
  from ninja_aio.models.utils import ModelUtil
513
+
475
514
  pk_field_type = ModelUtil(rel_model).pk_field_type
476
515
  # Use PkFromModel to extract pk from the related instance during serialization
477
516
  return (field_name, PkFromModel[pk_field_type] | None, None)
@@ -614,11 +653,18 @@ class BaseSerializer:
614
653
  if forward:
615
654
  forward_rels.append(forward)
616
655
 
656
+ # Combine explicit customs, inline customs, and forward relation schemas
657
+ all_customs = (
658
+ cls.get_custom_fields(fields_type)
659
+ + cls.get_inline_customs(fields_type)
660
+ + forward_rels
661
+ )
662
+
617
663
  return (
618
664
  fields,
619
665
  reverse_rels,
620
666
  cls.get_excluded_fields(fields_type),
621
- cls.get_custom_fields(fields_type) + forward_rels,
667
+ all_customs,
622
668
  cls.get_optional_fields(fields_type),
623
669
  )
624
670
 
@@ -666,7 +712,11 @@ class BaseSerializer:
666
712
  s_type = "create" if schema_type == "In" else "update"
667
713
  fields = cls.get_fields(s_type)
668
714
  optionals = cls.get_optional_fields(s_type)
669
- customs = cls.get_custom_fields(s_type) + optionals
715
+ customs = (
716
+ cls.get_custom_fields(s_type)
717
+ + optionals
718
+ + cls.get_inline_customs(s_type)
719
+ )
670
720
  excludes = cls.get_excluded_fields(s_type)
671
721
 
672
722
  # If no explicit fields and no excludes specified
@@ -698,17 +748,19 @@ class BaseSerializer:
698
748
  def get_related_schema_data(cls):
699
749
  """
700
750
  Build field/custom lists for 'Related' schema, flattening non-relational fields.
751
+
752
+ Custom fields (both explicit and inline) are always included since they
753
+ are computed/synthetic and not relation descriptors.
701
754
  """
702
755
  fields = cls.get_fields("read")
703
- custom_f = {
704
- name: (value, default)
705
- for name, value, default in cls.get_custom_fields("read")
706
- }
707
- _related_fields = []
756
+ customs = cls.get_custom_fields("read") + cls.get_inline_customs("read")
708
757
  model = cls._get_model()
709
- for f in fields + list(custom_f.keys()):
710
- field_obj = getattr(model, f)
711
- if not isinstance(
758
+
759
+ # Filter out relation fields from model fields
760
+ non_relation_fields = []
761
+ for f in fields:
762
+ field_obj = getattr(model, f, None)
763
+ if field_obj is None or not isinstance(
712
764
  field_obj,
713
765
  (
714
766
  ManyToManyDescriptor,
@@ -718,14 +770,13 @@ class BaseSerializer:
718
770
  ForwardOneToOneDescriptor,
719
771
  ),
720
772
  ):
721
- _related_fields.append(f)
722
- if not _related_fields:
773
+ non_relation_fields.append(f)
774
+
775
+ # No fields or customs means nothing to include
776
+ if not non_relation_fields and not customs:
723
777
  return None, None
724
- custom_related_fields = [
725
- (f, *custom_f[f]) for f in _related_fields if f in custom_f
726
- ]
727
- related_fields = [f for f in _related_fields if f not in custom_f]
728
- return related_fields, custom_related_fields
778
+
779
+ return non_relation_fields, customs
729
780
 
730
781
  @classmethod
731
782
  def generate_read_s(cls, depth: int = 1) -> Schema:
@@ -810,9 +861,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
810
861
  Disallowed model fields on create (e.g., id, timestamps).
811
862
  """
812
863
 
813
- fields: list[str] = []
814
- customs: list[tuple[str, type, Any]] = []
815
- optionals: list[tuple[str, type]] = []
864
+ fields: list[str | tuple[str, Any, Any]] = []
865
+ customs: list[tuple[str, Any, Any]] = []
866
+ optionals: list[tuple[str, Any]] = []
816
867
  excludes: list[str] = []
817
868
 
818
869
  class ReadSerializer:
@@ -832,9 +883,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
832
883
  Relation fields to serialize as IDs instead of nested objects.
833
884
  """
834
885
 
835
- fields: list[str] = []
836
- customs: list[tuple[str, type, Any]] = []
837
- optionals: list[tuple[str, type]] = []
886
+ fields: list[str | tuple[str, Any, Any]] = []
887
+ customs: list[tuple[str, Any, Any]] = []
888
+ optionals: list[tuple[str, Any]] = []
838
889
  excludes: list[str] = []
839
890
  relations_as_id: list[str] = []
840
891
 
@@ -853,9 +904,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
853
904
  Optional output fields.
854
905
  """
855
906
 
856
- fields: list[str] = []
857
- customs: list[tuple[str, type, Any]] = []
858
- optionals: list[tuple[str, type]] = []
907
+ fields: list[str | tuple[str, Any, Any]] = []
908
+ customs: list[tuple[str, Any, Any]] = []
909
+ optionals: list[tuple[str, Any]] = []
859
910
  excludes: list[str] = []
860
911
 
861
912
  class UpdateSerializer:
@@ -873,9 +924,9 @@ class ModelSerializer(models.Model, BaseSerializer, metaclass=ModelSerializerMet
873
924
  Immutable / blocked fields.
874
925
  """
875
926
 
876
- fields: list[str] = []
877
- customs: list[tuple[str, type, Any]] = []
878
- optionals: list[tuple[str, type]] = []
927
+ fields: list[str | tuple[str, Any, Any]] = []
928
+ customs: list[tuple[str, Any, Any]] = []
929
+ optionals: list[tuple[str, Any]] = []
879
930
  excludes: list[str] = []
880
931
 
881
932
  # Serializer type to configuration class mapping
@@ -1044,20 +1095,22 @@ class SchemaModelConfig(Schema):
1044
1095
  Configuration container for declarative schema definitions.
1045
1096
  Attributes
1046
1097
  ----------
1047
- fields : Optional[List[str]]
1048
- Explicit model fields to include.
1049
- optionals : Optional[List[tuple[str, type]]]
1050
- Optional model fields.
1098
+ fields : Optional[List[str | tuple]]
1099
+ Explicit model fields to include. Can also contain inline custom field tuples:
1100
+ - 2-tuple: (name, type) - required field
1101
+ - 3-tuple: (name, type, default) - optional field with default
1102
+ optionals : Optional[List[tuple[str, Any]]]
1103
+ Optional model fields. Type can be any valid type annotation including Union.
1051
1104
  exclude : Optional[List[str]]
1052
1105
  Model fields to exclude.
1053
- customs : Optional[List[tuple[str, type, Any]]]
1054
- Custom / synthetic fields.
1106
+ customs : Optional[List[tuple[str, Any, Any]]]
1107
+ Custom / synthetic fields. Type can be any valid type annotation including Union.
1055
1108
  """
1056
1109
 
1057
- fields: Optional[List[str]] = None
1058
- optionals: Optional[List[tuple[str, type]]] = None
1110
+ fields: Optional[List[str | tuple[str, Any, Any] | tuple[str, Any]]] = None
1111
+ optionals: Optional[List[tuple[str, Any]]] = None
1059
1112
  exclude: Optional[List[str]] = None
1060
- customs: Optional[List[tuple[str, type, Any]]] = None
1113
+ customs: Optional[List[tuple[str, Any, Any]]] = None
1061
1114
 
1062
1115
 
1063
1116
  class Serializer(BaseSerializer, metaclass=SerializerMeta):