django-ninja-aio-crud 2.16.2__tar.gz → 2.18.0__tar.gz

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.

Files changed (119) hide show
  1. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/.github/workflows/docs.yml +4 -0
  2. django_ninja_aio_crud-2.18.0/CHANGELOG.md +272 -0
  3. django_ninja_aio_crud-2.18.0/CLAUDE.md +199 -0
  4. django_ninja_aio_crud-2.18.0/PKG-INFO +425 -0
  5. django_ninja_aio_crud-2.18.0/README.md +389 -0
  6. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/authentication.md +50 -21
  7. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/models/model_serializer.md +94 -35
  8. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/models/model_util.md +47 -22
  9. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/models/serializers.md +113 -20
  10. django_ninja_aio_crud-2.18.0/docs/api/models/validators.md +344 -0
  11. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/pagination.md +63 -25
  12. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/renderers/orjson_renderer.md +37 -3
  13. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/views/api_view.md +31 -12
  14. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/views/api_view_set.md +52 -29
  15. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/views/decorators.md +55 -7
  16. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/api/views/mixins.md +44 -3
  17. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/auth.md +63 -24
  18. django_ninja_aio_crud-2.18.0/docs/contributing.md +80 -0
  19. django_ninja_aio_crud-2.18.0/docs/extra.css +539 -0
  20. django_ninja_aio_crud-2.18.0/docs/getting_started/installation.md +61 -0
  21. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/quick_start.md +61 -26
  22. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/quick_start_serializer.md +33 -10
  23. django_ninja_aio_crud-2.18.0/docs/index.md +579 -0
  24. django_ninja_aio_crud-2.18.0/docs/release_notes.md +3 -0
  25. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/tutorial/authentication.md +83 -37
  26. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/tutorial/crud.md +247 -197
  27. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/tutorial/filtering.md +90 -39
  28. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/tutorial/model.md +239 -184
  29. django_ninja_aio_crud-2.18.0/docs/tutorial/serializer.md +853 -0
  30. django_ninja_aio_crud-2.18.0/main.py +301 -0
  31. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/mkdocs.yml +13 -0
  32. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/__init__.py +1 -1
  33. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/models/serializers.py +637 -74
  34. django_ninja_aio_crud-2.18.0/overrides/main.html +10 -0
  35. django_ninja_aio_crud-2.18.0/overrides/partials/announce.html +4 -0
  36. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_app/models.py +54 -0
  37. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_app/serializers.py +48 -0
  38. django_ninja_aio_crud-2.18.0/tests/test_decorators.py +160 -0
  39. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_serializers.py +650 -2
  40. django_ninja_aio_crud-2.16.2/PKG-INFO +0 -379
  41. django_ninja_aio_crud-2.16.2/README.md +0 -343
  42. django_ninja_aio_crud-2.16.2/docs/contributing.md +0 -41
  43. django_ninja_aio_crud-2.16.2/docs/extra.css +0 -14
  44. django_ninja_aio_crud-2.16.2/docs/getting_started/installation.md +0 -10
  45. django_ninja_aio_crud-2.16.2/docs/index.md +0 -382
  46. django_ninja_aio_crud-2.16.2/docs/release_notes.md +0 -4
  47. django_ninja_aio_crud-2.16.2/main.py +0 -245
  48. django_ninja_aio_crud-2.16.2/tests/test_decorators.py +0 -84
  49. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/.github/dependabot.yml +0 -0
  50. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/.github/workflows/coverage.yml +0 -0
  51. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/.github/workflows/publish.yml +0 -0
  52. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/.gitignore +0 -0
  53. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/.pre-commit-config.yaml +0 -0
  54. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/LICENSE +0 -0
  55. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/CNAME +0 -0
  56. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
  57. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
  58. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
  59. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
  60. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
  61. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
  62. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/images/bar-swagger.png +0 -0
  63. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/images/favicon.ico +0 -0
  64. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/images/foo-swagger.png +0 -0
  65. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/images/logo.png +0 -0
  66. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
  67. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/docs/requirements.txt +0 -0
  68. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/api.py +0 -0
  69. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/auth.py +0 -0
  70. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/decorators/__init__.py +0 -0
  71. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/decorators/operations.py +0 -0
  72. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/decorators/views.py +0 -0
  73. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/exceptions.py +0 -0
  74. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/factory/__init__.py +0 -0
  75. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/factory/operations.py +0 -0
  76. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/helpers/__init__.py +0 -0
  77. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/helpers/api.py +0 -0
  78. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/helpers/query.py +0 -0
  79. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/models/__init__.py +0 -0
  80. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/models/utils.py +0 -0
  81. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/parsers.py +0 -0
  82. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/renders.py +0 -0
  83. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/__init__.py +0 -0
  84. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/api.py +0 -0
  85. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/filters.py +0 -0
  86. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/generics.py +0 -0
  87. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/helpers.py +0 -0
  88. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/types.py +0 -0
  89. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/views/__init__.py +0 -0
  90. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/views/api.py +0 -0
  91. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/ninja_aio/views/mixins.py +0 -0
  92. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/pyproject.toml +0 -0
  93. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/requirements.dev.txt +0 -0
  94. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/run-local-coverage.sh +0 -0
  95. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/__init__.py +0 -0
  96. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/core/__init__.py +0 -0
  97. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/core/test_decorators.py +0 -0
  98. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/core/test_exceptions_api.py +0 -0
  99. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/core/test_renderer_parser.py +0 -0
  100. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/generics/__init__.py +0 -0
  101. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/generics/literals.py +0 -0
  102. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/generics/models.py +0 -0
  103. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/generics/request.py +0 -0
  104. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/generics/views.py +0 -0
  105. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/helpers/__init__.py +0 -0
  106. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/helpers/test_many_to_many_api.py +0 -0
  107. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/models/__init__.py +0 -0
  108. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/models/test_model_util.py +0 -0
  109. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/models/test_models_extra.py +0 -0
  110. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_app/__init__.py +0 -0
  111. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_app/schema.py +0 -0
  112. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_app/views.py +0 -0
  113. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_auth.py +0 -0
  114. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_exceptions.py +0 -0
  115. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_query_util.py +0 -0
  116. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/test_settings.py +0 -0
  117. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/views/__init__.py +0 -0
  118. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/views/test_views.py +0 -0
  119. {django_ninja_aio_crud-2.16.2 → django_ninja_aio_crud-2.18.0}/tests/views/test_viewset.py +0 -0
@@ -29,6 +29,8 @@ on:
29
29
  - "2.14"
30
30
  - "2.15"
31
31
  - "2.16"
32
+ - "2.17"
33
+ - "2.18"
32
34
  make_latest:
33
35
  description: 'Set as "latest" and default?'
34
36
  type: boolean
@@ -61,6 +63,8 @@ on:
61
63
  - "2.14"
62
64
  - "2.15"
63
65
  - "2.16"
66
+ - "2.17"
67
+ - "2.18"
64
68
  delete_confirm:
65
69
  description: 'Confirm deletion of the selected version'
66
70
  type: boolean
@@ -0,0 +1,272 @@
1
+ # 📋 Release Notes
2
+
3
+ ## 🏷️ [v2.18.0] - 2026-02-01
4
+
5
+ ---
6
+
7
+ ### ✨ New Features
8
+
9
+ #### 🛡️ Validators on Serializers
10
+
11
+ > `ninja_aio/models/serializers.py`
12
+
13
+ Pydantic `@field_validator` and `@model_validator` can now be declared directly on serializer configuration classes. The framework automatically collects `PydanticDescriptorProxy` instances and creates a subclass of the generated schema with the validators attached.
14
+
15
+ **Supported on both serializer patterns:**
16
+
17
+ | Pattern | Where to declare validators |
18
+ |---|---|
19
+ | `ModelSerializer` | Inner classes: `CreateSerializer`, `ReadSerializer`, `UpdateSerializer`, `DetailSerializer` |
20
+ | `Serializer` (Meta-driven) | Dedicated inner classes: `CreateValidators`, `ReadValidators`, `UpdateValidators`, `DetailValidators` |
21
+
22
+ 🔀 Different validation rules can be applied per operation (e.g., stricter rules on create, lenient on update).
23
+
24
+ **ModelSerializer example:**
25
+
26
+ ```python
27
+ from django.db import models
28
+ from pydantic import field_validator, model_validator
29
+ from ninja_aio.models import ModelSerializer
30
+
31
+ class Book(ModelSerializer):
32
+ title = models.CharField(max_length=120)
33
+ description = models.TextField(blank=True)
34
+
35
+ class CreateSerializer:
36
+ fields = ["title", "description"]
37
+
38
+ @field_validator("title")
39
+ @classmethod
40
+ def validate_title_min_length(cls, v):
41
+ if len(v) < 3:
42
+ raise ValueError("Title must be at least 3 characters")
43
+ return v
44
+
45
+ class UpdateSerializer:
46
+ optionals = [("title", str), ("description", str)]
47
+
48
+ @field_validator("title")
49
+ @classmethod
50
+ def validate_title_not_empty(cls, v):
51
+ if v is not None and len(v.strip()) == 0:
52
+ raise ValueError("Title cannot be blank")
53
+ return v
54
+ ```
55
+
56
+ **Serializer (Meta-driven) example:**
57
+
58
+ ```python
59
+ from pydantic import field_validator
60
+ from ninja_aio.models import serializers
61
+
62
+ class BookSerializer(serializers.Serializer):
63
+ class Meta:
64
+ model = Book
65
+ schema_in = serializers.SchemaModelConfig(fields=["title", "description"])
66
+ schema_out = serializers.SchemaModelConfig(fields=["id", "title", "description"])
67
+
68
+ class CreateValidators:
69
+ @field_validator("title")
70
+ @classmethod
71
+ def validate_title_min_length(cls, v):
72
+ if len(v) < 3:
73
+ raise ValueError("Title must be at least 3 characters")
74
+ return v
75
+ ```
76
+
77
+ ---
78
+
79
+ #### 🧩 New Core Methods on `BaseSerializer`
80
+
81
+ > `ninja_aio/models/serializers.py`
82
+
83
+ | Method | Description |
84
+ |---|---|
85
+ | `_collect_validators(source_class)` | 🔍 Scans a class for `PydanticDescriptorProxy` instances created by `@field_validator` / `@model_validator` decorators. Returns a dict mapping attribute names to validator proxies. |
86
+ | `_apply_validators(schema, validators)` | 🔗 Creates a subclass of the generated schema with validators attached. Pydantic discovers validators during class creation. |
87
+ | `_get_validators(schema_type)` | 🗺️ Abstract method for subclasses to map schema types (`In`, `Patch`, `Out`, `Detail`, `Related`) to their validator source classes. |
88
+
89
+ ---
90
+
91
+ #### 🆕 New `_parse_payload()` Method on Serializer
92
+
93
+ > `ninja_aio/models/serializers.py`
94
+
95
+ `Serializer._parse_payload(payload)` accepts both `dict` and `Schema` instances, automatically calling `model_dump()` on Schema inputs. This enables passing validated Pydantic schemas directly to `create()` and `update()`.
96
+
97
+ ---
98
+
99
+ #### 📖 New Tutorial: "Define Your Serializer"
100
+
101
+ > `docs/tutorial/serializer.md`
102
+
103
+ Comprehensive tutorial page for the Meta-driven `Serializer` approach as an alternative to Step 1 (ModelSerializer). Covers:
104
+
105
+ - 📐 Schema definition with `SchemaModelConfig`
106
+ - 🔗 Relationships via `relations_serializers`
107
+ - ⚙️ Custom and computed fields
108
+ - 🚀 Query optimizations with `QuerySet`
109
+ - 🔄 Lifecycle hooks
110
+ - 🔌 Connecting to `APIViewSet`
111
+
112
+ ---
113
+
114
+ #### 📚 New Validators Documentation Page
115
+
116
+ > `docs/api/models/validators.md`
117
+
118
+ Full dedicated documentation page covering:
119
+
120
+ - 🏗️ `ModelSerializer` and `Serializer` approaches
121
+ - ✅ Supported validator types and modes
122
+ - 🔀 Different validators per operation
123
+ - ⚙️ Internal mechanics
124
+ - ⚠️ Error handling (422 responses)
125
+ - 💡 Complete examples
126
+
127
+ ---
128
+
129
+ ### 🔧 Improvements
130
+
131
+ #### ⚡ Schema Generation Now Applies Validators
132
+
133
+ > `ninja_aio/models/serializers.py`
134
+
135
+ `_generate_model_schema()` now calls `_get_validators()` for the requested schema type and `_apply_validators()` on the resulting schema. Applied consistently across all schema types: `Out`, `Detail`, `Related`, `In`, and `Patch`.
136
+
137
+ ---
138
+
139
+ #### 📦 `create()` and `update()` Accept Schema Objects
140
+
141
+ > `ninja_aio/models/serializers.py`
142
+
143
+ `Serializer.create()` and `Serializer.update()` payload parameter type changed from `dict[str, Any]` to `dict[str, Any] | Schema`, using the new `_parse_payload()` method to handle both inputs transparently.
144
+
145
+ ---
146
+
147
+ #### 🏷️ Updated Type Annotations
148
+
149
+ > `ninja_aio/models/serializers.py`
150
+
151
+ - `ModelSerializer` inner classes now accept `tuple[str, Any]` in addition to `tuple[str, Any, Any]` for both `fields` and `customs` attributes.
152
+ - `SchemaModelConfig.customs` type annotation updated to `List[tuple[str, Any, Any] | tuple[str, Any]]`.
153
+
154
+ ---
155
+
156
+ #### 📝 Comprehensive Docstrings
157
+
158
+ > `ninja_aio/models/serializers.py`
159
+
160
+ Added detailed NumPy-style docstrings with `Parameters`, `Returns`, and `Raises` sections to virtually all methods in `BaseSerializer`, `ModelSerializer`, and `Serializer` (30+ methods).
161
+
162
+ ---
163
+
164
+ ### 🎨 Documentation Overhaul
165
+
166
+ #### 💎 Complete Site Redesign
167
+
168
+ All documentation pages updated with Material for MkDocs icons, grid cards, section dividers, and modern formatting:
169
+
170
+ - 🏠 **Landing page** — Hero section, CTA buttons, grid cards for features, tabbed code comparison, Schema Validators section, key concepts in card layout
171
+ - 📖 **Tutorial pages** — Hero banners with step indicators, learning objectives, prerequisites boxes, summary checklists
172
+ - 📑 **API reference pages** — Material icons on headings, section dividers, "See Also" replaced with grid cards
173
+ - 🎨 **Custom CSS** — New styles for hero sections, card grids, tutorial components, and release notes UI
174
+ - ⚙️ **MkDocs theme** — Added template overrides, announcement bar, emoji extension, `md_in_html`, new navigation features
175
+
176
+ ---
177
+
178
+ #### 🖼️ README Redesign
179
+
180
+ > `README.md`
181
+
182
+ - 🎯 Centered HTML layout: logo, title, subtitle, and badge row
183
+ - 📊 Features bullet list replaced with formatted table
184
+ - 🅰️🅱️ Quick Start restructured into "Option A" and "Option B" sections
185
+ - 🛡️ New "Schema Validators" section with examples and mapping table
186
+ - 🔄 "Lifecycle Hooks" bullet list replaced with table
187
+ - 🧹 Redundant sections removed, "Buy me a coffee" uses styled badge
188
+
189
+ ---
190
+
191
+ #### 🗂️ MkDocs Navigation Updates
192
+
193
+ > `mkdocs.yml`
194
+
195
+ - ➕ Added `tutorial/serializer.md` — "Alternative: Define Your Serializer"
196
+ - ➕ Added `api/models/validators.md` — "Validators"
197
+ - ➕ Added `api/renderers/orjson_renderer.md` — "Renderers"
198
+
199
+ ---
200
+
201
+ #### 🔄 Release Notes Page Redesign
202
+
203
+ > `main.py`
204
+
205
+ Replaced table-based release notes layout with an interactive dropdown version selector and card-based display with human-readable date formatting.
206
+
207
+ ---
208
+
209
+ ### 🧪 Tests
210
+
211
+ > `tests/test_serializers.py`, `tests/test_app/models.py`, `tests/test_app/serializers.py`
212
+
213
+ #### `ValidatorsOnSerializersTestCase` — 14 tests
214
+
215
+ **🏗️ ModelSerializer validators:**
216
+
217
+ | Test | Verifies |
218
+ |---|---|
219
+ | `test_model_serializer_field_validator_rejects_invalid` | ❌ `@field_validator` on `CreateSerializer` rejects input below min length |
220
+ | `test_model_serializer_field_validator_accepts_valid` | ✅ `@field_validator` on `CreateSerializer` accepts valid input |
221
+ | `test_model_serializer_update_validator_rejects_blank` | ❌ `@field_validator` on `UpdateSerializer` rejects blank name |
222
+ | `test_model_serializer_update_validator_accepts_valid` | ✅ `@field_validator` on `UpdateSerializer` accepts valid input |
223
+ | `test_model_serializer_read_model_validator` | ✅ `@model_validator` on `ReadSerializer` is applied to output schema |
224
+ | `test_model_serializer_no_validators_returns_plain_schema` | ✅ Serializers without validators still work normally |
225
+
226
+ **🗺️ Meta-driven Serializer validators:**
227
+
228
+ | Test | Verifies |
229
+ |---|---|
230
+ | `test_meta_serializer_field_validator_rejects_invalid` | ❌ `CreateValidators` `@field_validator` rejects invalid input |
231
+ | `test_meta_serializer_field_validator_accepts_valid` | ✅ `CreateValidators` `@field_validator` accepts valid input |
232
+ | `test_meta_serializer_update_validator_rejects_blank` | ❌ `UpdateValidators` `@field_validator` rejects blank name |
233
+ | `test_meta_serializer_read_model_validator` | ✅ `ReadValidators` `@model_validator` is applied to output schema |
234
+
235
+ **🔧 Utility method tests:**
236
+
237
+ | Test | Verifies |
238
+ |---|---|
239
+ | `test_collect_validators_returns_empty_for_none` | 🔍 `_collect_validators(None)` returns `{}` |
240
+ | `test_collect_validators_returns_empty_for_no_validators` | 🔍 `_collect_validators` returns `{}` for class without validators |
241
+ | `test_apply_validators_returns_none_for_none_schema` | 🔍 `_apply_validators(None, ...)` returns `None` |
242
+ | `test_apply_validators_returns_schema_for_empty_validators` | 🔍 `_apply_validators(schema, {})` returns original schema |
243
+
244
+ **📦 New test fixtures:**
245
+
246
+ | File | Addition |
247
+ |---|---|
248
+ | `tests/test_app/models.py` | `TestModelWithValidators` — model with validators on `CreateSerializer`, `UpdateSerializer`, `ReadSerializer` |
249
+ | `tests/test_app/serializers.py` | `TestModelWithValidatorsMetaSerializer` — serializer with `CreateValidators`, `UpdateValidators`, `ReadValidators` |
250
+
251
+ ---
252
+
253
+ ### 📁 New Files
254
+
255
+ | File | Description |
256
+ |---|---|
257
+ | `CLAUDE.md` | 📋 Project instructions: overview, structure, tests, code style, architecture notes |
258
+ | `CHANGELOG.md` | 📝 Latest release notes |
259
+
260
+ ---
261
+
262
+ ### 🎯 Summary
263
+
264
+ This release introduces **Pydantic validators on serializers**, allowing `@field_validator` and `@model_validator` to be declared directly on serializer configuration classes. The framework automatically collects and applies these validators to generated schemas. Additionally, the entire documentation site has been redesigned with Material for MkDocs components.
265
+
266
+ **🌟 Key benefits:**
267
+
268
+ - 🛡️ **Schema-level validation** — Enforce input constraints beyond Django model fields, running before data touches the database
269
+ - 🔀 **Per-operation validation** — Apply different validation rules per CRUD operation (create vs. update vs. read)
270
+ - 🏗️ **Both serializer patterns** — Works with `ModelSerializer` (inner classes) and `Serializer` (`{Type}Validators` classes)
271
+ - ♻️ **Backwards compatible** — Existing serializers without validators continue to work unchanged
272
+ - 🎨 **Documentation redesign** — Modern Material for MkDocs layout with grid cards, hero sections, and interactive release notes
@@ -0,0 +1,199 @@
1
+ # CLAUDE.md
2
+
3
+ ## Project Overview
4
+
5
+ **django-ninja-aio-crud** is an async CRUD framework built on Django Ninja. It provides automated REST API generation with async support, authentication, filtering, pagination, and serialization.
6
+
7
+ - **Framework:** Django Ninja (>=1.3.0, <1.6)
8
+ - **Python:** 3.10 - 3.14
9
+ - **Build system:** Flit
10
+ - **Linter/Formatter:** Ruff
11
+
12
+ ## Project Structure
13
+
14
+ - `ninja_aio/` - Main source package
15
+ - `api.py` - NinjaAIO class extending NinjaAPI
16
+ - `auth.py` - JWT authentication (AsyncJwtBearer)
17
+ - `views/` - APIView, APIViewSet, mixins
18
+ - `models/` - ModelSerializer, Meta-driven Serializer, model utilities
19
+ - `schemas/` - Pydantic schema generation and filters
20
+ - `decorators/` - View and operation decorators
21
+ - `factory/` - Operation factory
22
+ - `helpers/` - Query and API helpers
23
+ - `tests/` - Test suite
24
+ - `test_settings.py` - Django settings for tests (SQLite in-memory)
25
+ - `test_app/` - Test Django app with models, serializers, views
26
+ - Subdirectories: `core/`, `generics/`, `helpers/`, `views/`, `models/`
27
+ - `docs/` - MkDocs documentation
28
+
29
+ ## Running Tests
30
+
31
+ Activate the virtual environment and run the coverage script:
32
+
33
+ ```sh
34
+ . ./.venv/bin/activate && ./run-local-coverage.sh
35
+ ```
36
+
37
+ This runs `coverage run -m django test --settings=tests.test_settings` and generates an HTML report in `.html/`.
38
+
39
+ To run tests without coverage:
40
+
41
+ ```sh
42
+ . ./.venv/bin/activate && python -m django test --settings=tests.test_settings
43
+ ```
44
+
45
+ ## Code Style
46
+
47
+ - Ruff is used for linting and formatting (configured via pre-commit hooks)
48
+ - Pre-commit hooks also check AST validity, merge conflicts, TOML/YAML syntax, trailing whitespace, and EOF newlines
49
+
50
+ ## Architecture Notes
51
+
52
+ ### Serializer System
53
+
54
+ Two serializer patterns exist:
55
+
56
+ - **ModelSerializer** (`ninja_aio/models/serializers.py`): Model-bound, config via inner classes (`CreateSerializer`, `ReadSerializer`, `UpdateSerializer`, `DetailSerializer`). Metaclass: `ModelSerializerMeta`.
57
+ - **Serializer** (`ninja_aio/models/serializers.py`): Meta-driven for arbitrary Django models, config via `Meta` class with `SchemaModelConfig` objects. Metaclass: `SerializerMeta`.
58
+
59
+ Both inherit from `BaseSerializer` which provides shared utilities and the core schema factory (`_generate_model_schema`).
60
+
61
+ ### Schema Generation Pipeline
62
+
63
+ 1. Field configuration gathered from inner classes / Meta
64
+ 2. `ninja.orm.create_schema()` called (wraps `pydantic.create_model`) — accepts `base_class`, `fields`, `custom_fields`, `exclude`, `depth`
65
+ 3. Validators collected and applied as a subclass of the generated schema (see Validators section)
66
+ 4. Resulting Pydantic model class used for input validation and output serialization
67
+
68
+ ### Validators on Serializers
69
+
70
+ Pydantic `@field_validator` and `@model_validator` can be declared on serializer config classes. The framework collects `PydanticDescriptorProxy` instances and creates a subclass of the generated schema with those validators attached.
71
+
72
+ **ModelSerializer** — validators on inner serializer classes:
73
+ ```python
74
+ class MyModel(ModelSerializer):
75
+ class CreateSerializer:
76
+ fields = ["name", "email"]
77
+
78
+ @field_validator("name")
79
+ @classmethod
80
+ def validate_name(cls, v):
81
+ if len(v) < 3:
82
+ raise ValueError("Name too short")
83
+ return v
84
+ ```
85
+
86
+ **Serializer** — validators on `{Type}Validators` inner classes:
87
+ ```python
88
+ class MySerializer(Serializer):
89
+ class Meta:
90
+ model = MyModel
91
+ schema_in = SchemaModelConfig(fields=["name", "email"])
92
+
93
+ class CreateValidators:
94
+ @field_validator("name")
95
+ @classmethod
96
+ def validate_name(cls, v):
97
+ return v.strip()
98
+ ```
99
+
100
+ Key methods in `BaseSerializer`:
101
+ - `_collect_validators(source_class)` — scans class for `PydanticDescriptorProxy` instances
102
+ - `_apply_validators(schema, validators)` — creates subclass with validators attached
103
+ - `_get_validators(schema_type)` — maps schema type to validator source class (overridden by each serializer)
104
+
105
+ ## Writing Release Notes / Changelogs
106
+
107
+ When asked to write a changelog or release notes, generate a diff first and then produce a comprehensive `CHANGELOG.md` entry following the structure below. The changelog is kept in `CHANGELOG.md` at the project root.
108
+
109
+ ### How to generate the diff
110
+
111
+ ```sh
112
+ # Diff between the current branch and main (for a feature branch)
113
+ git diff main...HEAD > diff.txt
114
+
115
+ # Or diff between two tags
116
+ git diff v2.17.0..v2.18.0 > diff.txt
117
+
118
+ # Or diff of all uncommitted changes (staged + unstaged)
119
+ git diff HEAD > diff.txt
120
+ ```
121
+
122
+ ### Changelog structure and style guide
123
+
124
+ Each release entry follows this format:
125
+
126
+ ```markdown
127
+ ## [vX.Y.Z] - YYYY-MM-DD
128
+
129
+ ---
130
+
131
+ ### New Features
132
+
133
+ #### Feature Name
134
+ > `path/to/file.py`
135
+
136
+ Description of the feature. Include code examples when the feature introduces new API surface.
137
+
138
+ ---
139
+
140
+ ### Improvements
141
+
142
+ #### Improvement Name
143
+ > `path/to/file.py`
144
+
145
+ Description of what changed and why.
146
+
147
+ ---
148
+
149
+ ### Documentation
150
+
151
+ Brief summary of documentation changes. Do NOT list every CSS class or inline style change. Keep `main.py`, `extra.css`, and `mkdocs.yml` changes to one-line summaries unless they introduce user-facing functionality.
152
+
153
+ ---
154
+
155
+ ### Tests
156
+
157
+ #### `TestClassName` — N tests
158
+
159
+ **Category:**
160
+
161
+ | Test | Verifies |
162
+ |---|---|
163
+ | `test_name` | What it verifies |
164
+
165
+ **New test fixtures:**
166
+
167
+ | File | Addition |
168
+ |---|---|
169
+ | `tests/test_app/models.py` | `ModelName` — brief description |
170
+
171
+ ---
172
+
173
+ ### Summary
174
+
175
+ Brief paragraph summarizing the release. Then:
176
+
177
+ **Key benefits:**
178
+ - Bullet points
179
+
180
+ ### All Files Changed
181
+
182
+ | File | Changes |
183
+ |---|---|
184
+ | `path/to/file` | Brief description |
185
+ ```
186
+
187
+ ### Key rules
188
+
189
+ - **Always overwrite `CHANGELOG.md`** — Every time a changelog is generated, overwrite the existing `CHANGELOG.md` file at the project root. Do not create a new file or append to it.
190
+ - **Use emojis** — Decorate section headers, sub-headers, bullet points, and table entries with contextual emojis (e.g., ✨ New Features, 🔧 Improvements, 🧪 Tests, ✅/❌ for pass/fail tests, 🎯 Summary)
191
+
192
+ - **Group by category**: New Features, Improvements, Documentation, Tests, Summary
193
+ - **Use `> path/to/file`** blockquotes to indicate which file a change belongs to
194
+ - **Include code examples** for new user-facing API features
195
+ - **Use tables** for listing tests, methods, file changes, and mappings
196
+ - **Keep docs/styling/config sections brief** — one-liner summaries for CSS, `main.py` template changes, and `mkdocs.yml` config. Do not enumerate individual CSS classes or JS functions
197
+ - **Always include "All Files Changed" table** at the bottom
198
+ - **Always include "Summary" section** with key benefits as bullet points
199
+ - **Separate sections with `---`** horizontal rules