django-ninja-aio-crud 2.14.0__tar.gz → 2.15.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.14.0 → django_ninja_aio_crud-2.15.0}/.github/workflows/docs.yml +2 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/PKG-INFO +1 -1
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/models/model_serializer.md +58 -6
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/models/serializers.md +42 -6
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/__init__.py +1 -1
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/models/serializers.py +24 -5
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_app/models.py +178 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_serializers.py +440 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/.github/dependabot.yml +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/.github/workflows/coverage.yml +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/.github/workflows/publish.yml +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/.gitignore +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/.pre-commit-config.yaml +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/LICENSE +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/README.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/CNAME +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/authentication.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/models/model_util.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/pagination.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/renderers/orjson_renderer.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/views/api_view.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/views/api_view_set.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/views/decorators.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/views/mixins.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/auth.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/contributing.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/extra.css +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/images/index/foo-index-create-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/images/index/foo-index-delete-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/images/index/foo-index-list-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/images/index/foo-index-retrieve-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/images/index/foo-index-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/images/index/foo-index-update-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/installation.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/quick_start.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/images/bar-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/images/favicon.ico +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/images/foo-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/images/logo.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/images/model_util/foo-reverse-relations-swagger.png +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/index.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/release_notes.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/requirements.txt +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/tutorial/authentication.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/tutorial/crud.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/tutorial/filtering.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/tutorial/model.md +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/main.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/mkdocs.yml +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/api.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/auth.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/decorators/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/decorators/operations.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/decorators/views.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/exceptions.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/factory/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/factory/operations.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/helpers/api.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/helpers/query.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/models/utils.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/parsers.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/renders.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/schemas/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/schemas/api.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/schemas/filters.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/schemas/generics.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/schemas/helpers.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/types.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/views/api.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/views/mixins.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/pyproject.toml +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/requirements.dev.txt +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/run-local-coverage.sh +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/core/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/core/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/core/test_exceptions_api.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/core/test_renderer_parser.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/generics/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/generics/literals.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/generics/models.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/generics/request.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/generics/views.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/helpers/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/helpers/test_many_to_many_api.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/models/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/models/test_model_util.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/models/test_models_extra.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_app/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_app/schema.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_app/serializers.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_app/views.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_auth.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_decorators.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_exceptions.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_query_util.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/test_settings.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/views/__init__.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/views/test_views.py +0 -0
- {django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/views/test_viewset.py +0 -0
|
@@ -27,6 +27,7 @@ on:
|
|
|
27
27
|
- "2.12"
|
|
28
28
|
- "2.13"
|
|
29
29
|
- "2.14"
|
|
30
|
+
- "2.15"
|
|
30
31
|
make_latest:
|
|
31
32
|
description: 'Set as "latest" and default?'
|
|
32
33
|
type: boolean
|
|
@@ -57,6 +58,7 @@ on:
|
|
|
57
58
|
- "2.12"
|
|
58
59
|
- "2.13"
|
|
59
60
|
- "2.14"
|
|
61
|
+
- "2.15"
|
|
60
62
|
delete_confirm:
|
|
61
63
|
description: 'Confirm deletion of the selected version'
|
|
62
64
|
type: boolean
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/models/model_serializer.md
RENAMED
|
@@ -419,12 +419,14 @@ Use `relations_as_id` to serialize relation fields as IDs instead of nested obje
|
|
|
419
419
|
|
|
420
420
|
| Relation Type | Output Type | Example Value |
|
|
421
421
|
|--------------------|-------------------|----------------------|
|
|
422
|
-
| Forward FK | `
|
|
423
|
-
| Forward O2O | `
|
|
424
|
-
| Reverse FK | `list[
|
|
425
|
-
| Reverse O2O | `
|
|
426
|
-
| M2M (forward) | `list[
|
|
427
|
-
| M2M (reverse) | `list[
|
|
422
|
+
| Forward FK | `PK_TYPE \| None` | `5` or `null` |
|
|
423
|
+
| Forward O2O | `PK_TYPE \| None` | `3` or `null` |
|
|
424
|
+
| Reverse FK | `list[PK_TYPE]` | `[1, 2, 3]` |
|
|
425
|
+
| Reverse O2O | `PK_TYPE \| None` | `7` or `null` |
|
|
426
|
+
| M2M (forward) | `list[PK_TYPE]` | `[1, 2]` |
|
|
427
|
+
| M2M (reverse) | `list[PK_TYPE]` | `[4, 5, 6]` |
|
|
428
|
+
|
|
429
|
+
**Note:** `PK_TYPE` is automatically detected from the related model's primary key field. Supported types include `int` (default), `UUID`, `str`, and any other Django primary key type.
|
|
428
430
|
|
|
429
431
|
**Example:**
|
|
430
432
|
|
|
@@ -494,6 +496,56 @@ class Article(ModelSerializer):
|
|
|
494
496
|
}
|
|
495
497
|
```
|
|
496
498
|
|
|
499
|
+
**UUID Primary Key Example:**
|
|
500
|
+
|
|
501
|
+
When models use UUID primary keys, the output type is automatically `UUID`:
|
|
502
|
+
|
|
503
|
+
```python
|
|
504
|
+
import uuid
|
|
505
|
+
from django.db import models
|
|
506
|
+
from ninja_aio.models import ModelSerializer
|
|
507
|
+
|
|
508
|
+
class Author(ModelSerializer):
|
|
509
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
510
|
+
name = models.CharField(max_length=200)
|
|
511
|
+
|
|
512
|
+
class ReadSerializer:
|
|
513
|
+
fields = ["id", "name", "books"]
|
|
514
|
+
relations_as_id = ["books"]
|
|
515
|
+
|
|
516
|
+
class Book(ModelSerializer):
|
|
517
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
518
|
+
title = models.CharField(max_length=200)
|
|
519
|
+
author = models.ForeignKey(Author, on_delete=models.CASCADE, related_name="books")
|
|
520
|
+
|
|
521
|
+
class ReadSerializer:
|
|
522
|
+
fields = ["id", "title", "author"]
|
|
523
|
+
relations_as_id = ["author"]
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
**Output (Author with UUID):**
|
|
527
|
+
|
|
528
|
+
```json
|
|
529
|
+
{
|
|
530
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
531
|
+
"name": "J.K. Rowling",
|
|
532
|
+
"books": [
|
|
533
|
+
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
534
|
+
"6ba7b811-9dad-11d1-80b4-00c04fd430c8"
|
|
535
|
+
]
|
|
536
|
+
}
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
**Output (Book with UUID):**
|
|
540
|
+
|
|
541
|
+
```json
|
|
542
|
+
{
|
|
543
|
+
"id": "6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
544
|
+
"title": "Harry Potter",
|
|
545
|
+
"author": "550e8400-e29b-41d4-a716-446655440000"
|
|
546
|
+
}
|
|
547
|
+
```
|
|
548
|
+
|
|
497
549
|
**Query Optimization Note:** When using `relations_as_id`, you should still use `select_related()` for forward relations and `prefetch_related()` for reverse/M2M relations to avoid N+1 queries:
|
|
498
550
|
|
|
499
551
|
```python
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/models/serializers.md
RENAMED
|
@@ -429,12 +429,14 @@ Use `relations_as_id` in Meta to serialize relation fields as IDs instead of nes
|
|
|
429
429
|
|
|
430
430
|
| Relation Type | Output Type | Example Value |
|
|
431
431
|
|--------------------|-------------------|----------------------|
|
|
432
|
-
| Forward FK | `
|
|
433
|
-
| Forward O2O | `
|
|
434
|
-
| Reverse FK | `list[
|
|
435
|
-
| Reverse O2O | `
|
|
436
|
-
| M2M (forward) | `list[
|
|
437
|
-
| M2M (reverse) | `list[
|
|
432
|
+
| Forward FK | `PK_TYPE \| None` | `5` or `null` |
|
|
433
|
+
| Forward O2O | `PK_TYPE \| None` | `3` or `null` |
|
|
434
|
+
| Reverse FK | `list[PK_TYPE]` | `[1, 2, 3]` |
|
|
435
|
+
| Reverse O2O | `PK_TYPE \| None` | `7` or `null` |
|
|
436
|
+
| M2M (forward) | `list[PK_TYPE]` | `[1, 2]` |
|
|
437
|
+
| M2M (reverse) | `list[PK_TYPE]` | `[4, 5, 6]` |
|
|
438
|
+
|
|
439
|
+
**Note:** `PK_TYPE` is automatically detected from the related model's primary key field. Supported types include `int` (default), `UUID`, `str`, and any other Django primary key type.
|
|
438
440
|
|
|
439
441
|
**Example:**
|
|
440
442
|
|
|
@@ -509,6 +511,40 @@ class ArticleSerializer(serializers.Serializer):
|
|
|
509
511
|
}
|
|
510
512
|
```
|
|
511
513
|
|
|
514
|
+
**UUID Primary Key Example:**
|
|
515
|
+
|
|
516
|
+
When related models use UUID primary keys, the output type is automatically `UUID`:
|
|
517
|
+
|
|
518
|
+
```python
|
|
519
|
+
import uuid
|
|
520
|
+
from django.db import models
|
|
521
|
+
|
|
522
|
+
class Author(models.Model):
|
|
523
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
524
|
+
name = models.CharField(max_length=200)
|
|
525
|
+
|
|
526
|
+
class AuthorSerializer(serializers.Serializer):
|
|
527
|
+
class Meta:
|
|
528
|
+
model = Author
|
|
529
|
+
schema_out = serializers.SchemaModelConfig(
|
|
530
|
+
fields=["id", "name", "books"]
|
|
531
|
+
)
|
|
532
|
+
relations_as_id = ["books"]
|
|
533
|
+
```
|
|
534
|
+
|
|
535
|
+
**Output (Author with UUID):**
|
|
536
|
+
|
|
537
|
+
```json
|
|
538
|
+
{
|
|
539
|
+
"id": "550e8400-e29b-41d4-a716-446655440000",
|
|
540
|
+
"name": "J.K. Rowling",
|
|
541
|
+
"books": [
|
|
542
|
+
"6ba7b810-9dad-11d1-80b4-00c04fd430c8",
|
|
543
|
+
"6ba7b811-9dad-11d1-80b4-00c04fd430c8"
|
|
544
|
+
]
|
|
545
|
+
}
|
|
546
|
+
```
|
|
547
|
+
|
|
512
548
|
**Combining with relations_serializers:**
|
|
513
549
|
|
|
514
550
|
You can use both `relations_as_id` and `relations_serializers` in the same serializer. Fields in `relations_as_id` take precedence:
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/models/serializers.py
RENAMED
|
@@ -46,8 +46,23 @@ def _extract_pk(v: Any) -> Any:
|
|
|
46
46
|
return v
|
|
47
47
|
|
|
48
48
|
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
class PkFromModel:
|
|
50
|
+
"""Subscriptable type for extracting PK from model instances during serialization.
|
|
51
|
+
|
|
52
|
+
Usage:
|
|
53
|
+
PkFromModel[int] -> for integer PKs
|
|
54
|
+
PkFromModel[str] -> for string PKs
|
|
55
|
+
PkFromModel[UUID] -> for UUID PKs
|
|
56
|
+
PkFromModel -> defaults to int (backwards compatible)
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
_default = Annotated[int, BeforeValidator(_extract_pk)]
|
|
60
|
+
|
|
61
|
+
def __class_getitem__(cls, pk_type: type) -> type:
|
|
62
|
+
return Annotated[pk_type, BeforeValidator(_extract_pk)]
|
|
63
|
+
|
|
64
|
+
def __new__(cls):
|
|
65
|
+
return cls._default
|
|
51
66
|
|
|
52
67
|
|
|
53
68
|
class BaseSerializer:
|
|
@@ -415,12 +430,14 @@ class BaseSerializer:
|
|
|
415
430
|
|
|
416
431
|
# Handle relations_as_id for reverse relations
|
|
417
432
|
if field_name in relations_as_id:
|
|
433
|
+
from ninja_aio.models.utils import ModelUtil
|
|
434
|
+
pk_field_type = ModelUtil(rel_model).pk_field_type
|
|
418
435
|
if many:
|
|
419
436
|
# For many relations, use PkFromModel to extract PKs from model instances
|
|
420
|
-
return (field_name, list[PkFromModel], Field(default_factory=list))
|
|
437
|
+
return (field_name, list[PkFromModel[pk_field_type]], Field(default_factory=list))
|
|
421
438
|
else:
|
|
422
439
|
# For single reverse relations (ReverseOneToOne), extract pk
|
|
423
|
-
return (field_name, PkFromModel | None, None)
|
|
440
|
+
return (field_name, PkFromModel[pk_field_type] | None, None)
|
|
424
441
|
|
|
425
442
|
schema = cls._resolve_relation_schema(field_name, rel_model)
|
|
426
443
|
if not schema:
|
|
@@ -455,8 +472,10 @@ class BaseSerializer:
|
|
|
455
472
|
|
|
456
473
|
# Handle relations_as_id: serialize as the raw FK ID
|
|
457
474
|
if field_name in relations_as_id:
|
|
475
|
+
from ninja_aio.models.utils import ModelUtil
|
|
476
|
+
pk_field_type = ModelUtil(rel_model).pk_field_type
|
|
458
477
|
# Use PkFromModel to extract pk from the related instance during serialization
|
|
459
|
-
return (field_name, PkFromModel | None, None)
|
|
478
|
+
return (field_name, PkFromModel[pk_field_type] | None, None)
|
|
460
479
|
|
|
461
480
|
# Special case: ModelSerializer with no readable fields should be skipped entirely
|
|
462
481
|
if isinstance(rel_model, ModelSerializerMeta):
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import uuid
|
|
2
|
+
|
|
1
3
|
from ninja_aio.models import ModelSerializer
|
|
2
4
|
from django.db import models
|
|
3
5
|
|
|
@@ -298,3 +300,179 @@ class ArticleAsId(BaseTestModelSerializer):
|
|
|
298
300
|
class ReadSerializer:
|
|
299
301
|
fields = ["id", "name", "description", "tags_as_id"]
|
|
300
302
|
relations_as_id = ["tags_as_id"]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
# ==========================================================
|
|
306
|
+
# RELATIONS AS ID WITH UUID PK TEST MODELS
|
|
307
|
+
# ==========================================================
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
class BaseUUIDTestModel(ModelSerializer):
|
|
311
|
+
"""Base model with UUID primary key."""
|
|
312
|
+
|
|
313
|
+
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
|
314
|
+
name = models.CharField(max_length=255)
|
|
315
|
+
|
|
316
|
+
class Meta:
|
|
317
|
+
abstract = True
|
|
318
|
+
|
|
319
|
+
class ReadSerializer:
|
|
320
|
+
fields = ["id", "name"]
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
class AuthorUUID(BaseUUIDTestModel):
|
|
324
|
+
"""Author model with UUID PK for testing relations_as_id on reverse FK."""
|
|
325
|
+
|
|
326
|
+
class ReadSerializer:
|
|
327
|
+
fields = ["id", "name", "books_uuid"]
|
|
328
|
+
relations_as_id = ["books_uuid"]
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class BookUUID(BaseUUIDTestModel):
|
|
332
|
+
"""Book model with UUID PK for testing relations_as_id on forward FK."""
|
|
333
|
+
|
|
334
|
+
author_uuid = models.ForeignKey(
|
|
335
|
+
AuthorUUID,
|
|
336
|
+
on_delete=models.CASCADE,
|
|
337
|
+
related_name="books_uuid",
|
|
338
|
+
null=True,
|
|
339
|
+
blank=True,
|
|
340
|
+
)
|
|
341
|
+
|
|
342
|
+
class ReadSerializer:
|
|
343
|
+
fields = ["id", "name", "author_uuid"]
|
|
344
|
+
relations_as_id = ["author_uuid"]
|
|
345
|
+
|
|
346
|
+
|
|
347
|
+
class ProfileUUID(BaseUUIDTestModel):
|
|
348
|
+
"""Profile model with UUID PK for testing relations_as_id on reverse O2O."""
|
|
349
|
+
|
|
350
|
+
class ReadSerializer:
|
|
351
|
+
fields = ["id", "name", "user_uuid"]
|
|
352
|
+
relations_as_id = ["user_uuid"]
|
|
353
|
+
|
|
354
|
+
|
|
355
|
+
class UserUUID(BaseUUIDTestModel):
|
|
356
|
+
"""User model with UUID PK for testing relations_as_id on forward O2O."""
|
|
357
|
+
|
|
358
|
+
profile_uuid = models.OneToOneField(
|
|
359
|
+
ProfileUUID,
|
|
360
|
+
on_delete=models.CASCADE,
|
|
361
|
+
related_name="user_uuid",
|
|
362
|
+
null=True,
|
|
363
|
+
blank=True,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
class ReadSerializer:
|
|
367
|
+
fields = ["id", "name", "profile_uuid"]
|
|
368
|
+
relations_as_id = ["profile_uuid"]
|
|
369
|
+
|
|
370
|
+
|
|
371
|
+
class TagUUID(BaseUUIDTestModel):
|
|
372
|
+
"""Tag model with UUID PK for testing relations_as_id on reverse M2M."""
|
|
373
|
+
|
|
374
|
+
class ReadSerializer:
|
|
375
|
+
fields = ["id", "name", "articles_uuid"]
|
|
376
|
+
relations_as_id = ["articles_uuid"]
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class ArticleUUID(BaseUUIDTestModel):
|
|
380
|
+
"""Article model with UUID PK for testing relations_as_id on forward M2M."""
|
|
381
|
+
|
|
382
|
+
tags_uuid = models.ManyToManyField(
|
|
383
|
+
TagUUID,
|
|
384
|
+
related_name="articles_uuid",
|
|
385
|
+
blank=True,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
class ReadSerializer:
|
|
389
|
+
fields = ["id", "name", "tags_uuid"]
|
|
390
|
+
relations_as_id = ["tags_uuid"]
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
# ==========================================================
|
|
394
|
+
# RELATIONS AS ID WITH STRING PK TEST MODELS
|
|
395
|
+
# ==========================================================
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
class BaseStringPKTestModel(ModelSerializer):
|
|
399
|
+
"""Base model with string (CharField) primary key."""
|
|
400
|
+
|
|
401
|
+
id = models.CharField(primary_key=True, max_length=50)
|
|
402
|
+
name = models.CharField(max_length=255)
|
|
403
|
+
|
|
404
|
+
class Meta:
|
|
405
|
+
abstract = True
|
|
406
|
+
|
|
407
|
+
class ReadSerializer:
|
|
408
|
+
fields = ["id", "name"]
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
class AuthorStringPK(BaseStringPKTestModel):
|
|
412
|
+
"""Author model with string PK for testing relations_as_id on reverse FK."""
|
|
413
|
+
|
|
414
|
+
class ReadSerializer:
|
|
415
|
+
fields = ["id", "name", "books_str"]
|
|
416
|
+
relations_as_id = ["books_str"]
|
|
417
|
+
|
|
418
|
+
|
|
419
|
+
class BookStringPK(BaseStringPKTestModel):
|
|
420
|
+
"""Book model with string PK for testing relations_as_id on forward FK."""
|
|
421
|
+
|
|
422
|
+
author_str = models.ForeignKey(
|
|
423
|
+
AuthorStringPK,
|
|
424
|
+
on_delete=models.CASCADE,
|
|
425
|
+
related_name="books_str",
|
|
426
|
+
null=True,
|
|
427
|
+
blank=True,
|
|
428
|
+
)
|
|
429
|
+
|
|
430
|
+
class ReadSerializer:
|
|
431
|
+
fields = ["id", "name", "author_str"]
|
|
432
|
+
relations_as_id = ["author_str"]
|
|
433
|
+
|
|
434
|
+
|
|
435
|
+
class ProfileStringPK(BaseStringPKTestModel):
|
|
436
|
+
"""Profile model with string PK for testing relations_as_id on reverse O2O."""
|
|
437
|
+
|
|
438
|
+
class ReadSerializer:
|
|
439
|
+
fields = ["id", "name", "user_str"]
|
|
440
|
+
relations_as_id = ["user_str"]
|
|
441
|
+
|
|
442
|
+
|
|
443
|
+
class UserStringPK(BaseStringPKTestModel):
|
|
444
|
+
"""User model with string PK for testing relations_as_id on forward O2O."""
|
|
445
|
+
|
|
446
|
+
profile_str = models.OneToOneField(
|
|
447
|
+
ProfileStringPK,
|
|
448
|
+
on_delete=models.CASCADE,
|
|
449
|
+
related_name="user_str",
|
|
450
|
+
null=True,
|
|
451
|
+
blank=True,
|
|
452
|
+
)
|
|
453
|
+
|
|
454
|
+
class ReadSerializer:
|
|
455
|
+
fields = ["id", "name", "profile_str"]
|
|
456
|
+
relations_as_id = ["profile_str"]
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
class TagStringPK(BaseStringPKTestModel):
|
|
460
|
+
"""Tag model with string PK for testing relations_as_id on reverse M2M."""
|
|
461
|
+
|
|
462
|
+
class ReadSerializer:
|
|
463
|
+
fields = ["id", "name", "articles_str"]
|
|
464
|
+
relations_as_id = ["articles_str"]
|
|
465
|
+
|
|
466
|
+
|
|
467
|
+
class ArticleStringPK(BaseStringPKTestModel):
|
|
468
|
+
"""Article model with string PK for testing relations_as_id on forward M2M."""
|
|
469
|
+
|
|
470
|
+
tags_str = models.ManyToManyField(
|
|
471
|
+
TagStringPK,
|
|
472
|
+
related_name="articles_str",
|
|
473
|
+
blank=True,
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
class ReadSerializer:
|
|
477
|
+
fields = ["id", "name", "tags_str"]
|
|
478
|
+
relations_as_id = ["tags_str"]
|
|
@@ -911,3 +911,443 @@ class RelationsAsIdIntegrationTestCase(TestCase):
|
|
|
911
911
|
self.assertIsInstance(result.articles_as_id, list)
|
|
912
912
|
self.assertEqual(len(result.articles_as_id), 1)
|
|
913
913
|
self.assertIn(self.article.pk, result.articles_as_id)
|
|
914
|
+
|
|
915
|
+
|
|
916
|
+
@tag("serializers", "relations_as_id", "uuid_pk")
|
|
917
|
+
class RelationsAsIdUUIDModelSerializerTestCase(TestCase):
|
|
918
|
+
"""Test cases for relations_as_id with UUID primary keys."""
|
|
919
|
+
|
|
920
|
+
def setUp(self):
|
|
921
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
922
|
+
|
|
923
|
+
def test_forward_fk_uuid_relations_as_id_schema(self):
|
|
924
|
+
"""Test forward FK field with UUID PK in relations_as_id generates UUID type."""
|
|
925
|
+
from tests.test_app.models import BookUUID
|
|
926
|
+
from uuid import UUID
|
|
927
|
+
|
|
928
|
+
schema = BookUUID.generate_read_s()
|
|
929
|
+
self.assertIsNotNone(schema)
|
|
930
|
+
self.assertIn("author_uuid", schema.model_fields)
|
|
931
|
+
|
|
932
|
+
field_info = schema.model_fields["author_uuid"]
|
|
933
|
+
self.assertTrue(
|
|
934
|
+
field_info.annotation is not None,
|
|
935
|
+
"author_uuid field should have a type annotation",
|
|
936
|
+
)
|
|
937
|
+
|
|
938
|
+
def test_reverse_fk_uuid_relations_as_id_schema(self):
|
|
939
|
+
"""Test reverse FK field with UUID PK in relations_as_id generates list[UUID] type."""
|
|
940
|
+
from tests.test_app.models import AuthorUUID
|
|
941
|
+
from uuid import UUID
|
|
942
|
+
|
|
943
|
+
schema = AuthorUUID.generate_read_s()
|
|
944
|
+
self.assertIsNotNone(schema)
|
|
945
|
+
self.assertIn("books_uuid", schema.model_fields)
|
|
946
|
+
|
|
947
|
+
field_info = schema.model_fields["books_uuid"]
|
|
948
|
+
self.assertTrue(
|
|
949
|
+
field_info.annotation is not None,
|
|
950
|
+
"books_uuid field should have a type annotation",
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
def test_forward_o2o_uuid_relations_as_id_schema(self):
|
|
954
|
+
"""Test forward O2O field with UUID PK in relations_as_id generates UUID type."""
|
|
955
|
+
from tests.test_app.models import UserUUID
|
|
956
|
+
|
|
957
|
+
schema = UserUUID.generate_read_s()
|
|
958
|
+
self.assertIsNotNone(schema)
|
|
959
|
+
self.assertIn("profile_uuid", schema.model_fields)
|
|
960
|
+
|
|
961
|
+
field_info = schema.model_fields["profile_uuid"]
|
|
962
|
+
self.assertTrue(
|
|
963
|
+
field_info.annotation is not None,
|
|
964
|
+
"profile_uuid field should have a type annotation",
|
|
965
|
+
)
|
|
966
|
+
|
|
967
|
+
def test_reverse_o2o_uuid_relations_as_id_schema(self):
|
|
968
|
+
"""Test reverse O2O field with UUID PK in relations_as_id generates UUID type."""
|
|
969
|
+
from tests.test_app.models import ProfileUUID
|
|
970
|
+
|
|
971
|
+
schema = ProfileUUID.generate_read_s()
|
|
972
|
+
self.assertIsNotNone(schema)
|
|
973
|
+
self.assertIn("user_uuid", schema.model_fields)
|
|
974
|
+
|
|
975
|
+
field_info = schema.model_fields["user_uuid"]
|
|
976
|
+
self.assertTrue(
|
|
977
|
+
field_info.annotation is not None,
|
|
978
|
+
"user_uuid field should have a type annotation",
|
|
979
|
+
)
|
|
980
|
+
|
|
981
|
+
def test_forward_m2m_uuid_relations_as_id_schema(self):
|
|
982
|
+
"""Test forward M2M field with UUID PK in relations_as_id generates list[UUID] type."""
|
|
983
|
+
from tests.test_app.models import ArticleUUID
|
|
984
|
+
|
|
985
|
+
schema = ArticleUUID.generate_read_s()
|
|
986
|
+
self.assertIsNotNone(schema)
|
|
987
|
+
self.assertIn("tags_uuid", schema.model_fields)
|
|
988
|
+
|
|
989
|
+
field_info = schema.model_fields["tags_uuid"]
|
|
990
|
+
self.assertTrue(
|
|
991
|
+
field_info.annotation is not None,
|
|
992
|
+
"tags_uuid field should have a type annotation",
|
|
993
|
+
)
|
|
994
|
+
|
|
995
|
+
def test_reverse_m2m_uuid_relations_as_id_schema(self):
|
|
996
|
+
"""Test reverse M2M field with UUID PK in relations_as_id generates list[UUID] type."""
|
|
997
|
+
from tests.test_app.models import TagUUID
|
|
998
|
+
|
|
999
|
+
schema = TagUUID.generate_read_s()
|
|
1000
|
+
self.assertIsNotNone(schema)
|
|
1001
|
+
self.assertIn("articles_uuid", schema.model_fields)
|
|
1002
|
+
|
|
1003
|
+
field_info = schema.model_fields["articles_uuid"]
|
|
1004
|
+
self.assertTrue(
|
|
1005
|
+
field_info.annotation is not None,
|
|
1006
|
+
"articles_uuid field should have a type annotation",
|
|
1007
|
+
)
|
|
1008
|
+
|
|
1009
|
+
|
|
1010
|
+
@tag("serializers", "relations_as_id", "uuid_pk", "integration")
|
|
1011
|
+
class RelationsAsIdUUIDIntegrationTestCase(TestCase):
|
|
1012
|
+
"""Integration tests for relations_as_id with UUID primary keys and actual data serialization."""
|
|
1013
|
+
|
|
1014
|
+
@classmethod
|
|
1015
|
+
def setUpTestData(cls):
|
|
1016
|
+
from tests.test_app.models import (
|
|
1017
|
+
AuthorUUID,
|
|
1018
|
+
BookUUID,
|
|
1019
|
+
ProfileUUID,
|
|
1020
|
+
UserUUID,
|
|
1021
|
+
TagUUID,
|
|
1022
|
+
ArticleUUID,
|
|
1023
|
+
)
|
|
1024
|
+
|
|
1025
|
+
# Create test data for FK relations with UUID PKs
|
|
1026
|
+
cls.author = AuthorUUID.objects.create(name="UUID Author 1")
|
|
1027
|
+
cls.book1 = BookUUID.objects.create(name="UUID Book 1", author_uuid=cls.author)
|
|
1028
|
+
cls.book2 = BookUUID.objects.create(name="UUID Book 2", author_uuid=cls.author)
|
|
1029
|
+
cls.book_no_author = BookUUID.objects.create(name="UUID Book 3", author_uuid=None)
|
|
1030
|
+
|
|
1031
|
+
# Create test data for O2O relations with UUID PKs
|
|
1032
|
+
cls.profile = ProfileUUID.objects.create(name="UUID Profile 1")
|
|
1033
|
+
cls.user = UserUUID.objects.create(name="UUID User 1", profile_uuid=cls.profile)
|
|
1034
|
+
|
|
1035
|
+
# Create test data for M2M relations with UUID PKs
|
|
1036
|
+
cls.tag1 = TagUUID.objects.create(name="UUID Tag 1")
|
|
1037
|
+
cls.tag2 = TagUUID.objects.create(name="UUID Tag 2")
|
|
1038
|
+
cls.article = ArticleUUID.objects.create(name="UUID Article 1")
|
|
1039
|
+
cls.article.tags_uuid.add(cls.tag1, cls.tag2)
|
|
1040
|
+
|
|
1041
|
+
def setUp(self):
|
|
1042
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
1043
|
+
|
|
1044
|
+
def test_forward_fk_uuid_serialization(self):
|
|
1045
|
+
"""Test forward FK field with UUID PK serializes as UUID."""
|
|
1046
|
+
from tests.test_app.models import BookUUID
|
|
1047
|
+
from uuid import UUID
|
|
1048
|
+
|
|
1049
|
+
schema = BookUUID.generate_read_s()
|
|
1050
|
+
result = schema.from_orm(self.book1)
|
|
1051
|
+
|
|
1052
|
+
self.assertIsInstance(result.author_uuid, UUID)
|
|
1053
|
+
self.assertEqual(result.author_uuid, self.author.pk)
|
|
1054
|
+
|
|
1055
|
+
def test_forward_fk_uuid_null_serialization(self):
|
|
1056
|
+
"""Test forward FK field with UUID PK and null value serializes as None."""
|
|
1057
|
+
from tests.test_app.models import BookUUID
|
|
1058
|
+
|
|
1059
|
+
schema = BookUUID.generate_read_s()
|
|
1060
|
+
result = schema.from_orm(self.book_no_author)
|
|
1061
|
+
|
|
1062
|
+
self.assertIsNone(result.author_uuid)
|
|
1063
|
+
|
|
1064
|
+
def test_reverse_fk_uuid_serialization(self):
|
|
1065
|
+
"""Test reverse FK field with UUID PK serializes as list of UUIDs."""
|
|
1066
|
+
from tests.test_app.models import AuthorUUID
|
|
1067
|
+
from uuid import UUID
|
|
1068
|
+
|
|
1069
|
+
author = AuthorUUID.objects.prefetch_related("books_uuid").get(pk=self.author.pk)
|
|
1070
|
+
|
|
1071
|
+
schema = AuthorUUID.generate_read_s()
|
|
1072
|
+
result = schema.from_orm(author)
|
|
1073
|
+
|
|
1074
|
+
self.assertIsInstance(result.books_uuid, list)
|
|
1075
|
+
self.assertEqual(len(result.books_uuid), 2)
|
|
1076
|
+
for book_id in result.books_uuid:
|
|
1077
|
+
self.assertIsInstance(book_id, UUID)
|
|
1078
|
+
self.assertIn(self.book1.pk, result.books_uuid)
|
|
1079
|
+
self.assertIn(self.book2.pk, result.books_uuid)
|
|
1080
|
+
|
|
1081
|
+
def test_forward_o2o_uuid_serialization(self):
|
|
1082
|
+
"""Test forward O2O field with UUID PK serializes as UUID."""
|
|
1083
|
+
from tests.test_app.models import UserUUID
|
|
1084
|
+
from uuid import UUID
|
|
1085
|
+
|
|
1086
|
+
schema = UserUUID.generate_read_s()
|
|
1087
|
+
result = schema.from_orm(self.user)
|
|
1088
|
+
|
|
1089
|
+
self.assertIsInstance(result.profile_uuid, UUID)
|
|
1090
|
+
self.assertEqual(result.profile_uuid, self.profile.pk)
|
|
1091
|
+
|
|
1092
|
+
def test_reverse_o2o_uuid_serialization(self):
|
|
1093
|
+
"""Test reverse O2O field with UUID PK serializes as UUID."""
|
|
1094
|
+
from tests.test_app.models import ProfileUUID
|
|
1095
|
+
from uuid import UUID
|
|
1096
|
+
|
|
1097
|
+
profile = ProfileUUID.objects.select_related("user_uuid").get(pk=self.profile.pk)
|
|
1098
|
+
|
|
1099
|
+
schema = ProfileUUID.generate_read_s()
|
|
1100
|
+
result = schema.from_orm(profile)
|
|
1101
|
+
|
|
1102
|
+
self.assertIsInstance(result.user_uuid, UUID)
|
|
1103
|
+
self.assertEqual(result.user_uuid, self.user.pk)
|
|
1104
|
+
|
|
1105
|
+
def test_forward_m2m_uuid_serialization(self):
|
|
1106
|
+
"""Test forward M2M field with UUID PK serializes as list of UUIDs."""
|
|
1107
|
+
from tests.test_app.models import ArticleUUID
|
|
1108
|
+
from uuid import UUID
|
|
1109
|
+
|
|
1110
|
+
article = ArticleUUID.objects.prefetch_related("tags_uuid").get(pk=self.article.pk)
|
|
1111
|
+
|
|
1112
|
+
schema = ArticleUUID.generate_read_s()
|
|
1113
|
+
result = schema.from_orm(article)
|
|
1114
|
+
|
|
1115
|
+
self.assertIsInstance(result.tags_uuid, list)
|
|
1116
|
+
self.assertEqual(len(result.tags_uuid), 2)
|
|
1117
|
+
for tag_id in result.tags_uuid:
|
|
1118
|
+
self.assertIsInstance(tag_id, UUID)
|
|
1119
|
+
self.assertIn(self.tag1.pk, result.tags_uuid)
|
|
1120
|
+
self.assertIn(self.tag2.pk, result.tags_uuid)
|
|
1121
|
+
|
|
1122
|
+
def test_reverse_m2m_uuid_serialization(self):
|
|
1123
|
+
"""Test reverse M2M field with UUID PK serializes as list of UUIDs."""
|
|
1124
|
+
from tests.test_app.models import TagUUID
|
|
1125
|
+
from uuid import UUID
|
|
1126
|
+
|
|
1127
|
+
tag = TagUUID.objects.prefetch_related("articles_uuid").get(pk=self.tag1.pk)
|
|
1128
|
+
|
|
1129
|
+
schema = TagUUID.generate_read_s()
|
|
1130
|
+
result = schema.from_orm(tag)
|
|
1131
|
+
|
|
1132
|
+
self.assertIsInstance(result.articles_uuid, list)
|
|
1133
|
+
self.assertEqual(len(result.articles_uuid), 1)
|
|
1134
|
+
self.assertIsInstance(result.articles_uuid[0], UUID)
|
|
1135
|
+
self.assertIn(self.article.pk, result.articles_uuid)
|
|
1136
|
+
|
|
1137
|
+
|
|
1138
|
+
@tag("serializers", "relations_as_id", "string_pk")
|
|
1139
|
+
class RelationsAsIdStringPKModelSerializerTestCase(TestCase):
|
|
1140
|
+
"""Test cases for relations_as_id with string (CharField) primary keys."""
|
|
1141
|
+
|
|
1142
|
+
def setUp(self):
|
|
1143
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
1144
|
+
|
|
1145
|
+
def test_forward_fk_string_relations_as_id_schema(self):
|
|
1146
|
+
"""Test forward FK field with string PK in relations_as_id generates str type."""
|
|
1147
|
+
from tests.test_app.models import BookStringPK
|
|
1148
|
+
|
|
1149
|
+
schema = BookStringPK.generate_read_s()
|
|
1150
|
+
self.assertIsNotNone(schema)
|
|
1151
|
+
self.assertIn("author_str", schema.model_fields)
|
|
1152
|
+
|
|
1153
|
+
field_info = schema.model_fields["author_str"]
|
|
1154
|
+
self.assertTrue(
|
|
1155
|
+
field_info.annotation is not None,
|
|
1156
|
+
"author_str field should have a type annotation",
|
|
1157
|
+
)
|
|
1158
|
+
|
|
1159
|
+
def test_reverse_fk_string_relations_as_id_schema(self):
|
|
1160
|
+
"""Test reverse FK field with string PK in relations_as_id generates list[str] type."""
|
|
1161
|
+
from tests.test_app.models import AuthorStringPK
|
|
1162
|
+
|
|
1163
|
+
schema = AuthorStringPK.generate_read_s()
|
|
1164
|
+
self.assertIsNotNone(schema)
|
|
1165
|
+
self.assertIn("books_str", schema.model_fields)
|
|
1166
|
+
|
|
1167
|
+
field_info = schema.model_fields["books_str"]
|
|
1168
|
+
self.assertTrue(
|
|
1169
|
+
field_info.annotation is not None,
|
|
1170
|
+
"books_str field should have a type annotation",
|
|
1171
|
+
)
|
|
1172
|
+
|
|
1173
|
+
def test_forward_o2o_string_relations_as_id_schema(self):
|
|
1174
|
+
"""Test forward O2O field with string PK in relations_as_id generates str type."""
|
|
1175
|
+
from tests.test_app.models import UserStringPK
|
|
1176
|
+
|
|
1177
|
+
schema = UserStringPK.generate_read_s()
|
|
1178
|
+
self.assertIsNotNone(schema)
|
|
1179
|
+
self.assertIn("profile_str", schema.model_fields)
|
|
1180
|
+
|
|
1181
|
+
field_info = schema.model_fields["profile_str"]
|
|
1182
|
+
self.assertTrue(
|
|
1183
|
+
field_info.annotation is not None,
|
|
1184
|
+
"profile_str field should have a type annotation",
|
|
1185
|
+
)
|
|
1186
|
+
|
|
1187
|
+
def test_reverse_o2o_string_relations_as_id_schema(self):
|
|
1188
|
+
"""Test reverse O2O field with string PK in relations_as_id generates str type."""
|
|
1189
|
+
from tests.test_app.models import ProfileStringPK
|
|
1190
|
+
|
|
1191
|
+
schema = ProfileStringPK.generate_read_s()
|
|
1192
|
+
self.assertIsNotNone(schema)
|
|
1193
|
+
self.assertIn("user_str", schema.model_fields)
|
|
1194
|
+
|
|
1195
|
+
field_info = schema.model_fields["user_str"]
|
|
1196
|
+
self.assertTrue(
|
|
1197
|
+
field_info.annotation is not None,
|
|
1198
|
+
"user_str field should have a type annotation",
|
|
1199
|
+
)
|
|
1200
|
+
|
|
1201
|
+
def test_forward_m2m_string_relations_as_id_schema(self):
|
|
1202
|
+
"""Test forward M2M field with string PK in relations_as_id generates list[str] type."""
|
|
1203
|
+
from tests.test_app.models import ArticleStringPK
|
|
1204
|
+
|
|
1205
|
+
schema = ArticleStringPK.generate_read_s()
|
|
1206
|
+
self.assertIsNotNone(schema)
|
|
1207
|
+
self.assertIn("tags_str", schema.model_fields)
|
|
1208
|
+
|
|
1209
|
+
field_info = schema.model_fields["tags_str"]
|
|
1210
|
+
self.assertTrue(
|
|
1211
|
+
field_info.annotation is not None,
|
|
1212
|
+
"tags_str field should have a type annotation",
|
|
1213
|
+
)
|
|
1214
|
+
|
|
1215
|
+
def test_reverse_m2m_string_relations_as_id_schema(self):
|
|
1216
|
+
"""Test reverse M2M field with string PK in relations_as_id generates list[str] type."""
|
|
1217
|
+
from tests.test_app.models import TagStringPK
|
|
1218
|
+
|
|
1219
|
+
schema = TagStringPK.generate_read_s()
|
|
1220
|
+
self.assertIsNotNone(schema)
|
|
1221
|
+
self.assertIn("articles_str", schema.model_fields)
|
|
1222
|
+
|
|
1223
|
+
field_info = schema.model_fields["articles_str"]
|
|
1224
|
+
self.assertTrue(
|
|
1225
|
+
field_info.annotation is not None,
|
|
1226
|
+
"articles_str field should have a type annotation",
|
|
1227
|
+
)
|
|
1228
|
+
|
|
1229
|
+
|
|
1230
|
+
@tag("serializers", "relations_as_id", "string_pk", "integration")
|
|
1231
|
+
class RelationsAsIdStringPKIntegrationTestCase(TestCase):
|
|
1232
|
+
"""Integration tests for relations_as_id with string primary keys and actual data serialization."""
|
|
1233
|
+
|
|
1234
|
+
@classmethod
|
|
1235
|
+
def setUpTestData(cls):
|
|
1236
|
+
from tests.test_app.models import (
|
|
1237
|
+
AuthorStringPK,
|
|
1238
|
+
BookStringPK,
|
|
1239
|
+
ProfileStringPK,
|
|
1240
|
+
UserStringPK,
|
|
1241
|
+
TagStringPK,
|
|
1242
|
+
ArticleStringPK,
|
|
1243
|
+
)
|
|
1244
|
+
|
|
1245
|
+
# Create test data for FK relations with string PKs
|
|
1246
|
+
cls.author = AuthorStringPK.objects.create(id="author-001", name="String Author 1")
|
|
1247
|
+
cls.book1 = BookStringPK.objects.create(id="book-001", name="String Book 1", author_str=cls.author)
|
|
1248
|
+
cls.book2 = BookStringPK.objects.create(id="book-002", name="String Book 2", author_str=cls.author)
|
|
1249
|
+
cls.book_no_author = BookStringPK.objects.create(id="book-003", name="String Book 3", author_str=None)
|
|
1250
|
+
|
|
1251
|
+
# Create test data for O2O relations with string PKs
|
|
1252
|
+
cls.profile = ProfileStringPK.objects.create(id="profile-001", name="String Profile 1")
|
|
1253
|
+
cls.user = UserStringPK.objects.create(id="user-001", name="String User 1", profile_str=cls.profile)
|
|
1254
|
+
|
|
1255
|
+
# Create test data for M2M relations with string PKs
|
|
1256
|
+
cls.tag1 = TagStringPK.objects.create(id="tag-001", name="String Tag 1")
|
|
1257
|
+
cls.tag2 = TagStringPK.objects.create(id="tag-002", name="String Tag 2")
|
|
1258
|
+
cls.article = ArticleStringPK.objects.create(id="article-001", name="String Article 1")
|
|
1259
|
+
cls.article.tags_str.add(cls.tag1, cls.tag2)
|
|
1260
|
+
|
|
1261
|
+
def setUp(self):
|
|
1262
|
+
warnings.simplefilter("ignore", UserWarning)
|
|
1263
|
+
|
|
1264
|
+
def test_forward_fk_string_serialization(self):
|
|
1265
|
+
"""Test forward FK field with string PK serializes as str."""
|
|
1266
|
+
from tests.test_app.models import BookStringPK
|
|
1267
|
+
|
|
1268
|
+
schema = BookStringPK.generate_read_s()
|
|
1269
|
+
result = schema.from_orm(self.book1)
|
|
1270
|
+
|
|
1271
|
+
self.assertIsInstance(result.author_str, str)
|
|
1272
|
+
self.assertEqual(result.author_str, self.author.pk)
|
|
1273
|
+
self.assertEqual(result.author_str, "author-001")
|
|
1274
|
+
|
|
1275
|
+
def test_forward_fk_string_null_serialization(self):
|
|
1276
|
+
"""Test forward FK field with string PK and null value serializes as None."""
|
|
1277
|
+
from tests.test_app.models import BookStringPK
|
|
1278
|
+
|
|
1279
|
+
schema = BookStringPK.generate_read_s()
|
|
1280
|
+
result = schema.from_orm(self.book_no_author)
|
|
1281
|
+
|
|
1282
|
+
self.assertIsNone(result.author_str)
|
|
1283
|
+
|
|
1284
|
+
def test_reverse_fk_string_serialization(self):
|
|
1285
|
+
"""Test reverse FK field with string PK serializes as list of strs."""
|
|
1286
|
+
from tests.test_app.models import AuthorStringPK
|
|
1287
|
+
|
|
1288
|
+
author = AuthorStringPK.objects.prefetch_related("books_str").get(pk=self.author.pk)
|
|
1289
|
+
|
|
1290
|
+
schema = AuthorStringPK.generate_read_s()
|
|
1291
|
+
result = schema.from_orm(author)
|
|
1292
|
+
|
|
1293
|
+
self.assertIsInstance(result.books_str, list)
|
|
1294
|
+
self.assertEqual(len(result.books_str), 2)
|
|
1295
|
+
for book_id in result.books_str:
|
|
1296
|
+
self.assertIsInstance(book_id, str)
|
|
1297
|
+
self.assertIn(self.book1.pk, result.books_str)
|
|
1298
|
+
self.assertIn(self.book2.pk, result.books_str)
|
|
1299
|
+
|
|
1300
|
+
def test_forward_o2o_string_serialization(self):
|
|
1301
|
+
"""Test forward O2O field with string PK serializes as str."""
|
|
1302
|
+
from tests.test_app.models import UserStringPK
|
|
1303
|
+
|
|
1304
|
+
schema = UserStringPK.generate_read_s()
|
|
1305
|
+
result = schema.from_orm(self.user)
|
|
1306
|
+
|
|
1307
|
+
self.assertIsInstance(result.profile_str, str)
|
|
1308
|
+
self.assertEqual(result.profile_str, self.profile.pk)
|
|
1309
|
+
self.assertEqual(result.profile_str, "profile-001")
|
|
1310
|
+
|
|
1311
|
+
def test_reverse_o2o_string_serialization(self):
|
|
1312
|
+
"""Test reverse O2O field with string PK serializes as str."""
|
|
1313
|
+
from tests.test_app.models import ProfileStringPK
|
|
1314
|
+
|
|
1315
|
+
profile = ProfileStringPK.objects.select_related("user_str").get(pk=self.profile.pk)
|
|
1316
|
+
|
|
1317
|
+
schema = ProfileStringPK.generate_read_s()
|
|
1318
|
+
result = schema.from_orm(profile)
|
|
1319
|
+
|
|
1320
|
+
self.assertIsInstance(result.user_str, str)
|
|
1321
|
+
self.assertEqual(result.user_str, self.user.pk)
|
|
1322
|
+
self.assertEqual(result.user_str, "user-001")
|
|
1323
|
+
|
|
1324
|
+
def test_forward_m2m_string_serialization(self):
|
|
1325
|
+
"""Test forward M2M field with string PK serializes as list of strs."""
|
|
1326
|
+
from tests.test_app.models import ArticleStringPK
|
|
1327
|
+
|
|
1328
|
+
article = ArticleStringPK.objects.prefetch_related("tags_str").get(pk=self.article.pk)
|
|
1329
|
+
|
|
1330
|
+
schema = ArticleStringPK.generate_read_s()
|
|
1331
|
+
result = schema.from_orm(article)
|
|
1332
|
+
|
|
1333
|
+
self.assertIsInstance(result.tags_str, list)
|
|
1334
|
+
self.assertEqual(len(result.tags_str), 2)
|
|
1335
|
+
for tag_id in result.tags_str:
|
|
1336
|
+
self.assertIsInstance(tag_id, str)
|
|
1337
|
+
self.assertIn(self.tag1.pk, result.tags_str)
|
|
1338
|
+
self.assertIn(self.tag2.pk, result.tags_str)
|
|
1339
|
+
|
|
1340
|
+
def test_reverse_m2m_string_serialization(self):
|
|
1341
|
+
"""Test reverse M2M field with string PK serializes as list of strs."""
|
|
1342
|
+
from tests.test_app.models import TagStringPK
|
|
1343
|
+
|
|
1344
|
+
tag = TagStringPK.objects.prefetch_related("articles_str").get(pk=self.tag1.pk)
|
|
1345
|
+
|
|
1346
|
+
schema = TagStringPK.generate_read_s()
|
|
1347
|
+
result = schema.from_orm(tag)
|
|
1348
|
+
|
|
1349
|
+
self.assertIsInstance(result.articles_str, list)
|
|
1350
|
+
self.assertEqual(len(result.articles_str), 1)
|
|
1351
|
+
self.assertIsInstance(result.articles_str[0], str)
|
|
1352
|
+
self.assertIn(self.article.pk, result.articles_str)
|
|
1353
|
+
self.assertEqual(result.articles_str[0], "article-001")
|
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/docs/api/renderers/orjson_renderer.md
RENAMED
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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.14.0 → django_ninja_aio_crud-2.15.0}/docs/getting_started/installation.md
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/decorators/__init__.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/ninja_aio/decorators/operations.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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.14.0 → django_ninja_aio_crud-2.15.0}/tests/core/test_exceptions_api.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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.14.0 → django_ninja_aio_crud-2.15.0}/tests/helpers/test_many_to_many_api.py
RENAMED
|
File without changes
|
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.0}/tests/models/test_model_util.py
RENAMED
|
File without changes
|
{django_ninja_aio_crud-2.14.0 → django_ninja_aio_crud-2.15.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
|