django-ninja-aio-crud 2.16.1__tar.gz → 2.17.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.16.1 → django_ninja_aio_crud-2.17.0}/PKG-INFO +1 -1
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/models/model_serializer.md +26 -9
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/models/serializers.md +38 -1
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/models/serializers.py +96 -43
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_app/models.py +10 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_serializers.py +229 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.github/workflows/docs.yml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/README.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/models/model_util.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/views/api_view.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/views/api_view_set.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/views/decorators.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/views/mixins.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/extra.css +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/quick_start_serializer.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/index.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/main.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/mkdocs.yml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/helpers/api.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/helpers/query.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/models/utils.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/schemas/filters.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/schemas/helpers.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/views/api.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/helpers/test_many_to_many_api.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_app/serializers.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_app/views.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/test_settings.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/views/test_views.py +0 -0
- {django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/views/test_viewset.py +0 -0
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/models/model_serializer.md
RENAMED
|
@@ -49,7 +49,7 @@ Describes how to build a create (input) schema for a model.
|
|
|
49
49
|
|
|
50
50
|
| Attribute | Type | Description |
|
|
51
51
|
| ----------- | ------------------------ | --------------------------------------------------------------------------------------------------------------------------------- |
|
|
52
|
-
| `fields` | `list[str]`
|
|
52
|
+
| `fields` | `list[str \| tuple]` | REQUIRED model field names for creation. Can also include inline custom tuples (see below) |
|
|
53
53
|
| `optionals` | `list[tuple[str, type]]` | Optional model fields: `(field_name, python_type)` |
|
|
54
54
|
| `customs` | `list[tuple]` | Synthetic inputs. Tuple forms: `(name, type)` = required (no default); `(name, type, default)` = optional (literal or callable) |
|
|
55
55
|
| `excludes` | `list[str]` | Field names rejected on create |
|
|
@@ -75,6 +75,23 @@ class User(ModelSerializer):
|
|
|
75
75
|
excludes = ["id", "created_at"]
|
|
76
76
|
```
|
|
77
77
|
|
|
78
|
+
**Inline Custom Fields:**
|
|
79
|
+
|
|
80
|
+
You can also define custom fields directly in the `fields` list as tuples:
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
class User(ModelSerializer):
|
|
84
|
+
class CreateSerializer:
|
|
85
|
+
fields = [
|
|
86
|
+
"username",
|
|
87
|
+
"email",
|
|
88
|
+
("password_confirm", str), # 2-tuple: required
|
|
89
|
+
("send_welcome", bool, True), # 3-tuple: optional with default
|
|
90
|
+
]
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
This is equivalent to using the separate `customs` list but keeps field definitions together.
|
|
94
|
+
|
|
78
95
|
**Resolution Order for `customs`:**
|
|
79
96
|
|
|
80
97
|
1. Payload value (if provided)
|
|
@@ -100,12 +117,12 @@ Describes how to build a read (output) schema for a model.
|
|
|
100
117
|
|
|
101
118
|
**Attributes**
|
|
102
119
|
|
|
103
|
-
| Attribute | Type
|
|
104
|
-
|
|
105
|
-
| `fields` | `list[str]`
|
|
106
|
-
| `excludes` | `list[str]`
|
|
107
|
-
| `customs` | `list[tuple]`
|
|
108
|
-
| `relations_as_id`| `list[str]`
|
|
120
|
+
| Attribute | Type | Description |
|
|
121
|
+
|------------------|----------------------|-------------|
|
|
122
|
+
| `fields` | `list[str \| tuple]` | **REQUIRED.** Model fields / related names explicitly included in the read (output) schema. Can also include inline custom tuples. |
|
|
123
|
+
| `excludes` | `list[str]` | Fields / related names to always omit (takes precedence over `fields` and `optionals`). Use for sensitive or noisy data (e.g., passwords, internal flags). |
|
|
124
|
+
| `customs` | `list[tuple]` | Computed / synthetic output values. Tuple formats:<br>• `(name, type)` = required resolvable attribute (object attribute or property). Serialization error if not resolvable.<br>• `(name, type, default)` = optional; default may be a callable (`lambda obj: ...`) or a literal value. |
|
|
125
|
+
| `relations_as_id`| `list[str]` | Relation fields to serialize as IDs instead of nested objects. Works with forward FK, forward O2O, reverse FK, reverse O2O, and M2M relations. |
|
|
109
126
|
|
|
110
127
|
**Example:**
|
|
111
128
|
|
|
@@ -163,7 +180,7 @@ This allows partial overrides: define only `DetailSerializer.fields` while inher
|
|
|
163
180
|
|
|
164
181
|
| Attribute | Type | Description |
|
|
165
182
|
| ----------- | ------------------------ | ----------------------------------------------------------------------------- |
|
|
166
|
-
| `fields` | `list[str]`
|
|
183
|
+
| `fields` | `list[str \| tuple]` | Model fields to include in detail view. Can include inline custom tuples. Falls back to ReadSerializer.fields if empty |
|
|
167
184
|
| `excludes` | `list[str]` | Fields to exclude from detail view (falls back to ReadSerializer.excludes if empty) |
|
|
168
185
|
| `customs` | `list[tuple]` | Computed fields: `(name, type)` required; `(name, type, default)` optional (falls back to ReadSerializer.customs if empty) |
|
|
169
186
|
| `optionals` | `list[tuple[str, type]]` | Optional output fields (falls back to ReadSerializer.optionals if empty) |
|
|
@@ -240,7 +257,7 @@ Describes how to build an update (partial/full) input schema.
|
|
|
240
257
|
|
|
241
258
|
| Attribute | Type | Description |
|
|
242
259
|
| ----------- | ------------------------ | ----------------------------------------------------------------------------- |
|
|
243
|
-
| `fields` | `list[str]`
|
|
260
|
+
| `fields` | `list[str \| tuple]` | REQUIRED fields for update (rarely used). Can include inline custom tuples |
|
|
244
261
|
| `optionals` | `list[tuple[str, type]]` | Updatable optional fields (typical for PATCH) |
|
|
245
262
|
| `customs` | `list[tuple]` | Instruction fields: `(name, type)` required; `(name, type, default)` optional |
|
|
246
263
|
| `excludes` | `list[str]` | Immutable fields that cannot be updated |
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/models/serializers.md
RENAMED
|
@@ -45,11 +45,48 @@ Define a Serializer subclass with a nested Meta:
|
|
|
45
45
|
|
|
46
46
|
SchemaModelConfig fields:
|
|
47
47
|
|
|
48
|
-
- **fields**: `list[str]` - Model field names to include
|
|
48
|
+
- **fields**: `list[str | tuple]` - Model field names to include. Can also contain inline custom field tuples (see below)
|
|
49
49
|
- **optionals**: `list[tuple[str, type]]` - Optional fields with their types
|
|
50
50
|
- **exclude**: `list[str]` - Fields to exclude from schema
|
|
51
51
|
- **customs**: `list[tuple[str, type, Any]]` - Custom/computed fields
|
|
52
52
|
|
|
53
|
+
### Inline Custom Fields
|
|
54
|
+
|
|
55
|
+
You can define custom fields directly in the `fields` list as tuples, providing a more concise syntax:
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
class ArticleSerializer(serializers.Serializer):
|
|
59
|
+
class Meta:
|
|
60
|
+
model = models.Article
|
|
61
|
+
schema_out = serializers.SchemaModelConfig(
|
|
62
|
+
# Mix regular fields with inline custom tuples
|
|
63
|
+
fields=[
|
|
64
|
+
"id",
|
|
65
|
+
"title",
|
|
66
|
+
("word_count", int, 0), # 3-tuple: (name, type, default)
|
|
67
|
+
("is_featured", bool), # 2-tuple: (name, type) - required
|
|
68
|
+
]
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Tuple formats:**
|
|
73
|
+
|
|
74
|
+
- **2-tuple**: `(name, type)` - Required field (equivalent to default `...`)
|
|
75
|
+
- **3-tuple**: `(name, type, default)` - Optional field with default value
|
|
76
|
+
|
|
77
|
+
This is equivalent to using the separate `customs` list but keeps field definitions together:
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
# These two are equivalent:
|
|
81
|
+
|
|
82
|
+
# Using inline customs
|
|
83
|
+
fields=["id", "title", ("extra", str, "default")]
|
|
84
|
+
|
|
85
|
+
# Using separate customs list
|
|
86
|
+
fields=["id", "title"]
|
|
87
|
+
customs=[("extra", str, "default")]
|
|
88
|
+
```
|
|
89
|
+
|
|
53
90
|
### Schema Generation
|
|
54
91
|
|
|
55
92
|
Generate schemas explicitly using these methods:
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/models/serializers.py
RENAMED
|
@@ -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(
|
|
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,
|
|
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
|
|
377
|
-
|
|
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 (
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
722
|
-
|
|
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
|
-
|
|
725
|
-
|
|
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,
|
|
815
|
-
optionals: list[tuple[str,
|
|
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,
|
|
837
|
-
optionals: list[tuple[str,
|
|
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,
|
|
858
|
-
optionals: list[tuple[str,
|
|
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,
|
|
878
|
-
optionals: list[tuple[str,
|
|
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
|
-
|
|
1050
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
1113
|
+
customs: Optional[List[tuple[str, Any, Any]]] = None
|
|
1061
1114
|
|
|
1062
1115
|
|
|
1063
1116
|
class Serializer(BaseSerializer, metaclass=SerializerMeta):
|
|
@@ -227,6 +227,16 @@ class TestModelSerializerWithBothSerializers(BaseTestModelSerializer):
|
|
|
227
227
|
# No customs defined - should NOT inherit from read
|
|
228
228
|
|
|
229
229
|
|
|
230
|
+
class TestModelSerializerInlineCustoms(BaseTestModelSerializer):
|
|
231
|
+
"""Model with inline custom fields defined directly in the fields list."""
|
|
232
|
+
|
|
233
|
+
class ReadSerializer:
|
|
234
|
+
fields = ["id", "name", ("inline_computed", str, "computed_value")]
|
|
235
|
+
|
|
236
|
+
class CreateSerializer:
|
|
237
|
+
fields = ["name", ("extra_create_input", str, "")]
|
|
238
|
+
|
|
239
|
+
|
|
230
240
|
# ==========================================================
|
|
231
241
|
# RELATIONS AS ID TEST MODELS
|
|
232
242
|
# ==========================================================
|
|
@@ -1506,3 +1506,232 @@ class RelationsAsIdStringPKIntegrationTestCase(TestCase):
|
|
|
1506
1506
|
self.assertIsInstance(result.articles_str[0], str)
|
|
1507
1507
|
self.assertIn(self.article.pk, result.articles_str)
|
|
1508
1508
|
self.assertEqual(result.articles_str[0], "article-001")
|
|
1509
|
+
|
|
1510
|
+
|
|
1511
|
+
@tag("serializers", "inline_customs")
|
|
1512
|
+
class InlineCustomsSerializerTestCase(TestCase):
|
|
1513
|
+
"""Test cases for inline custom fields defined directly in the fields list."""
|
|
1514
|
+
|
|
1515
|
+
def setUp(self):
|
|
1516
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
1517
|
+
|
|
1518
|
+
def test_serializer_read_schema_with_inline_customs_3_tuple(self):
|
|
1519
|
+
"""Test that inline customs (3-tuple) in schema_out fields work correctly."""
|
|
1520
|
+
|
|
1521
|
+
class InlineCustomsReadSerializer(serializers.Serializer):
|
|
1522
|
+
class Meta:
|
|
1523
|
+
model = TestModelForeignKey
|
|
1524
|
+
schema_out = serializers.SchemaModelConfig(
|
|
1525
|
+
fields=["id", "name", ("custom_read", str, "default_value")]
|
|
1526
|
+
)
|
|
1527
|
+
|
|
1528
|
+
schema = InlineCustomsReadSerializer.generate_read_s()
|
|
1529
|
+
self.assertIsNotNone(schema)
|
|
1530
|
+
self.assertIn("id", schema.model_fields)
|
|
1531
|
+
self.assertIn("name", schema.model_fields)
|
|
1532
|
+
self.assertIn("custom_read", schema.model_fields)
|
|
1533
|
+
|
|
1534
|
+
def test_serializer_read_schema_with_inline_customs_2_tuple(self):
|
|
1535
|
+
"""Test that inline customs (2-tuple, required) in schema_out fields work correctly."""
|
|
1536
|
+
|
|
1537
|
+
class InlineCustoms2TupleSerializer(serializers.Serializer):
|
|
1538
|
+
class Meta:
|
|
1539
|
+
model = TestModelForeignKey
|
|
1540
|
+
schema_out = serializers.SchemaModelConfig(
|
|
1541
|
+
fields=["id", "name", ("required_custom", int)]
|
|
1542
|
+
)
|
|
1543
|
+
|
|
1544
|
+
schema = InlineCustoms2TupleSerializer.generate_read_s()
|
|
1545
|
+
self.assertIsNotNone(schema)
|
|
1546
|
+
self.assertIn("id", schema.model_fields)
|
|
1547
|
+
self.assertIn("name", schema.model_fields)
|
|
1548
|
+
self.assertIn("required_custom", schema.model_fields)
|
|
1549
|
+
|
|
1550
|
+
def test_serializer_create_schema_with_inline_customs(self):
|
|
1551
|
+
"""Test that inline customs in schema_in fields work correctly."""
|
|
1552
|
+
|
|
1553
|
+
class InlineCustomsCreateSerializer(serializers.Serializer):
|
|
1554
|
+
class Meta:
|
|
1555
|
+
model = TestModelForeignKey
|
|
1556
|
+
schema_in = serializers.SchemaModelConfig(
|
|
1557
|
+
fields=["name", ("extra_input", str, "")]
|
|
1558
|
+
)
|
|
1559
|
+
schema_out = serializers.SchemaModelConfig(fields=["id", "name"])
|
|
1560
|
+
|
|
1561
|
+
schema = InlineCustomsCreateSerializer.generate_create_s()
|
|
1562
|
+
self.assertIsNotNone(schema)
|
|
1563
|
+
self.assertIn("name", schema.model_fields)
|
|
1564
|
+
self.assertIn("extra_input", schema.model_fields)
|
|
1565
|
+
# Should NOT have model fields that weren't explicitly listed
|
|
1566
|
+
self.assertNotIn("description", schema.model_fields)
|
|
1567
|
+
|
|
1568
|
+
def test_serializer_update_schema_with_inline_customs(self):
|
|
1569
|
+
"""Test that inline customs in schema_update fields work correctly."""
|
|
1570
|
+
|
|
1571
|
+
class InlineCustomsUpdateSerializer(serializers.Serializer):
|
|
1572
|
+
class Meta:
|
|
1573
|
+
model = TestModelForeignKey
|
|
1574
|
+
schema_update = serializers.SchemaModelConfig(
|
|
1575
|
+
fields=[("update_flag", bool, False)],
|
|
1576
|
+
optionals=[("name", str)],
|
|
1577
|
+
)
|
|
1578
|
+
schema_out = serializers.SchemaModelConfig(fields=["id", "name"])
|
|
1579
|
+
|
|
1580
|
+
schema = InlineCustomsUpdateSerializer.generate_update_s()
|
|
1581
|
+
self.assertIsNotNone(schema)
|
|
1582
|
+
self.assertIn("update_flag", schema.model_fields)
|
|
1583
|
+
self.assertIn("name", schema.model_fields)
|
|
1584
|
+
|
|
1585
|
+
def test_serializer_inline_customs_combined_with_explicit_customs(self):
|
|
1586
|
+
"""Test that inline customs and explicit customs can coexist."""
|
|
1587
|
+
|
|
1588
|
+
class CombinedCustomsSerializer(serializers.Serializer):
|
|
1589
|
+
class Meta:
|
|
1590
|
+
model = TestModelForeignKey
|
|
1591
|
+
schema_out = serializers.SchemaModelConfig(
|
|
1592
|
+
fields=["id", "name", ("inline_custom", str, "inline")],
|
|
1593
|
+
customs=[("explicit_custom", int, 0)],
|
|
1594
|
+
)
|
|
1595
|
+
|
|
1596
|
+
schema = CombinedCustomsSerializer.generate_read_s()
|
|
1597
|
+
self.assertIsNotNone(schema)
|
|
1598
|
+
self.assertIn("id", schema.model_fields)
|
|
1599
|
+
self.assertIn("name", schema.model_fields)
|
|
1600
|
+
self.assertIn("inline_custom", schema.model_fields)
|
|
1601
|
+
self.assertIn("explicit_custom", schema.model_fields)
|
|
1602
|
+
|
|
1603
|
+
def test_serializer_get_fields_excludes_inline_customs(self):
|
|
1604
|
+
"""Test that get_fields() returns only string field names."""
|
|
1605
|
+
|
|
1606
|
+
class FieldsOnlySerializer(serializers.Serializer):
|
|
1607
|
+
class Meta:
|
|
1608
|
+
model = TestModelForeignKey
|
|
1609
|
+
schema_out = serializers.SchemaModelConfig(
|
|
1610
|
+
fields=["id", "name", ("custom", str, "val")]
|
|
1611
|
+
)
|
|
1612
|
+
|
|
1613
|
+
fields = FieldsOnlySerializer.get_fields("read")
|
|
1614
|
+
self.assertEqual(fields, ["id", "name"])
|
|
1615
|
+
# Should not include tuples
|
|
1616
|
+
self.assertNotIn(("custom", str, "val"), fields)
|
|
1617
|
+
|
|
1618
|
+
def test_serializer_get_inline_customs_returns_only_tuples(self):
|
|
1619
|
+
"""Test that get_inline_customs() returns only inline custom tuples."""
|
|
1620
|
+
|
|
1621
|
+
class InlineCustomsOnlySerializer(serializers.Serializer):
|
|
1622
|
+
class Meta:
|
|
1623
|
+
model = TestModelForeignKey
|
|
1624
|
+
schema_out = serializers.SchemaModelConfig(
|
|
1625
|
+
fields=["id", "name", ("custom1", str, "val"), ("custom2", int)]
|
|
1626
|
+
)
|
|
1627
|
+
|
|
1628
|
+
inline_customs = InlineCustomsOnlySerializer.get_inline_customs("read")
|
|
1629
|
+
self.assertEqual(len(inline_customs), 2)
|
|
1630
|
+
# First is a 3-tuple
|
|
1631
|
+
self.assertEqual(inline_customs[0], ("custom1", str, "val"))
|
|
1632
|
+
# Second is normalized from 2-tuple to 3-tuple with Ellipsis
|
|
1633
|
+
self.assertEqual(inline_customs[1], ("custom2", int, ...))
|
|
1634
|
+
|
|
1635
|
+
def test_serializer_detail_schema_with_inline_customs(self):
|
|
1636
|
+
"""Test that inline customs work in schema_detail."""
|
|
1637
|
+
|
|
1638
|
+
class DetailInlineCustomsSerializer(serializers.Serializer):
|
|
1639
|
+
class Meta:
|
|
1640
|
+
model = TestModelForeignKey
|
|
1641
|
+
schema_out = serializers.SchemaModelConfig(fields=["id", "name"])
|
|
1642
|
+
schema_detail = serializers.SchemaModelConfig(
|
|
1643
|
+
fields=["id", "name", "description", ("detail_extra", str, "extra")]
|
|
1644
|
+
)
|
|
1645
|
+
|
|
1646
|
+
read_schema = DetailInlineCustomsSerializer.generate_read_s()
|
|
1647
|
+
detail_schema = DetailInlineCustomsSerializer.generate_detail_s()
|
|
1648
|
+
|
|
1649
|
+
# Read schema should NOT have detail_extra
|
|
1650
|
+
self.assertNotIn("detail_extra", read_schema.model_fields)
|
|
1651
|
+
|
|
1652
|
+
# Detail schema should have all fields including inline custom
|
|
1653
|
+
self.assertIn("id", detail_schema.model_fields)
|
|
1654
|
+
self.assertIn("name", detail_schema.model_fields)
|
|
1655
|
+
self.assertIn("description", detail_schema.model_fields)
|
|
1656
|
+
self.assertIn("detail_extra", detail_schema.model_fields)
|
|
1657
|
+
|
|
1658
|
+
def test_serializer_related_schema_with_inline_customs(self):
|
|
1659
|
+
"""Test that inline customs are included in related schema for non-relation fields."""
|
|
1660
|
+
|
|
1661
|
+
class RelatedInlineCustomsSerializer(serializers.Serializer):
|
|
1662
|
+
class Meta:
|
|
1663
|
+
model = TestModelForeignKey
|
|
1664
|
+
schema_out = serializers.SchemaModelConfig(
|
|
1665
|
+
fields=["id", "name", ("computed", str, "computed_value")]
|
|
1666
|
+
)
|
|
1667
|
+
|
|
1668
|
+
related_schema = RelatedInlineCustomsSerializer.generate_related_s()
|
|
1669
|
+
self.assertIsNotNone(related_schema)
|
|
1670
|
+
self.assertIn("id", related_schema.model_fields)
|
|
1671
|
+
self.assertIn("name", related_schema.model_fields)
|
|
1672
|
+
self.assertIn("computed", related_schema.model_fields)
|
|
1673
|
+
|
|
1674
|
+
def test_inline_customs_only_schema(self):
|
|
1675
|
+
"""Test schema with only inline customs (no regular fields)."""
|
|
1676
|
+
|
|
1677
|
+
class OnlyInlineCustomsSerializer(serializers.Serializer):
|
|
1678
|
+
class Meta:
|
|
1679
|
+
model = TestModelForeignKey
|
|
1680
|
+
schema_in = serializers.SchemaModelConfig(
|
|
1681
|
+
fields=[("custom_only", str, ...)]
|
|
1682
|
+
)
|
|
1683
|
+
schema_out = serializers.SchemaModelConfig(fields=["id"])
|
|
1684
|
+
|
|
1685
|
+
schema = OnlyInlineCustomsSerializer.generate_create_s()
|
|
1686
|
+
self.assertIsNotNone(schema)
|
|
1687
|
+
self.assertIn("custom_only", schema.model_fields)
|
|
1688
|
+
# Should NOT have any model fields auto-included
|
|
1689
|
+
self.assertNotIn("name", schema.model_fields)
|
|
1690
|
+
self.assertNotIn("description", schema.model_fields)
|
|
1691
|
+
|
|
1692
|
+
|
|
1693
|
+
@tag("serializers", "inline_customs", "model_serializer")
|
|
1694
|
+
class InlineCustomsModelSerializerTestCase(TestCase):
|
|
1695
|
+
"""Test cases for inline custom fields with ModelSerializer."""
|
|
1696
|
+
|
|
1697
|
+
def setUp(self):
|
|
1698
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
1699
|
+
|
|
1700
|
+
def test_model_serializer_read_schema_with_inline_customs(self):
|
|
1701
|
+
"""Test ModelSerializer ReadSerializer with inline customs."""
|
|
1702
|
+
from tests.test_app.models import TestModelSerializerInlineCustoms
|
|
1703
|
+
|
|
1704
|
+
schema = TestModelSerializerInlineCustoms.generate_read_s()
|
|
1705
|
+
self.assertIsNotNone(schema)
|
|
1706
|
+
self.assertIn("id", schema.model_fields)
|
|
1707
|
+
self.assertIn("name", schema.model_fields)
|
|
1708
|
+
self.assertIn("inline_computed", schema.model_fields)
|
|
1709
|
+
|
|
1710
|
+
def test_model_serializer_create_schema_with_inline_customs(self):
|
|
1711
|
+
"""Test ModelSerializer CreateSerializer with inline customs."""
|
|
1712
|
+
from tests.test_app.models import TestModelSerializerInlineCustoms
|
|
1713
|
+
|
|
1714
|
+
schema = TestModelSerializerInlineCustoms.generate_create_s()
|
|
1715
|
+
self.assertIsNotNone(schema)
|
|
1716
|
+
self.assertIn("name", schema.model_fields)
|
|
1717
|
+
self.assertIn("extra_create_input", schema.model_fields)
|
|
1718
|
+
|
|
1719
|
+
def test_model_serializer_get_inline_customs(self):
|
|
1720
|
+
"""Test get_inline_customs() works for ModelSerializer."""
|
|
1721
|
+
from tests.test_app.models import TestModelSerializerInlineCustoms
|
|
1722
|
+
|
|
1723
|
+
inline_customs = TestModelSerializerInlineCustoms.get_inline_customs("read")
|
|
1724
|
+
self.assertEqual(len(inline_customs), 1)
|
|
1725
|
+
self.assertEqual(inline_customs[0][0], "inline_computed")
|
|
1726
|
+
|
|
1727
|
+
def test_model_serializer_get_fields_excludes_inline_customs(self):
|
|
1728
|
+
"""Test get_fields() excludes inline customs for ModelSerializer."""
|
|
1729
|
+
from tests.test_app.models import TestModelSerializerInlineCustoms
|
|
1730
|
+
|
|
1731
|
+
fields = TestModelSerializerInlineCustoms.get_fields("read")
|
|
1732
|
+
self.assertIn("id", fields)
|
|
1733
|
+
self.assertIn("name", fields)
|
|
1734
|
+
self.assertNotIn("inline_computed", fields)
|
|
1735
|
+
# Should not contain tuples
|
|
1736
|
+
for f in fields:
|
|
1737
|
+
self.assertIsInstance(f, str)
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/.github/workflows/coverage.yml
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/renderers/orjson_renderer.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/api/views/api_view_set.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/installation.md
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/getting_started/quick_start.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/docs/tutorial/authentication.md
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/decorators/__init__.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/decorators/operations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/ninja_aio/factory/operations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/core/test_exceptions_api.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/core/test_renderer_parser.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/helpers/test_many_to_many_api.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/models/test_model_util.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.16.1 → django_ninja_aio_crud-2.17.0}/tests/models/test_models_extra.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|