django-ninja-aio-crud 2.17.0__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.
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/.github/workflows/docs.yml +4 -0
- django_ninja_aio_crud-2.18.0/CHANGELOG.md +272 -0
- django_ninja_aio_crud-2.18.0/CLAUDE.md +199 -0
- django_ninja_aio_crud-2.18.0/PKG-INFO +425 -0
- django_ninja_aio_crud-2.18.0/README.md +389 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/authentication.md +50 -21
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/models/model_serializer.md +68 -26
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/models/model_util.md +47 -22
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/models/serializers.md +75 -19
- django_ninja_aio_crud-2.18.0/docs/api/models/validators.md +344 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/pagination.md +63 -25
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/renderers/orjson_renderer.md +37 -3
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/views/api_view.md +31 -12
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/views/api_view_set.md +52 -29
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/views/decorators.md +55 -7
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/api/views/mixins.md +44 -3
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/auth.md +63 -24
- django_ninja_aio_crud-2.18.0/docs/contributing.md +80 -0
- django_ninja_aio_crud-2.18.0/docs/extra.css +539 -0
- django_ninja_aio_crud-2.18.0/docs/getting_started/installation.md +61 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/quick_start.md +61 -26
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/quick_start_serializer.md +33 -10
- django_ninja_aio_crud-2.18.0/docs/index.md +579 -0
- django_ninja_aio_crud-2.18.0/docs/release_notes.md +3 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/tutorial/authentication.md +83 -37
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/tutorial/crud.md +247 -197
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/tutorial/filtering.md +90 -39
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/tutorial/model.md +239 -184
- django_ninja_aio_crud-2.18.0/docs/tutorial/serializer.md +853 -0
- django_ninja_aio_crud-2.18.0/main.py +301 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/mkdocs.yml +13 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/models/serializers.py +557 -47
- django_ninja_aio_crud-2.18.0/overrides/main.html +10 -0
- django_ninja_aio_crud-2.18.0/overrides/partials/announce.html +4 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_app/models.py +44 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_app/serializers.py +48 -0
- django_ninja_aio_crud-2.18.0/tests/test_decorators.py +160 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_serializers.py +421 -2
- django_ninja_aio_crud-2.17.0/PKG-INFO +0 -379
- django_ninja_aio_crud-2.17.0/README.md +0 -343
- django_ninja_aio_crud-2.17.0/docs/contributing.md +0 -41
- django_ninja_aio_crud-2.17.0/docs/extra.css +0 -14
- django_ninja_aio_crud-2.17.0/docs/getting_started/installation.md +0 -10
- django_ninja_aio_crud-2.17.0/docs/index.md +0 -382
- django_ninja_aio_crud-2.17.0/docs/release_notes.md +0 -4
- django_ninja_aio_crud-2.17.0/main.py +0 -245
- django_ninja_aio_crud-2.17.0/tests/test_decorators.py +0 -84
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/helpers/api.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/helpers/query.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/models/utils.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/filters.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/schemas/helpers.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/views/api.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/helpers/test_many_to_many_api.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_app/views.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/test_settings.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.17.0 → django_ninja_aio_crud-2.18.0}/tests/views/test_views.py +0 -0
- {django_ninja_aio_crud-2.17.0 → 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
|