v2.9.0 diff | 2026-01-14 |  Release notesRelease Notes
[v2.9.0] - 2026-01-14
✨ Added
🛠 Changed
- API Parameter Change:
is_for_read → is_for:
- Renamed
is_for_read: bool parameter to is_for: Literal["read", "detail"] | None across all ModelUtil methods:
get_objects()
get_object()
read_s()
list_read_s()
_get_base_queryset()
_apply_query_optimizations()
_serialize_queryset()
_serialize_single_object()
_handle_query_mode()
_read_s()
-
This enables explicit control over which optimization strategy to use
-
Query Optimization Methods Now Accept is_for Parameter:
get_select_relateds(is_for: Literal["read", "detail"] = "read")
get_reverse_relations(is_for: Literal["read", "detail"] = "read")
-
_get_read_optimizations(is_for: Literal["read", "detail"] = "read")
-
APIViewSet Retrieve Endpoint:
- Now uses
is_for="detail" when schema_detail is available
-
Falls back to is_for="read" when no detail schema is configured
-
Code Formatting Improvements:
- Reformatted multi-line tuples in
_is_reverse_relation()
- Reformatted conditional in
_warn_missing_relation_serializer()
- Reformatted error message in
get_schema_out_data()
🐛 Fixed
- Query Optimization Fallback Bug:
- Fixed
_get_read_optimizations() to fall back to read config when detail config is not defined
- Previously returned empty
ModelQuerySetSchema() when QuerySet.detail was missing, losing all optimizations
📝 Documentation
- ModelUtil Documentation (docs/api/models/model_util.md):
- Updated all method signatures from
is_for_read: bool to is_for: Literal["read", "detail"] | None
- Added
QuerySet.detail configuration example
- Added
serializable_detail_fields property documentation
- Updated examples to show
is_for="read" and is_for="detail" usage
-
Added fallback behavior notes for detail optimizations
-
ModelSerializer Documentation (docs/api/models/model_serializer.md):
- Added Fallback Behavior note in
DetailSerializer section
- Updated
generate_detail_s() comment to indicate fallback to read schema
-
Updated fields table to mention fallback behavior
-
Serializer Documentation (docs/api/models/serializers.md):
- Added
QuerySet.detail configuration example
- Added explanation of how each QuerySet config is applied (
read, detail, queryset_request, extras)
🧪 Tests
🔧 Internal Changes
- BaseSerializer Changes:
- Added
detail = ModelQuerySetSchema() to inner QuerySet class
-
Added fallback logic in get_fields() for detail type
-
QueryUtilBaseScopesSchema Changes:
-
Added DETAIL: str = "detail" scope constant
-
QueryUtil Changes:
- Added
detail_config property for accessing detail query configuration
🚀 Use Cases & Examples
Detail-Specific Query Optimizations
```python
from ninja_aio.models import ModelSerializer
from ninja_aio.schemas.helpers import ModelQuerySetSchema
class Article(ModelSerializer):
title = models.CharField(max_length=200)
summary = models.TextField()
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag)
comments = models.ManyToManyField(Comment)
class ReadSerializer:
# List view: minimal fields
fields = ["id", "title", "summary", "author"]
class DetailSerializer:
# Detail view: all fields including expensive relations
fields = ["id", "title", "summary", "content", "author", "tags", "comments"]
class QuerySet:
# Optimizations for list endpoint
read = ModelQuerySetSchema(
select_related=["author"],
prefetch_related=[],
)
# Optimizations for retrieve endpoint (more aggressive prefetching)
detail = ModelQuerySetSchema(
select_related=["author", "author__profile"],
prefetch_related=["tags", "comments", "comments__author"],
)
```
Behavior:
- GET /articles/ uses QuerySet.read optimizations (light prefetching)
- GET /articles/{pk} uses QuerySet.detail optimizations (full prefetching)
Fallback Behavior
```python
class Article(ModelSerializer):
class ReadSerializer:
fields = ["id", "title", "content"]
class QuerySet:
read = ModelQuerySetSchema(
select_related=["author"],
prefetch_related=["tags"],
)
# No detail config - will fall back to read!
Both list and retrieve use QuerySet.read optimizations
generate_detail_s() returns same schema as generate_read_s()
```
Using is_for Parameter Directly
```python
from ninja_aio.models import ModelUtil
util = ModelUtil(Article)
For list operations
qs = await util.get_objects(request, is_for="read")
For single object retrieval
obj = await util.get_object(request, pk=1, is_for="detail")
For serialization
data = await util.read_s(schema, request, instance=obj, is_for="detail")
items = await util.list_read_s(schema, request, instances=qs, is_for="read")
```
🔍 Migration Guide
Breaking Change: is_for_read → is_for
If you call ModelUtil methods directly with is_for_read, update to use is_for:
```python
Before (v2.8.0)
await util.get_objects(request, is_for_read=True)
await util.get_object(request, pk=1, is_for_read=True)
await util.read_s(schema, request, instance=obj, is_for_read=True)
After (v2.9.0)
await util.get_objects(request, is_for="read")
await util.get_object(request, pk=1, is_for="detail")
await util.read_s(schema, request, instance=obj, is_for="detail")
```
Mapping:
| Old Parameter | New Parameter |
|---------------|---------------|
| is_for_read=True | is_for="read" (for list) or is_for="detail" (for retrieve) |
| is_for_read=False | is_for=None |
Adding Detail-Specific Optimizations
```python
Before (v2.8.0) - Same optimizations for list and retrieve
class QuerySet:
read = ModelQuerySetSchema(
select_related=["author"],
prefetch_related=["tags", "comments"], # Always loaded!
)
After (v2.9.0) - Different optimizations per operation
class QuerySet:
read = ModelQuerySetSchema(
select_related=["author"],
prefetch_related=[], # Light for list
)
detail = ModelQuerySetSchema(
select_related=["author", "author__profile"],
prefetch_related=["tags", "comments"], # Full for retrieve
)
```
📊 Performance Benefits
| Scenario | Without Detail Config | With Detail Config |
|----------|----------------------|-------------------|
| List 100 articles | Prefetches tags + comments for all | Only prefetches what's needed for list |
| Retrieve single | Uses list optimizations | Uses detail-specific optimizations |
| N+1 queries | May occur if list over-fetches | Optimized per endpoint |
| Memory usage | Higher (unnecessary prefetch) | Optimized per operation |
⚠️ Important Notes
- Breaking Change:
is_for_read: bool parameter renamed to is_for: Literal["read", "detail"] | None
- Fallback Behavior: All fallbacks are automatic - no configuration needed for backward compatibility
- QuerySet.detail: Optional - falls back to
QuerySet.read if not defined
- DetailSerializer fields: Optional - falls back to
ReadSerializer fields if not defined
- generate_detail_s(): Now always returns a schema (falls back to read schema)
🔗 Links
Version History
For older versions, please refer to the GitHub releases page. |
v2.8.0 diff | 2026-01-14 |  Release notesRelease Notes
[v2.8.0] - 2026-01-14
✨ Added
- Detail Schema Support for Retrieve Endpoints:
- New
DetailSerializer configuration class for ModelSerializer
- New
schema_detail configuration option for Serializer Meta class
- New
schema_detail attribute on APIViewSet for custom detail schemas
- New
generate_detail_s() method for generating detail schemas
- Retrieve endpoint (
GET /{base}/{pk}) now uses schema_detail when available, falling back to schema_out
-
Enables performance optimization: minimal fields for list views, full details for single object retrieval
-
serializer_class Support for M2MRelationSchema:
M2MRelationSchema now accepts serializer_class parameter for plain Django models
- Auto-generates
related_schema from the serializer when provided
- Alternative to manually providing
related_schema for plain models
- Validation ensures
serializer_class cannot be used when model is already a ModelSerializer
🛠 Changed
- APIViewSet Schema Generation:
get_schemas() now returns a 4-tuple: (schema_out, schema_detail, schema_in, schema_update)
- New
_get_retrieve_schema() helper method for retrieve endpoint schema selection
-
retrieve_view() updated to use detail schema when available
-
Refactored get_schema_out_data() Function:
- Extracted helper methods for better code organization:
_is_reverse_relation() - Check if field is a reverse relation
_is_forward_relation() - Check if field is a forward relation
_warn_missing_relation_serializer() - Emit warning for missing serializer mappings
_process_field() - Process single field and determine classification
- Renamed parameter
type to schema_type to avoid shadowing built-in
- Renamed internal variable
rels to forward_rels for clarity
-
Now accepts schema_type: Literal["Out", "Detail"] parameter
-
Performance Optimization in _generate_union_schema():
- Fixed double method call issue using walrus operator
-
generate_related_s() now called once per serializer instead of twice
-
Updated Type Definitions:
S_TYPES now includes "detail": Literal["read", "detail", "create", "update"]
SCHEMA_TYPES now includes "Detail": Literal["In", "Out", "Detail", "Patch", "Related"]
📝 Documentation
- ModelSerializer Documentation (docs/api/models/model_serializer.md):
- New DetailSerializer section with complete documentation
- Updated schema generation table to include
generate_detail_s()
- Added example showing List vs Detail output differences
-
Updated "Auto-Generated Schemas" to show five schema types
-
Serializer Documentation (docs/api/models/serializers.md):
- Added
schema_detail to Meta configuration options
- New "Detail Schema for Retrieve Endpoint" section
-
Updated schema generation examples to include generate_detail_s()
-
APIViewSet Documentation (docs/api/views/api_view_set.md):
- Updated CRUD endpoints table to show retrieve uses
schema_detail
- Added
schema_detail to Core Attributes table
- New "Detail Schema for Retrieve Endpoint" section with examples
- Updated automatic schema generation section
- Added
serializer_class documentation for M2MRelationSchema
- Added tabbed examples for
related_schema vs serializer_class usage
🧪 Tests
- New Detail Schema Test Cases:
-
DetailSerializerTestCase in tests/test_serializers.py:
test_generate_detail_schema_with_serializer() - Basic detail schema generation
test_generate_detail_schema_returns_none_when_not_configured() - None when not configured
test_detail_schema_with_relations() - Relations in detail schema
test_detail_schema_with_custom_fields() - Custom fields support
test_detail_schema_with_optionals() - Optional fields support
-
DetailSchemaModelSerializerTestCase in tests/views/test_viewset.py:
test_read_schema_has_minimal_fields() - ReadSerializer has minimal fields
test_detail_schema_has_extended_fields() - DetailSerializer has extended fields
test_get_retrieve_schema_returns_detail() - Retrieve uses detail schema
test_get_schemas_returns_four_tuple() - get_schemas returns 4-tuple
-
DetailSchemaSerializerTestCase - Tests for Serializer class with schema_detail
-
DetailSchemaFallbackTestCase - Tests fallback to schema_out when no detail defined
-
New M2M serializer_class Test Cases:
M2MRelationSchemaSerializerClassTestCase - Tests M2M with serializer_class
-
M2MRelationSchemaValidationTestCase:
test_serializer_class_with_plain_model_succeeds()
test_model_serializer_auto_generates_related_schema()
test_serializer_class_with_model_serializer_raises_error()
test_plain_model_without_serializer_class_or_related_schema_raises_error()
test_explicit_related_schema_takes_precedence()
-
New Test Model:
TestModelSerializerWithDetail in tests/test_app/models.py
-
Demonstrates separate ReadSerializer and DetailSerializer configurations
-
Updated Existing Tests:
- All
schemas property definitions updated to return 4-tuple format
test_get_schemas updated to expect 4 elements instead of 3
- Refactored
ManyToManyAPITestCase into Tests.BaseManyToManyAPITestCase base class
🔧 Internal Changes
- Schema Mapping Updates:
_SCHEMA_META_MAP now includes "detail": "DetailSerializer" for ModelSerializer
_SERIALIZER_CONFIG_MAP now includes "detail": "detail" for Serializer
-
_get_serializer_config() updated to handle "detail" case
-
ModelSerializer Changes:
- New
DetailSerializer inner class with fields, customs, optionals, excludes attributes
_generate_model_schema() updated to handle "Detail" schema type
-
Schema naming: "Out" → {model}SchemaOut, "Detail" → {model}DetailSchemaOut
-
Serializer.Meta Changes:
- New
schema_detail: Optional[SchemaModelConfig] attribute
-
model_dump() now uses detail schema when available for single object serialization
-
M2MRelationSchema Changes:
- New
serializer_class: Optional[SerializerMeta] field
validate_related_schema() validator updated to handle serializer_class
- ManyToManyAPI updated to pass serializer_class to ModelUtil
🚀 Use Cases & Examples
Detail Schema for Performance Optimization
```python
from ninja_aio.models import ModelSerializer
from django.db import models
class Article(ModelSerializer):
title = models.CharField(max_length=200)
summary = models.TextField()
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag)
view_count = models.IntegerField(default=0)
class ReadSerializer:
# List view: minimal fields for performance
fields = ["id", "title", "summary", "author"]
class DetailSerializer:
# Detail view: all fields including expensive relations
fields = ["id", "title", "summary", "content", "author", "tags", "view_count"]
customs = [
("reading_time", int, lambda obj: len(obj.content.split()) // 200),
]
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
pass # Schemas auto-generated from model
```
Endpoint Behavior:
- GET /articles/ returns [{"id": 1, "title": "...", "summary": "...", "author": {...}}, ...]
- GET /articles/1 returns {"id": 1, "title": "...", "summary": "...", "content": "...", "author": {...}, "tags": [...], "view_count": 1234, "reading_time": 5}
Detail Schema with Serializer Class
```python
from ninja_aio.models import serializers
class ArticleSerializer(serializers.Serializer):
class Meta:
model = models.Article
schema_out = serializers.SchemaModelConfig(
# List view: minimal fields
fields=["id", "title", "summary"]
)
schema_detail = serializers.SchemaModelConfig(
# Detail view: all fields
fields=["id", "title", "summary", "content", "author", "tags"],
customs=[("reading_time", int, lambda obj: len(obj.content.split()) // 200)]
)
@api.viewset(model=models.Article)
class ArticleViewSet(APIViewSet):
serializer_class = ArticleSerializer
```
M2M with serializer_class
```python
from ninja_aio.models import serializers
from ninja_aio.schemas import M2MRelationSchema
class TagSerializer(serializers.Serializer):
class Meta:
model = Tag
schema_out = serializers.SchemaModelConfig(fields=["id", "name"])
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
m2m_relations = [
M2MRelationSchema(
model=Tag, # plain Django model
related_name="tags",
serializer_class=TagSerializer, # auto-generates related_schema
add=True,
remove=True,
get=True,
)
]
```
🔍 Migration Guide
Using Detail Schemas
No migration required! Detail schema support is fully backward compatible:
```python
Existing code continues to work (no DetailSerializer = uses schema_out for retrieve)
class Article(ModelSerializer):
class ReadSerializer:
fields = ["id", "title", "content"] # Used for both list AND retrieve
New: Add DetailSerializer for different retrieve response
class Article(ModelSerializer):
class ReadSerializer:
fields = ["id", "title"] # Used for list only
class DetailSerializer:
fields = ["id", "title", "content", "author", "tags"] # Used for retrieve
```
Using serializer_class in M2MRelationSchema
```python
Before (v2.7.0) - Must provide related_schema manually
M2MRelationSchema(
model=Tag,
related_name="tags",
related_schema=TagOut, # Must define this schema manually
)
After (v2.8.0) - Can use serializer_class instead
M2MRelationSchema(
model=Tag,
related_name="tags",
serializer_class=TagSerializer, # Auto-generates related_schema!
)
```
Updating Custom ViewSet Subclasses
If you override get_schemas(), update to return 4-tuple:
```python
Before (v2.7.0)
def get_schemas(self):
return (schema_out, schema_in, schema_update)
After (v2.8.0)
def get_schemas(self):
return (schema_out, schema_detail, schema_in, schema_update)
```
🎯 When to Use Detail Schema
- Performance Optimization: Return minimal fields in list views, full details in retrieve
- API Design: Clients get summaries in lists, full objects on individual requests
- Expensive Relations: Avoid loading M2M/reverse relations for list endpoints
- Computed Fields: Only compute expensive fields for single object retrieval
- Bandwidth Optimization: Reduce payload size for list responses
📊 Performance Benefits
| Scenario | Without Detail Schema | With Detail Schema |
|----------|----------------------|-------------------|
| List 100 articles | Returns 100 × full content | Returns 100 × summary only |
| Load M2M tags | Loaded for all 100 items | Only loaded for single retrieve |
| Computed fields | Calculated for all items | Only calculated on retrieve |
| Response size | Large (full content) | Optimized per endpoint |
⚠️ Important Notes
- Fallback Behavior: If
DetailSerializer/schema_detail not defined, retrieve uses schema_out
- Schema Generation:
generate_detail_s() returns None if no detail config exists
- Backward Compatibility: All existing code works without changes
- 4-Tuple Return:
get_schemas() now returns 4 values instead of 3
- M2M Validation: Cannot use
serializer_class with ModelSerializer models
🙏 Acknowledgments
This release focuses on:
- Enhanced API design flexibility with separate list/detail schemas
- Performance optimization for list endpoints
- Better M2M relation configuration options
- Improved code organization and maintainability
🔗 Links
📦 Quick Start with Detail Schema
```python
from ninja_aio.models import ModelSerializer
from ninja_aio.views import APIViewSet
from ninja_aio import NinjaAIO
from django.db import models
api = NinjaAIO(title="My API")
Step 1: Define your model with ReadSerializer and DetailSerializer
class Article(ModelSerializer):
title = models.CharField(max_length=200)
summary = models.TextField()
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
tags = models.ManyToManyField(Tag)
class ReadSerializer:
fields = ["id", "title", "summary"] # Minimal for list
class DetailSerializer:
fields = ["id", "title", "summary", "content", "author", "tags"] # Full for retrieve
Step 2: Create your ViewSet (schemas auto-generated!)
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
pass
That's it! Your API now has optimized list and detail endpoints:
GET /articles/ → Returns list with minimal fields
GET /articles/{pk} → Returns single article with all fields
```
Version History
For older versions, please refer to the GitHub releases page. |
v2.7.0 diff | 2026-01-13 |  Release notesRelease Notes
[v2.7.0] - 2026-01-13
✨ Added
🛠 Changed
- Enhanced Serializer Reference Resolution:
- Now handles Union types by recursively resolving each member
- Handles ForwardRef objects created by string type hints in unions (e.g.,
Union["StringType"])
-
Optimizes single-type unions by returning the single type directly
-
Enhanced Relation Schema Generation:
- Generates union schemas when serializer reference is a Union type
- Maintains full backward compatibility with single serializer references
-
Automatically filters out None schemas from union members
-
Updated Type Hints:
- All serializer methods updated to reflect Union support
- Better type safety for Union[Schema, ...] return values
- Clearer documentation of acceptable input types
📝 Documentation
- Comprehensive Union Types Documentation in docs/api/models/serializers.md:
-
New "Union Types for Polymorphic Relations" section:
- Complete explanation of Union support with real-world examples
- Basic polymorphic example with Video and Image serializers
- All four Union type format variations documented with code samples
- Use cases: polymorphic relations, flexible APIs, gradual migrations, multi-tenant systems
- Complete polymorphic example using Django's GenericForeignKey
- BlogPost/Product/Event example showing complex multi-model relations
-
New "String Reference Formats" section:
- Local class name format:
"ArticleSerializer"
- Absolute import path format:
"myapp.serializers.ArticleSerializer"
- Requirements and resolution behavior documented
- Cross-module references example with circular dependencies
-
Enhanced Configuration Section:
relations_serializers parameter updated to document Union support
- Clear explanation: "Serializer class, string reference, or Union of serializers"
- Forward/circular dependencies and polymorphic relations highlighted
- Updated comparison table showing Union support feature
-
Updated Key Features:
- Added Union types for polymorphic relations to key features list
- Updated notes to mention Union type lazy resolution
- Added note about schema generator creating unions
-
Code Examples and Best Practices:
- Video/Image comment example for basic polymorphic relations
- BlogPost/Product/Event example for complex GenericForeignKey usage
- Cross-module circular reference example (Article ↔ User)
- All four Union format variations with syntax examples
🧪 Tests
🔧 Internal Changes
🚀 Use Cases & Examples
Basic Polymorphic Relations
```python
from typing import Union
from ninja_aio.models import serializers
class VideoSerializer(serializers.Serializer):
class Meta:
model = models.Video
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "duration", "url"]
)
class ImageSerializer(serializers.Serializer):
class Meta:
model = models.Image
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "width", "height", "url"]
)
class CommentSerializer(serializers.Serializer):
class Meta:
model = models.Comment
schema_out = serializers.SchemaModelConfig(
fields=["id", "text", "content_object"]
)
relations_serializers = {
"content_object": Union[VideoSerializer, ImageSerializer],
}
```
Cross-Module References
```python
myapp/serializers.py
class ArticleSerializer(serializers.Serializer):
class Meta:
model = models.Article
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "author"]
)
relations_serializers = {
"author": "users.serializers.UserSerializer", # Absolute path
}
users/serializers.py
class UserSerializer(serializers.Serializer):
class Meta:
model = models.User
schema_out = serializers.SchemaModelConfig(
fields=["id", "username", "articles"]
)
relations_serializers = {
"articles": "myapp.serializers.ArticleSerializer", # Circular ref!
}
```
Generic Foreign Keys
```python
from django.contrib.contenttypes.fields import GenericForeignKey
from typing import Union
class CommentSerializer(serializers.Serializer):
class Meta:
model = Comment
schema_out = serializers.SchemaModelConfig(
fields=["id", "text", "created_at", "content_object"]
)
relations_serializers = {
"content_object": Union[
BlogPostSerializer,
ProductSerializer,
EventSerializer
],
}
```
🔍 Migration Guide
Using Union Types
No migration needed! Union support is fully backward compatible:
```python
Existing code continues to work
class MySerializer(serializers.Serializer):
class Meta:
model = MyModel
relations_serializers = {
"author": AuthorSerializer, # ✅ Still works
}
New Union syntax available
class MySerializer(serializers.Serializer):
class Meta:
model = MyModel
relations_serializers = {
"content": Union[VideoSerializer, ImageSerializer], # ✅ New!
}
```
Using Absolute Import Paths
Update string references to use absolute paths for cross-module references:
```python
Before (v2.6.1) - Only local references worked
relations_serializers = {
"author": "AuthorSerializer", # Must be in same module
}
After (v2.7.0) - Absolute paths supported
relations_serializers = {
"author": "users.serializers.AuthorSerializer", # ✅ Cross-module!
}
```
String Reference Formats
Both formats are supported:
```python
relations_serializers = {
# Local reference (same module)
"field1": "LocalSerializer",
# Absolute import path (any module)
"field2": "myapp.serializers.RemoteSerializer",
# Union with mixed formats
"field3": Union["LocalSerializer", "myapp.other.RemoteSerializer"],
}
```
🎯 When to Use Union Types
- Polymorphic Relations: Generic foreign keys, Django ContentType relations
- Flexible APIs: Different response formats based on runtime type
- Gradual Migrations: Transitioning between serializer implementations
- Multi-Tenant Systems: Different serialization per tenant
- Dynamic Content: CMS systems with multiple content types
- Activity Feeds: Mixed content types in single endpoint
📊 Performance Notes
- Lazy Resolution: Union members resolved only when schemas generated (no startup overhead)
- Schema Caching: Generated schemas can be cached for better performance
- Memory Efficient: Only generates schemas for types actually used
- Import Optimization: Absolute paths only import modules when needed
⚠️ Important Notes
- String References: Resolve within same module by default; use absolute paths for cross-module
- Union Schema Generation: Creates union of all possible schemas from union members
- Backward Compatibility: All existing code continues to work without changes
- Python Version: Requires Python 3.10+ (Union syntax compatibility)
- Type Validation: Union types provide type hints but runtime validation depends on your model logic
🙏 Acknowledgments
This release focuses on:
- Enhanced flexibility for polymorphic relationships
- Better support for complex project architectures
- Improved developer experience with cross-module references
- Python 3.10+ compatibility and modern typing features
🔗 Links
📦 Quick Start with Union Types
```python
from typing import Union
from ninja_aio.models import serializers
Step 1: Define your serializers
class VideoSerializer(serializers.Serializer):
class Meta:
model = Video
schema_out = serializers.SchemaModelConfig(fields=["id", "title", "url"])
class ImageSerializer(serializers.Serializer):
class Meta:
model = Image
schema_out = serializers.SchemaModelConfig(fields=["id", "title", "url"])
Step 2: Use Union in relations_serializers
class CommentSerializer(serializers.Serializer):
class Meta:
model = Comment
schema_out = serializers.SchemaModelConfig(
fields=["id", "text", "content_object"]
)
relations_serializers = {
"content_object": Union[VideoSerializer, ImageSerializer],
}
Step 3: Use with APIViewSet (automatic!)
@api.viewset(model=Comment)
class CommentViewSet(APIViewSet):
serializer_class = CommentSerializer
# Union types work automatically!
```
Version History
For older versions, please refer to the GitHub releases page. |
v2.6.1 diff | 2026-01-12 |  Release notesRelease Notes
[v2.6.1] - 2026-01-12
✨ Added
🛠 Changed
- Schema Generation Lifecycle:
- Removed eager schema generation from
Serializer.__init_subclass__()
- Schemas are now generated on-demand via explicit calls to
generate_*() methods
- Removed cached schema properties (
.schema_in, .schema_out, .schema_update, .schema_related)
-
Breaking: Must use generate_create_s(), generate_read_s(), etc. instead of accessing properties
-
Internal Refactoring:
- Replaced
match/case with if/elif statements in _generate_model_schema() for better readability
- Added configuration mapping dictionaries (
_SERIALIZER_CONFIG_MAP) to simplify lookups
- Consolidated duplicate schema resolution logic in relation handling methods
-
Improved code organization with clearer comments and structure
-
APIViewSet Integration:
- Added
serializer instance property initialized from serializer_class()
- Better integration with on-demand schema generation
📝 Documentation
⚠ Breaking Changes & Migration Notes
Removed Schema Properties
Schema properties have been removed from Serializer class. You must now explicitly call generation methods:
```python
Before (v2.5.0) - NO LONGER WORKS
ArticleSerializer.schema_in # ❌ AttributeError
ArticleSerializer.schema_out # ❌ AttributeError
ArticleSerializer.schema_update # ❌ AttributeError
ArticleSerializer.schema_related # ❌ AttributeError
After (v2.6.0) - Explicit generation required
ArticleSerializer.generate_create_s() # ✅ Returns create schema
ArticleSerializer.generate_read_s() # ✅ Returns read schema
ArticleSerializer.generate_update_s() # ✅ Returns update schema
ArticleSerializer.generate_related_s() # ✅ Returns related schema
```
Note: This change typically doesn't affect user code since these methods are called internally by APIViewSet. Only relevant if you're calling these methods directly.
🔍 Migration Guide
1. Update Schema Access in Custom Code
If you're directly accessing schema properties, update to use generation methods:
```python
Before (v2.5.0)
class ArticleViewSet(APIViewSet):
def get_schemas(self):
return {
"in": self.serializer_class.schema_in, # ❌ No longer works
"out": self.serializer_class.schema_out, # ❌ No longer works
}
After (v2.6.0)
class ArticleViewSet(APIViewSet):
def get_schemas(self):
return {
"in": self.serializer_class.generate_create_s(), # ✅ Explicit generation
"out": self.serializer_class.generate_read_s(), # ✅ Explicit generation
}
```
2. Use String References for Circular Dependencies
Take advantage of string references to simplify circular dependencies:
```python
Before (v2.5.0) - Workarounds needed for circular refs
class AuthorSerializer(serializers.Serializer):
class Meta:
model = models.Author
schema_out = serializers.SchemaModelConfig(
fields=["id", "name", "articles"]
)
# Had to carefully order class definitions or use late binding
After (v2.6.0) - String references make it easy
class AuthorSerializer(serializers.Serializer):
class Meta:
model = models.Author
schema_out = serializers.SchemaModelConfig(
fields=["id", "name", "articles"]
)
relations_serializers = {
"articles": "ArticleSerializer", # ✅ Forward reference
}
class ArticleSerializer(serializers.Serializer):
class Meta:
model = models.Article
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "author"]
)
relations_serializers = {
"author": "AuthorSerializer", # ✅ Circular reference works!
}
```
String Reference Requirements:
- Must be the exact class name as a string
- Serializer must be defined in the same module
- Resolution happens lazily when generate_*() is called
- Both forward and circular references are supported
3. Schema Generation Best Practices
In APIViewSet (no changes needed):
```python
APIViewSet handles schema generation automatically
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
serializer_class = ArticleSerializer
# No changes needed - works automatically
```
In Custom Code (call generate methods):
```python
Explicit schema generation when needed
from ninja import Router
router = Router()
@router.post("/articles/", response=ArticleSerializer.generate_read_s())
async def create_article(request, payload: ArticleSerializer.generate_create_s()):
serializer = ArticleSerializer()
instance = await serializer.create(payload.model_dump())
return await serializer.model_dump(instance)
```
Caching Schemas (if needed for performance):
```python
Cache schemas at module level if generating repeatedly
ARTICLE_CREATE_SCHEMA = ArticleSerializer.generate_create_s()
ARTICLE_READ_SCHEMA = ArticleSerializer.generate_read_s()
@router.post("/articles/", response=ARTICLE_READ_SCHEMA)
async def create_article(payload: ARTICLE_CREATE_SCHEMA):
# Use cached schemas
pass
```
4. Complete Migration Example
Here's a complete before/after example:
```python
Before (v2.5.0)
from ninja_aio.models import serializers
from ninja_aio import NinjaAIO
from ninja_aio.views import APIViewSet
class AuthorSerializer(serializers.Serializer):
class Meta:
model = models.Author
schema_out = serializers.SchemaModelConfig(
fields=["id", "name"]
)
class ArticleSerializer(serializers.Serializer):
class Meta:
model = models.Article
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "author"]
)
relations_serializers = {
"author": AuthorSerializer, # Required class ordering
}
Access schemas (no longer works)
create_schema = ArticleSerializer.schema_in # ❌
read_schema = ArticleSerializer.schema_out # ❌
After (v2.6.0)
from ninja_aio.models import serializers
from ninja_aio import NinjaAIO
from ninja_aio.views import APIViewSet
class AuthorSerializer(serializers.Serializer):
class Meta:
model = models.Author
schema_out = serializers.SchemaModelConfig(
fields=["id", "name", "articles"]
)
relations_serializers = {
"articles": "ArticleSerializer", # ✅ String reference
}
class ArticleSerializer(serializers.Serializer):
class Meta:
model = models.Article
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "author"]
)
relations_serializers = {
"author": "AuthorSerializer", # ✅ Circular reference!
}
Explicit schema generation
create_schema = ArticleSerializer.generate_create_s() # ✅
read_schema = ArticleSerializer.generate_read_s() # ✅
Using with APIViewSet (no changes needed)
api = NinjaAIO()
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
serializer_class = ArticleSerializer
# Automatically works with on-demand generation
```
🐛 Bug Fixes
- Fixed lazy resolution issues with forward and circular serializer references
- Improved error messages when string references cannot be resolved
- Corrected
model_dump() and models_dump() to use explicit schema generation
- Fixed potential issues with
model_util vs util attribute naming
🚀 Performance Improvements
- Reduced Initialization Overhead: Schemas only generated when actually needed
- Memory Efficiency: Unused schemas are never created
- Lazy Resolution: String references resolved on-demand, reducing startup time
- Faster Imports: Removed eager schema generation from module import time
📊 Code Quality Improvements
- Reduced Code Duplication:
- Extracted common relation resolution logic into
_resolve_relation_schema()
- Consolidated duplicate code in
_build_schema_reverse_rel() and _build_schema_forward_rel()
-
Reduced relation handling code by ~40 lines
-
Improved Maintainability:
- Replaced
match/case with clearer if/elif statements
- Added configuration mapping dictionaries for cleaner lookups
- Better code organization with descriptive comments
-
Consistent use of any() for empty checks
-
Better Readability:
- Flattened nesting in
_generate_model_schema()
- Clearer separation between special cases and standard logic
- Improved docstrings and parameter descriptions
- More descriptive variable names
🙏 Acknowledgments
This release focuses on:
- Architectural improvements for forward/circular dependency support
- Cleaner, more maintainable internal code structure
- On-demand resource generation for better performance
- Enhanced developer experience with string references
📝 Notes
-
Schema Generation: While properties were removed, APIViewSet automatically calls generate_*() methods, so most applications won't need code changes
-
Performance: On-demand generation typically improves startup time. If you need schemas multiple times, consider caching them at module level
-
String References: Only resolve within the same module. For cross-module references, use direct class imports
-
Backward Compatibility: Code using APIViewSet continues to work without changes. Direct schema property access will raise AttributeError
-
Internal Refactoring: This release includes significant internal refactoring for code quality without changing public APIs (except removal of schema properties)
🔗 Links
📦 Upgrade Checklist
Use this checklist when upgrading from v2.5.0 to v2.6.0:
- [ ] Search codebase for
.schema_in, .schema_out, .schema_update, .schema_related property access
- [ ] Replace with
generate_create_s(), generate_read_s(), generate_update_s(), generate_related_s() calls
- [ ] Update any circular serializer references to use string references
- [ ] Review custom
create() and update() method implementations (if any)
- [ ] Test all CRUD endpoints to ensure proper functionality
- [ ] Update any schema caching logic to use explicit generation
- [ ] Review and update API documentation if it references old property access
|
v2.5.0 diff | 2026-01-12 |  Release notesRelease Notes
[v2.5.0] - 2026-01-12
✨ Added
🛠 Changed
- APIViewSet:
- CRUD views now automatically decorated with
@aatomic for transactional integrity
-
Enhanced get_schemas() method for unified schema generation from both ModelSerializer and Serializer
-
ModelUtil:
-
Query optimization merging logic improved to respect both model and serializer configurations
-
Serializer Lifecycle Hooks:
- All Serializer hooks now consistently receive
instance parameter
- Inline execution of before/after save hooks integrated with
@aatomic decorator
- Hook signatures standardized:
custom_actions(payload, instance), post_create(instance), before_save(instance), etc.
📝 Documentation
⚠ Breaking Changes & Migration Notes
Transaction Behavior (New Default)
Create, update, and delete operations are now automatically wrapped in database transactions:
```python
Automatic transaction wrapping (new in v2.5.0)
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
pass # create/update/delete wrapped in @aatomic
```
Migration: If you were manually managing transactions in lifecycle hooks, you may encounter nested transaction issues. Remove manual transaction management:
```python
Before (v2.4.0)
async def post_create(self, instance):
async with transaction.atomic(): # Remove this
await AuditLog.objects.acreate(...)
After (v2.5.0)
async def post_create(self, instance):
# Transaction already managed by @aatomic
await AuditLog.objects.acreate(...)
```
Serializer Hook Signatures
Added Serializer hooks signatures, they are standardized to always receive instance:
```python
v2.5.0 - Standardized (always receive instance)
class MySerializer(Serializer):
async def custom_actions(self, payload, instance):
# instance parameter required
pass
async def post_create(self, instance):
# instance parameter required
pass
def before_save(self, instance):
# instance parameter required
pass
def after_save(self, instance):
# instance parameter required
pass
def on_delete(self, instance):
# instance parameter required
pass
```
🔍 Migration Guide
1. Updating Serializer Lifecycle Hooks
If you're using Serializer (Meta-driven pattern), update hook signatures to receive instance parameter:
```python
from ninja_aio.models import serializers
from asgiref.sync import sync_to_async
class ArticleSerializer(serializers.Serializer):
class Meta:
model = Article
schema_in = serializers.SchemaModelConfig(
fields=["title", "content", "author"],
customs=[("send_notification", bool, True)]
)
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "content", "author", "created_at"]
)
# Async hooks - receive instance parameter
async def custom_actions(self, payload, instance):
"""Execute custom logic after field assignment."""
if payload.get("send_notification"):
# Access instance fields
await send_email(
instance.author.email,
f"Article created: {instance.title}"
)
async def post_create(self, instance):
"""Hook after first save (creation only)."""
await AuditLog.objects.acreate(
action="article_created",
article_id=instance.id,
user_id=instance.author_id
)
# Sync hooks - also receive instance parameter
def before_save(self, instance):
"""Modify instance before save."""
from django.utils.text import slugify
if not instance.slug:
instance.slug = slugify(instance.title)
def after_save(self, instance):
"""Execute logic after save."""
# Clear cache
from django.core.cache import cache
cache.delete(f"article:{instance.id}")
def on_create_before_save(self, instance):
"""Before save, creation only."""
instance.view_count = 0
def on_create_after_save(self, instance):
"""After save, creation only."""
# Log creation
import logging
logger = logging.getLogger(__name__)
logger.info(f"Article {instance.id} created")
def on_delete(self, instance):
"""After deletion."""
import logging
logger = logging.getLogger(__name__)
logger.info(f"Article {instance.id} deleted")
```
Key Points:
- All hooks receive instance as a parameter
- Async hooks: custom_actions(payload, instance), post_create(instance)
- Sync hooks: before_save(instance), after_save(instance), on_delete(instance)
- Creation-specific hooks: on_create_before_save(instance), on_create_after_save(instance)
2. Configuring QuerySet Optimization
Add QuerySet configuration to your Serializer or ModelSerializer for automatic query optimization:
```python
from ninja_aio.models import serializers
from ninja_aio.schemas.helpers import ModelQuerySetSchema, ModelQuerySetExtraSchema
class ArticleSerializer(serializers.Serializer):
class Meta:
model = Article
schema_out = serializers.SchemaModelConfig(
fields=["id", "title", "content", "author", "category", "tags"]
)
relations_serializers = {
"author": AuthorSerializer,
"category": CategorySerializer,
"tags": TagSerializer,
}
class QuerySet:
# Applied to list and retrieve operations
read = ModelQuerySetSchema(
select_related=["author", "category"],
prefetch_related=["tags"],
)
# Applied when queryset_request hook is called
queryset_request = ModelQuerySetSchema(
select_related=["author__profile"],
prefetch_related=["comments", "comments__author"],
)
# Named scopes for specific use cases
extras = [
ModelQuerySetExtraSchema(
scope="detail_view",
select_related=["author", "author__profile", "category"],
prefetch_related=["tags", "comments", "comments__author"],
),
ModelQuerySetExtraSchema(
scope="list_view",
select_related=["author", "category"],
prefetch_related=["tags"],
),
]
@classmethod
async def queryset_request(cls, request):
"""
Optional: Customize queryset based on request.
Automatically enhanced with QuerySet.queryset_request optimizations.
"""
qs = cls._meta.model.objects.all()
# Filter based on user permissions
if not request.user.is_staff:
qs = qs.filter(is_published=True)
# Add request-specific filters
if request.GET.get("featured"):
qs = qs.filter(is_featured=True)
return qs
```
For ModelSerializer:
```python
from ninja_aio.models import ModelSerializer
from ninja_aio.schemas.helpers import ModelQuerySetSchema
from django.db import models
class Article(ModelSerializer):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
category = models.ForeignKey(Category, on_delete=models.SET_NULL, null=True)
tags = models.ManyToManyField(Tag, related_name="articles")
class ReadSerializer:
fields = ["id", "title", "content", "author", "category", "tags"]
class QuerySet:
read = ModelQuerySetSchema(
select_related=["author", "category"],
prefetch_related=["tags"],
)
queryset_request = ModelQuerySetSchema(
select_related=["author__profile"],
prefetch_related=["comments"],
)
@classmethod
async def queryset_request(cls, request):
"""Optimize queries for this model."""
return cls.objects.select_related("author", "category")
```
How QuerySet Configuration Works:
read: Applied automatically to list and retrieve operations when is_for_read=True
queryset_request: Applied when with_qs_request=True (default) in get_objects() or get_object()
extras: Named scopes accessible via QueryUtil.SCOPES for custom scenarios
- Merging: Optimizations from multiple sources are merged (no duplicates)
Benefits:
- Eliminates N+1 queries automatically
- Centralizes query optimization configuration
- Works with both ModelSerializer and Serializer patterns
- Optimizations apply to all CRUD operations
3. Customizing Model Display Names
Override verbose names without modifying models:
python
@api.viewset(model=Article)
class ArticleViewSet(APIViewSet):
model_verbose_name = "Blog Post"
model_verbose_name_plural = "Blog Posts"
# OpenAPI will use "Blog Post" instead of "Article"
🐛 Bug Fixes
- Fixed query optimization merging when both model and serializer provide hints
- Corrected
read_s() behavior when both instance and query_data provided (now raises clear error)
- Improved error messages for missing primary key in
get_object()
- Fixed duplicate route registration with
@unique_view decorator
🚀 Performance Improvements
- Transaction management with
@aatomic reduces database round-trips
- Query optimization merging eliminates redundant select_related/prefetch_related
with_qs_request parameter allows skipping hook when not needed
🙏 Acknowledgments
This release focuses on:
- Enhanced transaction safety
- Flexible query control
- Per-operation customization
- Comprehensive documentation
📝 Notes
-
Backward Compatibility: All v2.4.0 code continues to work. New parameters have sensible defaults.
-
Transaction Overhead: The @aatomic decorator adds minimal overhead. If you need non-transactional operations, override the view methods directly.
-
Query Parameters: with_qs_request defaults to True to maintain v2.4.0 behavior. Set to False to skip the queryset_request hook.
-
Serializer Hooks: If migrating from v2.4.0 Serializer usage, ensure all hooks accept the instance parameter.
🔗 Links
|
v2.4.0 diff | 2026-01-09 |  Release notesRelease Notes
[v2.4.0] - 2026-01-09
✨ Added
- Serializer (Meta-driven):
- New Serializer for vanilla Django models configured via nested Meta (no ModelSerializer inheritance).
- Dynamic schema generation helpers: generate_read_s, generate_create_s, generate_update_s, generate_related_s.
- Relation handling via relations_serializers for forward and reverse relations.
- APIViewSet:
- serializer_class to auto-generate missing schemas for non-ModelSerializer models and drive queryset_request.
- ModelUtil:
- Accepts serializer_class to build querysets using Serializer.queryset_request when provided.
- Docs/README:
- New docs page: api/models/serializers.md with usage and examples.
- README sections/examples for Meta-driven Serializer.
- MkDocs nav entry for Serializer.
- Tests:
- Serializer tests (forward and reverse relations).
- Viewset tests using serializer_class-backed endpoints.
🛠 Changed
- Package layout:
- ninja_aio/models/init.py now exports ModelUtil and ModelSerializer.
- models.py refactored to models/utils.py; ModelSerializer moved to models/serializers.py.
- API internals:
- APIViewSet.compute_schema generates schemas from serializer_class for vanilla models; retains model-backed generation for ModelSerializer.
- ModelUtil.get_queryset_request uses serializer_class when provided.
- Docs:
- Index formatting tweaks; added links and examples for Serializer docs.
📝 Documentation
- Serializer (Meta-driven):
- Configure via Meta: model, schema_in, schema_out, schema_update, relations_serializers.
- Examples for FK and reverse relations; customs and optionals.
- APIViewSet:
- Using serializer_class to auto-generate schemas and plug into queryset_request.
- README:
- Quick example attaching Serializer to APIViewSet.
- MkDocs:
- Added nav entry under Models: Serializer (Meta-driven).
⚠ Notes / Potential Impact
- Relation serializers:
- Reverse relations on vanilla models need relations_serializers entries to include nested schemas; otherwise skipped unless the related model is a ModelSerializer.
- A UserWarning is emitted when a reverse relation is listed without a mapping; suppressed in tests via NINJA_AIO_TESTING=True.
- Refactor:
- Imports may need updates due to ModelUtil relocation and new serializers module.
🔍 Migration / Action
- Define a Meta-driven Serializer for existing Django models and attach it to APIViewSet via serializer_class.
- Provide relations_serializers for reverse relations to include nested schemas on read.
- Update imports:
- from ninja_aio.models import ModelUtil, ModelSerializer
- from ninja_aio.models.serializers import Serializer, SchemaModelConfig
- If relying on queryset_request with vanilla models, implement Serializer.queryset_request; APIViewSet and ModelUtil will use it automatically.
|
v2.3.2 diff | 2026-01-08 |  Release notes[v2.3.2] - 2026-01-08
✨ Added
- Support Url pydantic field serialization
- Support for django ninja until 1.6
|
v2.3.1 diff | 2026-01-07 |  Release notes[v2.3.1] - 2026-01-07
✨ Added
- CI/Docs Deployment:
- GitHub workflow updated to recognize and manage version "2.3" for docs deploy/delete.
- Documentation/README:
- Added link to the external example repository: https://github.com/caspel26/ninja-aio-blog-example.
🛠 Changed
- README/Docs:
- Switched examples to decorator-first style with
@api.viewset(Model) and in-class method decorators (e.g., @api_post).
- Removed explicit
api = api and model = ... from examples where @api.viewset(...) is used; emphasized automatic registration.
- Cleaned and reformatted examples and quick links table; clarified usage in the index page and decorators page.
- Packaging:
- Python version spec adjusted from
>=3.10, <=3.14 to >=3.10, <3.15 in pyproject metadata.
🗑 Removed
- In-repo example apps:
- Deleted
examples/ex_1 and examples/ex_2 (models, views, urls, and auth). Examples are now hosted in the external repository linked in the README.
📝 Documentation
- Index and README updated to prefer
@api.viewset(Model) and decorator-based custom endpoints.
- Decorators page (
docs/api/views/decorators.md) revised to reflect decorator-first usage.
- Added references to the external example repository for complete, runnable samples.
|
v2.3.0 diff | 2026-01-04 |  Release notesRelease Notes
[v2.3.0] - 2026-01-04
✨ Added
- Decorators:
- New operation decorators for class methods:
api_get, api_post, api_put, api_patch, api_delete, api_options, api_head (import from ninja_aio.decorators).
- Utilities:
decorate_view, aatomic, unique_view (now under ninja_aio.decorators).
- Factory-backed decorators ensure clean OpenAPI signatures (exclude
self) and support extra decorators like pagination.
- Views:
- APIView/APIViewSet auto-register decorated methods via lazy binding; no manual
add_views_to_route() when using @api.view / @api.viewset.
- APIViewSet supports global trailing slash setting via
settings.NINJA_AIO_APPEND_SLASH (default True).
- M2M:
M2MRelationSchema.append_slash to control trailing slash on the GET relation route.
- Relation path normalization for consistent URLs whether
path includes leading slash or not.
- Tests/Examples:
- Added decorator-based examples and tests for custom endpoints on views and viewsets.
🛠 Changed
- README/Docs:
- Prefer
@api.viewset(Model) with decorator-based endpoints; legacy views() remains supported.
- Clarified trailing slash behavior for CRUD retrieve paths and M2M relations.
- Decorator-first examples across APIView and APIViewSet pages; cleaner OpenAPI notes.
- API internals:
- Base API class now binds decorator-registered methods via
_add_views(); APIView/APIViewSet call super()._add_views() before legacy views().
- APIViewSet path generation respects
NINJA_AIO_APPEND_SLASH for retrieve path (/{pk}/ vs /{pk}).
- Exceptions/Helpers:
- Added docstrings for clearer behavior in exceptions, query helpers, and schemas.
📝 Documentation
- APIView/APIViewSet:
- Decorator-first usage with examples; automatic lazy registration; signature preservation.
- Decorators:
- Using operation decorators with extra decorators (e.g.,
paginate(PageNumberPagination), unique_view(name)).
- ViewSet relations:
- Per-relation
append_slash; path normalization rules; trailing slash settings.
- README:
- Simplified setup:
@api.viewset(Model) and decorator-based custom endpoints.
⚠ Notes / Potential Impact
- Trailing slash:
- Global
NINJA_AIO_APPEND_SLASH defaults to True. Disable to remove trailing slash from retrieve paths.
- M2M GET relation endpoints default to no trailing slash; enable per relation with
append_slash=True.
- Registration:
- When using
@api.view / @api.viewset, endpoints defined via decorators are mounted automatically; avoid redundant manual registration.
- OpenAPI:
- Decorator-backed handlers exclude
self and preserve type hints for cleaner specs.
🔍 Migration / Action
- Adopt decorators for extra endpoints:
- APIView: annotate the class with
@api.view(...), then decorate methods with @api_get("/path", ...).
- APIViewSet: annotate with
@api.viewset(Model, ...), then use @api_get("/path", ...), @api_post(...), etc.
- Trailing slash configuration:
- Set
NINJA_AIO_APPEND_SLASH=False in Django settings to drop trailing slash on retrieve paths globally.
- For M2M GET relations, use
M2MRelationSchema(append_slash=True/False) to control trailing slash.
- Legacy support:
views() continues to work; prefer decorators for clearer code and better OpenAPI.
- Docs/examples:
- Update references to new decorator modules and follow decorator-first examples.
|
v2.2.0 diff | 2026-01-03 |  Release notesRelease Notes
[2.2.0] - 2026-01-03
✨ Added
- API:
- Decorators:
NinjaAIO.view(prefix, tags) and NinjaAIO.viewset(model, prefix, tags) for automatic registration.
- Base API class: shared attributes for APIView and APIViewSet (api, router_tags, api_route_path).
- Views:
- APIView: supports constructor args
(api, prefix, tags) with router_tags and standardized error_codes.
- APIViewSet: constructor
(api, model, prefix, tags); infers base path from model when not provided; router_tags support.
- Auth:
- JwtKeys type expands to include
jwk.OctKey (HMAC).
validate_key accepts jwk.OctKey.
encode_jwt/decode_jwt type hints generalized to JwtKeys.
- Tests:
- Added decorator-based tests for APIView and APIViewSet (ModelSerializer and plain Django model).
- Updated ManyToMany tests to construct viewset with
api argument.
- Docs:
- APIView and APIViewSet docs: “Recommended” decorator-based examples.
- Mixins doc moved to
docs/api/views/mixins.md.
- Index updated with modern ModelSchema-based examples and async ORM usage.
🛠 Changed
- Version:
- Bumped to 2.2.0 in
ninja_aio/__init__.py.
- Error codes:
- Standardized to {400, 401, 404}; removed 428 references in code and docs.
- Docs:
docs/api/authentication.md: use list-based auth [JWTAuth(), APIKeyAuth()] instead of bitwise OR.
docs/api/views/api_view.md and api_view_set.md: emphasize decorator usage; cleaner examples; notes updated.
- MkDocs nav: Mixins path updated to
api/views/mixins.md.
- API internals:
- APIView/APIViewSet refactored to share base attributes, constructor supports
api, prefix, and tags.
- Docs workflow:
mike set-default --push latest when MAKE_LATEST is true.
- Packaging:
- Python requirement set to
>=3.10, <=3.14 in pyproject.toml.
📝 Documentation
- Updated:
- Authentication: list-based auth configuration and clarified behavior.
- APIView/APIViewSet: decorator-first usage, async compatibility, and standard error codes.
- Index: ModelSchema In/Out patterns with async ORM examples.
- Moved:
- Mixins doc to
api/views/mixins.md; MkDocs navigation adjusted.
⚠ Notes / Potential Impact
- Error handling:
- 428 code removed; rely on {400, 401, 404}.
- Auth configuration:
- Use lists for multiple auth methods; bitwise OR in docs deprecated.
- Docs deployment:
- Default alias set to “latest” on deploy when MAKE_LATEST=true.
- Python compatibility:
- Upper bound set to 3.14.
🔍 Migration / Action
- Adopt decorators:
- APIView:
@api.view(prefix="/path", tags=[...])
- APIViewSet:
@api.viewset(model=MyModel, prefix="/path", tags=[...])
- Update auth configuration:
- HMAC keys supported via
jwk.OctKey where applicable.
- Error codes:
- Remove references/handlers for 428; standardize to {400, 401, 404}.
- Docs links:
- Update references to Mixins at
api/views/mixins.md.
- Runtime:
- Ensure Python version is <= 3.14 per
pyproject.toml.
|
v2.1.0 diff | 2026-01-02 |  Release notesRelease Notes
[2.1.0] - 2026-01-01
✨ Added
- Views:
- ReadOnlyViewSet: list and retrieve-only endpoints.
- WriteOnlyViewSet: create, update, and delete-only endpoints.
- Exported via
ninja_aio.views.__init__ for cleaner imports.
- Mixins:
- New filtering mixins under
ninja_aio/views/mixins.py: IcontainsFilterViewSetMixin, BooleanFilterViewSetMixin, NumericFilterViewSetMixin, DateFilterViewSetMixin, and specialized Greater/Less variants.
- Auth docs:
- New
docs/auth.md with JWT helpers and AsyncJwtBearer usage and configuration.
- Tests:
- Extended test model with
age, active, and active_from fields.
- Added viewset tests for mixins (icontains, boolean, numeric, date comparisons).
- Added auth tests for JWT encode/decode and AsyncJwtBearer claim validation.
- Docs navigation:
- Added Mixins page and JWT & AsyncJwtBearer page to MkDocs nav.
- MkDocs
mike config sets default: latest.
🛠 Changed
- Docs workflow (
.github/workflows/docs.yml):
- Safer deletion: requires explicit
delete_version choice and delete_confirm, protects latest, stable, and current default.
make_latest default set to false.
- Coverage workflow:
- Bump
codecov/codecov-action from v5.5.1 to v5.5.2.
- API helpers:
- Use
decorate_view to compose unique_view and paginate for related GET endpoints.
- APIViewSet (imports and behavior):
- Module reorganized to
ninja_aio/views/api.py with updated internal imports.
get_schemas: generates schemas only if missing when model is a ModelSerializerMeta, else returns explicitly set schemas.
- Hook docs clarified to allow sync or async handlers for query params.
- Auth:
encode_jwt: header now includes kid only when present (conditional merge).
- Docs:
docs/api/views/api_view_set.md updated to document ReadOnlyViewSet and WriteOnlyViewSet.
docs/mixins.md aligned with implemented mixins and examples.
📝 Documentation
- New:
- JWT & AsyncJwtBearer guide with examples for settings and direct JWK usage.
- Updated:
- Mixins reference to match implemented classes and recommended query param types.
- APIViewSet docs extended with ReadOnly/WriteOnly usage.
⚠ Notes / Potential Impact
- Docs deployment:
- Deletion requires explicit confirmation and cannot remove protected aliases or current default.
- Mixins:
- Date filters expect values that implement
isoformat; prefer Pydantic date/datetime in query params.
🔍 Migration / Action
- Update imports:
from ninja_aio.views import APIViewSet, ReadOnlyViewSet, WriteOnlyViewSet
from ninja_aio.views import mixins for filter mixins.
- For related list endpoints using custom decorators, consider adopting
decorate_view for consistent composition.
- If using JWT:
- Optionally set
JWT_PRIVATE_KEY, JWT_PUBLIC_KEY, JWT_ISSUER, JWT_AUDIENCE in Django settings.
- Validate claims via
AsyncJwtBearer.claims registry and verify allowed algorithms.
- Review docs workflow inputs before deleting versions; use
delete_confirm: true.
|
v2.0.0 diff | 2025-12-16 |  Release notesRelease Notes
[2.0.0] - 2025-12-16
✨ Added
- QueryUtil and query scopes:
- New
QueryUtil with SCOPES (READ, QUERYSET_REQUEST, plus extras) and apply_queryset_optimizations.
ModelSerializer.query_util bound per model via __init_subclass__.
ModelSerializer.QuerySet supports read, queryset_request, extras.
- Query schemas:
QuerySchema, ObjectQuerySchema, ObjectsQuerySchema, ModelQuerySetSchema, ModelQuerySetExtraSchema, QueryUtilBaseScopesSchema.
- ModelUtil:
get_objects(...): optimized queryset fetching with filters and select/prefetch hints.
get_object(...): single-object retrieval by pk or getters with optimizations.
read_s(...) and list_read_s(...): serialize instances or auto-fetch via query schemas.
- Relation discovery helpers:
get_select_relateds(), get_reverse_relations().
- PK type resolution:
pk_field_type with helpful error for unknown field types.
- ManyToManyAPI:
- GET related endpoints return
{items: [...], count: N}.
- Relation filter handlers accept sync or async functions.
- Related items use
ModelUtil.list_read_s for serialization.
- Per-relation single-object resolution handler for POST:
<related_name>_query_handler(...).
- Schemas modularization:
- New modules:
ninja_aio/schemas/api.py, ninja_aio/schemas/generics.py, and exported names under ninja_aio/schemas/__init__.py.
- Decorators:
decorate_view utility to compose multiple decorators (sync/async), skipping None.
APIViewSet.extra_decorators via DecoratorsSchema for per-operation decoration.
- Renderer:
- ORJSON renderer option via
settings.NINJA_AIO_ORJSON_RENDERER_OPTION (bitmask, supports |).
🛠 Changed
- APIViewSet:
- List uses
ModelUtil.get_objects and list_read_s with read optimizations; filter hooks retained.
- Retrieve uses
read_s with QuerySchema(getters={"pk": ...}).
- Path PK schema type inferred from model PK via
ModelUtil.pk_field_type.
- Default read query data comes from
ModelSerializer.QuerySet.read via query_util.
- Built-ins and custom decorators composed with
decorate_view (e.g., paginate, unique_view, extras).
- ModelSerializer:
- Binds
util = ModelUtil(cls) and query_util = QueryUtil(cls) to subclasses.
queryset_request applies configured optimizations from QuerySet.queryset_request.
- ModelUtil internals:
- Unified
_apply_query_optimizations merges explicit select/prefetch with auto-discovered relations when is_for_read=True.
- Serialization paths standardized through internal helpers;
read_s/list_read_s accept schema first.
- Auth:
AsyncJwtBearer.verify_token simplifies error handling; drops explicit AuthError.
- Imports:
ManyToManyAPI consumed from ninja_aio/helpers/api.py.
- Runtime requirements:
- Upper bounds added:
django-ninja <=1.5.1, joserfc <=1.4.1, orjson <=3.11.5.
- Docs and site:
- MkDocs/mike integration for versioned docs; new workflow
docs.yml.
🔴 Breaking Changes
- Path PK schema type:
- PK type is inferred from the model PK. Code relying on
int | str in path schemas may need adjustments.
- ManyToMany GET response shape:
- Response changed from a plain list to
{items: [...], count: N}. Clients must adapt parsing.
- Import paths:
- Schema helpers moved under
ninja_aio/schemas/helpers.py and re-exported by ninja_aio/schemas/__init__.py.
ManyToManyAPI import is now from ninja_aio.helpers.api import ManyToManyAPI.
- ModelUtil read API:
read_s and list_read_s signatures accept schema first and support instance or query_data. Code passing (request, obj, schema) must switch to (schema, request, instance=obj).
📝 Documentation
- Updated:
- ModelUtil reference: QuerySet config, QueryUtil, query schemas,
get_objects, get_object, read_s, list_read_s.
- APIViewSet: list/retrieve flow, PK type inference, M2M GET envelope, async/sync filter handlers, operation decorators.
- Tutorial (model): QuerySet config and
query_util examples; fetch/serialize using query schemas.
- Index: overview of query optimizations and schemas.
- ORJSON renderer: configuration guide.
⚠ Notes / Potential Impact
| Area | Observation | Impact |
| ------------------- | ----------------------------------------------------------------- | --------------------------------------------------------------------- |
| Query optimizations | is_for_read=True merges explicit and auto-discovered relations. | More joins/prefetches; re-check performance for heavy endpoints. |
| Requirements caps | Upper bounds added for core deps. | Ensure compatible versions in your environment. |
| Decorator order | decorate_view applies standard Python stacking order. | Verify nesting with paginate, unique_view, and custom decorators. |
🔍 Migration / Action
- Update imports:
from ninja_aio.schemas.helpers import QuerySchema, ObjectQuerySchema, ObjectsQuerySchema, ModelQuerySetSchema, ModelQuerySetExtraSchema
from ninja_aio.helpers.api import ManyToManyAPI
- Adjust M2M GET consumers to handle
{items, count}.
- Update
read_s/list_read_s calls to new parameter order.
- Verify path PK handling in custom routes that relied on a generic PK type.
- Review
QuerySet.read / QuerySet.queryset_request for desired select/prefetch behavior.
- Optionally configure ORJSON via
NINJA_AIO_ORJSON_RENDERER_OPTION.
|
v2.0.0-rc1 diff | 2025-12-07 |  Release notesRelease Notes
[2.0.0-rc1] - 2025-12-07
✨ Added
- QueryUtil and query scopes:
- New
QueryUtil with SCOPES (READ, QUERYSET_REQUEST, plus extras) and apply_queryset_optimizations.
ModelSerializer.query_util bound per model via __init_subclass__.
ModelSerializer.QuerySet supports read, queryset_request, extras.
- Query schemas:
QuerySchema, ObjectQuerySchema, ObjectsQuerySchema, ModelQuerySetSchema, ModelQuerySetExtraSchema, QueryUtilBaseScopesSchema.
- ModelUtil:
get_objects(...): optimized queryset fetching with filters and select/prefetch hints.
get_object(...): single-object retrieval by pk or getters with optimizations.
read_s(...) and list_read_s(...): serialize instances or auto-fetch via query schemas.
- Relation discovery helpers:
get_select_relateds(), get_reverse_relations().
- PK type resolution:
pk_field_type with helpful error for unknown field types.
- ManyToManyAPI:
- GET related endpoints return
{items: [...], count: N}.
- Relation filter handlers accept sync or async functions.
- Related items use
ModelUtil.list_read_s for serialization.
- Schemas modularization:
- New modules:
ninja_aio/schemas/api.py, ninja_aio/schemas/generics.py, and exported names under ninja_aio/schemas/__init__.py.
- Decorators:
- Minor hardening and docs for
aatomic and unique_view.
🛠 Changed
- APIViewSet:
- List view uses
ModelUtil.get_objects and list_read_s with read optimizations; filter hooks retained.
- Retrieve view uses
read_s with QuerySchema(getters={"pk": ...}).
- Path PK schema type inferred from model PK via
ModelUtil.pk_field_type.
- Default read query data comes from
ModelSerializer.QuerySet.read via query_util.
- ModelSerializer:
- Binds
util = ModelUtil(cls) and query_util = QueryUtil(cls) to subclasses.
queryset_request applies configured optimizations from QuerySet.queryset_request.
- ModelUtil internals:
- Unified
_apply_query_optimizations merges explicit select/prefetch with auto-discovered relations when is_for_read=True.
- Serialization paths standardized through internal bump helpers.
- Auth:
AsyncJwtBearer.verify_token simplifies error handling; drops explicit AuthError.
- Imports:
ManyToManyAPI consumed from ninja_aio/helpers/api.py.
- Runtime requirements:
- Pinned upper bounds for
django-ninja, joserfc, orjson.
📝 Documentation
- Updated:
- ModelUtil reference: QuerySet config, QueryUtil, query schemas,
get_objects, get_object, read_s, list_read_s.
- APIViewSet: list/retrieve flow, PK type inference, M2M GET envelope, async/sync filter handlers.
- Tutorial (model): QuerySet config and
query_util examples; fetch/serialize using query schemas.
- Index: overview of query optimizations and schemas.
🔴 Breaking Changes
- Path PK schema type:
- PK type is now inferred from the model PK. Code relying on
int | str in path schemas may need adjustments.
- ManyToMany GET response shape:
- Response changed from a plain list to an envelope
{items: [...], count: N}. Clients must adapt parsing.
- Import paths:
- Schema helpers moved under
ninja_aio/schemas/helpers.py and re-exported by ninja_aio/schemas/__init__.py.
ManyToManyAPI import is now from ninja_aio.helpers.api import ManyToManyAPI.
- ModelUtil read API:
read_s and list_read_s signatures accept schema first and support instance or query_data. Code passing (request, obj, schema) must switch to (schema, request, instance=obj).
⚠ Notes / Potential Impact
| Area | Observation | Impact |
| ---- | ----------- | ------ |
| Query optimizations | is_for_read=True merges explicit and auto-discovered relations. | More joins/prefetches; re-check performance for heavy endpoints. |
| Requirements caps | Upper bounds added for core deps. | Ensure compatible versions in your environment. |
🔍 Migration / Action
- Update imports:
from ninja_aio.schemas.helpers import QuerySchema, ObjectQuerySchema, ObjectsQuerySchema, ModelQuerySetSchema, ModelQuerySetExtraSchema
from ninja_aio.helpers.api import ManyToManyAPI
- Adjust M2M GET consumers to handle
{items, count}.
- Update
read_s/list_read_s calls to new parameter order.
- Verify path PK handling in custom routes that relied on a generic PK type.
- Review
QuerySet.read / QuerySet.queryset_request for desired select/prefetch behavior.
✅ Suggested Follow-Ups
- Add perf checks around list/retrieve with merged relations.
- Expand tests for:
- PK type inference in path schemas.
- Sync vs async relation filter handlers.
- QueryUtil extras scopes resolution and application.
|
v2.0.0-rc2 diff | 2025-12-12 |  Release notesRelease Notes
[2.0.0-rc2] - 2025-12-12
✨ Added
- support for django-ninja 1.5.1
- support for orjson 3.11.5
|
v2.0.0-rc3 diff | 2025-12-12 |  Release notesRelease Notes
[2.0.0-rc3] - 2025-12-12
✨ Added
- ManyToManyAPI:
- New per-relation POST object resolution handler:
<related_name>_query_handler(self, request, pk, instance) returning a queryset, resolved via .afirst().
- Endpoint registration details documented: GET without trailing slash, POST with trailing slash; operationId conventions (
get_{base}_{rel}, manage_{base}_{rel}).
🛠 Changed
- ManyToManyAPI:
- Split handlers: GET uses
<related_name>_query_params_handler(self, queryset, filters_dict); POST uses <related_name>_query_handler(...) for per-PK validation.
- Manage view uses
_collect_m2m(...) with additional context (related_name, instance) and falls back to ModelUtil.get_objects(...) when query handler is absent.
- Improved docs and docstrings for concurrency, error semantics, and request/response payloads.
- Docs:
- Refined M2M section: clarified handlers, paths, operationIds, request bodies, and concurrency.
- Minor wording and formatting improvements; standardized examples.
- Version:
- Bump to
2.0.0-rc3.
📝 Documentation
- APIViewSet M2M docs updated:
- Clarified GET filters vs POST per-PK resolution.
- Documented response semantics and per-PK success/error messages.
- Added an example showcasing both handlers.
🔴 Breaking Changes
- Handler naming:
- GET filters must use
<related_name>_query_params_handler; POST add/remove resolution must use <related_name>_query_handler. Existing single-handler implementations should be split accordingly.
- Endpoint paths:
- GET relation:
/{base}/{pk}/{rel_path} (no trailing slash).
- POST relation:
/{base}/{pk}/{rel_path}/ (trailing slash).
⚠ Notes / Potential Impact
| Area | Observation | Impact |
| ---- | ----------- | ------ |
| Validation | POST uses per-PK resolution handler when present; fallback uses ModelUtil.get_objects. | Tighten access control and scoping per relation. |
| Concurrency | aadd and aremove run concurrently via asyncio.gather. | Faster bulk mutations; ensure thread-safety of custom logic. |
🔍 Migration / Action
- Implement per-relation handlers:
- GET filters:
def|async def <rel>_query_params_handler(self, qs, filters: dict) -> qs.
- POST resolution:
async def <rel>_query_handler(self, request, pk, instance) -> queryset.
- Verify clients and OpenAPI consumers against the documented endpoint paths and operationIds.
- Ensure manage responses are consumed as documented (
results, errors with count and details).
✅ Suggested Follow-Ups
- Add tests for:
- Presence/absence of
<related_name>_query_handler fallback behavior.
- Sync vs async GET filter handlers.
- Per-PK error and success detail aggregation.
|
v2.0.0-rc4 diff | 2025-12-12 |  Release notesRelease Notes
[2.0.0-rc4] - 2025-12-12
✨ Added
- possibility to override router tag in APIViewSet
|
v2.0.0-rc5 diff | 2025-12-12 |  Release notesRelease Notes
[2.0.0-rc5] - 2025-12-12
🛠 Changed
- fix: update log messages to use 'pk' instead of 'id' for consistency in ManyToManyAPI
|
v2.0.0-rc6 diff | 2025-12-12 |  Release notesRelease Notes
[2.0.0-rc6] - 2025-12-12
✨ Added
- ORJSONRenderer:
- Configurable orjson option via Django settings:
NINJA_AIO_ORJSON_RENDERER_OPTION.
- New
dumps classmethod applying the configured option to all JSON responses.
🛠 Changed
- Version bump:
__version__ updated from 2.0.0-rc5 to 2.0.0-rc6.
- Rendering internals:
render now calls self.dumps(...) instead of orjson.dumps(...) directly.
📝 Documentation
- Mention
NINJA_AIO_ORJSON_RENDERER_OPTION in setup/config docs with example values (e.g., orjson.OPT_NAIVE_UTC, orjson.OPT_SERIALIZE_DATACLASS).
🔍 Migration / Action
- If you need specific JSON encoding behavior, set in Django settings:
NINJA_AIO_ORJSON_RENDERER_OPTION = orjson.OPT_NAIVE_UTC | orjson.OPT_SERIALIZE_NUMPY (example).
- No code changes required for consumers; behavior is backward compatible when the setting is absent.
⚠ Notes / Potential Impact
| Area | Observation | Impact |
| ---- | ----------- | ------ |
| JSON options | Renderer honors global orjson options. | Unified behavior across endpoints; verify compatibility with clients. |
|
v2.0.0-rc7 diff | 2025-12-16 |  Release notesRelease Notes
[2.0.0-rc7] - 2025-12-16
✨ Added
- Decorators:
decorate_view: compose multiple decorators (sync/async), preserves normal stacking order, skips None.
APIViewSet.extra_decorators: declarative per-operation decorators.
DecoratorsSchema in ninja_aio.schemas.helpers to configure per-op decorators.
🛠 Changed
- APIViewSet:
create, list, retrieve, update, delete compose built-ins (unique_view, paginate) and user-provided extras via decorate_view for consistent ordering.
📝 Documentation
- New:
docs/api/views/decorators.md: decorate_view usage, conditional decoration, and extra_decorators with DecoratorsSchema.
docs/api/renderers/orjson_renderer.md: how to configure ORJSON options in settings.py.
⚠ Notes / Potential Impact
- Decorator order:
decorate_view applies decorators in standard Python stacking semantics. If you relied on a specific nesting between paginate, unique_view, and custom decorators, verify behavior.
🔍 Migration / Action
- Optionally move per-operation decorators to
APIViewSet.extra_decorators = DecoratorsSchema(...).
- If desired, configure ORJSON behavior via
NINJA_AIO_ORJSON_RENDERER_OPTION in settings.py.
|
v1.0.5 diff | 2025-12-07 |  Release notes[1.0.5] - 2025-12-07
🛠 Changed
- limited support up to django ninja 1.4.5
|
v1.0.4 diff | 2025-11-03 |  Release notes[1.0.4] - 2025-11-03
✨ Added
ModelUtil._rewrite_nested_foreign_keys: reintroduced helper to rename nested FK keys from <field> to <field>_id inside nested dicts (currently invoked conditionally in parse_output_data).
🛠 Changed
ModelUtil._extract_field_obj converted to async; now uses agetattr for safer async attribute access.
ModelUtil.parse_output_data:
- Awaits the new async
_extract_field_obj.
- Fetches related instance first, then (conditionally) calls
_rewrite_nested_foreign_keys when the outer field is a ForeignKey.
📝 Documentation
- Table in
docs/api/models/model_serializer.md (CreateSerializer attributes) reformatted:
- Condensed multiline description for
customs into a single line with semicolons.
- Adjusted column widths / alignment for cleaner diff footprint.
⚠ Note / Potential Issue
| Area | Observation | Impact |
| ---- | ----------- | ------ |
| parse_output_data | Result of _rewrite_nested_foreign_keys is assigned to local v but not reattached to payload (final output still sets payload[k] = rel_instance). | FK key rewriting may be a no-op for consumers; behavior might not match intent. |
🔍 Migration / Action
- If you relied on the absence of FK key rewriting (1.0.3), verify whether the restored helper actually affects payloads (it likely does not yet).
- If rewriting is desired, ensure the transformed dict (or additional metadata) is surfaced in the serialized output or adjust logic accordingly.
✅ Suggested Follow-Ups
- Add a test asserting expected presence (or absence) of
<field>_id keys in nested output.
- Decide whether payload should expose both the related object and rewritten key map, or deprecate the helper again if not needed.
|
v1.0.3 diff | 2025-11-03 |  Release notes[1.0.3] - 2025-11-03
✨ Added
M2MRelationSchema: New optional field related_schema documented (auto-generated when using a ModelSerializer).
🛠 Changed
- Documentation tables (CRUD, Core Attributes, Auth, M2M Endpoints, Hooks) reformatted for alignment & readability.
- Extra blank lines inserted to improve Markdown rendering clarity.
ModelUtil.parse_output_data: simplified nested relation handling (direct instance assignment).
🗑 Removed
ModelUtil._rewrite_nested_foreign_keys helper.
- Foreign key nested dict rewriting logic (
<field> → <field>_id) during output serialization.
📄 Documentation
- Added warning block describing support for plain Django
Model in M2MRelationSchema.model and mandatory related_schema when used.
- Added
related_schema bullet to M2M relation capabilities list.
- Ensured file ends with a trailing newline.
⚠ Breaking Change
| Change | Impact |
| ------ | ------ |
| Removal of FK key rewriting in nested outputs | Clients expecting <nested_fk>_id keys must adjust parsing logic |
🔍 Migration Notes
- If consumers relied on
<nested_fk>_id keys, add a post-serialization adapter to inject them, or reintroduce prior logic.
- When declaring M2M relations with plain Django models, always provide
related_schema; omission now results in validation errors.
📌 Highlights
- Cleaner docs + explicit M2M plain model guidance.
- Leaner serialization path (less mutation, clearer intent).
🧪 Suggested Follow‑Ups
- Add regression test ensuring nested FK dicts are no longer rewritten.
- Consider exposing an optional flag to restore legacy FK key rewriting if demand appears.
|
v1.0.2 diff | 2025-11-01 |  Release notes[1.0.2] - 2025-11-01
✨ Added
- SonarCloud Quality Gate badge (README + docs index).
- Custom domain support (docs/CNAME).
- Release Notes page with dynamic macros (
docs/release_notes.md + mkdocs-macros-plugin).
- Release automation script (
main.py) generating tables, full changelog, and cards.
- ManyToManyAPI helper (
ninja_aio/helpers/api.py) with dynamic GET / ADD / REMOVE endpoints, filter schemas, concurrent operations, and query handler support.
- Helpers package export (
helpers/__init__.py).
- Extended schema support in
M2MRelationSchema (auto related_schema via validator).
- Refactored M2M integration in
APIViewSet (now uses ManyToManyAPI).
- New test suites: decorators, exceptions/API, renderer/parser, many-to-many API.
- Centralized literal for “not found” (
tests/generics/literals.py).
🛠 Changed
NotFoundError: error key now uses underscored verbose name.
ORJSONRenderer: replaced nested mutation with recursive transform.
ModelUtil / ModelSerializer: added comprehensive docstrings, normalized custom field tuples, improved FK and nested output handling.
- Removed inline M2M view logic from
APIViewSet.
- Enriched model serializer docs (tables, normalization, error cases).
M2MRelationSchema: validation for related schema generation.
🧾 Documentation
- Major rewrite of
docs/api/models/model_serializer.md: normalization workflow, error cases, best practices, expanded examples.
- Added Release Notes navigation in
mkdocs.yml.
- Inline internal-use warning for
ManyToManyAPI.
- Improved readability (spacing, tables, JSON formatting).
✅ Tests
- Coverage for:
- ORJSON transformations (bytes→base64, IP→string).
unique_view name suffix logic.
- Exception parsing and API defaults.
- M2M add/remove flows + duplicate/error handling.
- Updated NotFoundError key format.
- Reused shared literal for 404 assertions.
📦 Tooling
- Added
mkdocs-macros-plugin.
- Automated release visualization (HTML tables, cards).
- Cleaner MkDocs theme (font configuration).
⚠ Impact
| Change | Potential Effect |
| ------ | ---------------- |
| Underscored error keys | Clients parsing old keys must adjust |
| Extracted M2M logic | Custom subclasses relying on internals must migrate |
| 2‑tuple customs now required | Missing values trigger validation errors |
🔍 Upgrade Notes
- Update error handling for new 404 key shape.
- Migrate any manual M2M endpoint wiring to
ManyToManyAPI.
- Review custom field tuples—add defaults if optional behavior desired.
🧪 Follow‑Ups
- Tag release (
git tag -a vX.Y.Z -m "Release vX.Y.Z" && git push --tags).
- Optionally add top-level
CHANGELOG.md.
- Decide on public stability of
ManyToManyAPI (remove warning when ready).
📌 Release Template
```markdown
vX.Y.Z (YYYY-MM-DD)
Highlights:
- ...
Full release table: /release_notes/ |
v1.0.1 diff | 2025-10-30 |  Release notes1.0.1 - 2025-10-30
Added
- Docs: New dev dependencies file
requirements.dev.txt.
- MkDocs: Additional plugins (
mkdocstrings, section-index, autorefs) and extended markdown_extensions.
- Theme extras: social links, analytics stub, version metadata.
- CSS: Logo sizing rules in
docs/extra.css.
Changed
- README: Reduced length, modernized intro, added concise feature + quick start sections.
- Pagination docs: Reformatted tables, spacing, clarified examples.
- Contributing docs: Expanded with setup, PR guidelines, issue template hints.
- Tutorial (CRUD & Filtering): Table formatting, spacing normalization, improved examples.
- Favicon path moved
docs/img/favicon.ico → docs/images/favicon.ico; logo updated.
- Index docs: Documentation URL switched to custom domain.
- MkDocs config:
site_url updated to https://django-ninja-aio.com/.
- Added logo/favicon references and rich navigation features.
- Expanded palette + features (search, code copy/select, tooltips, etc.).
- PyProject metadata: Documentation URL updated to new domain.
- Pagination imports switched to
from ninja.pagination instead of local alias in examples.
- Refactor:
_m2m_views now takes a single M2MRelationSchema and is invoked in a loop (improves clarity).
- Minor docstring spacing added before CRUD endpoint decorators.
- M2M registration: Logic unchanged functionally but simplified iteration pattern.
Removed
- Legacy automatic loop inside
_m2m_views (replaced by external loop in _add_views).
- Redundant long README sections (old serializer deep examples, extended auth/pagination prose).
Internal
_add_views now iterates self.m2m_relations and calls _m2m_views(relation) for each.
- Consistent path/auth resolution maintained; no schema changes to public API.
- Added
use_directory_urls: true explicitly in mkdocs.yml.
Impact
- No breaking API changes.
- Documentation structure improved; search indexing benefits from new plugins.
- M2M internals slightly cleaner; external behavior stable.
Migration Notes
No action required for existing users. |
v1.0.0 diff | 2025-10-28 |  Release notes1.0.0 - 2025-10-28
Added
- Per‑relation M2M configuration via
M2MRelationSchema (replaces tuples).
- Per‑relation flags:
add, remove, get.
- Per‑relation
filters with dynamic schema generation and hook <related_name>_query_params_handler.
- Method
_generate_m2m_filters_schemas to build all M2M filter schemas.
- Query param injection for M2M GET:
filters: Query[filters_schema] = None.
- Extended docstrings for
APIViewSet and internal helper methods.
- Overridable hooks documented in docs (
query_params_handler, per‑relation handlers).
- Changelog: version bump to
__version__ = "1.0.0".
Changed
api_view_set.md rewritten: tuple-based M2M section replaced with M2MRelationSchema docs, new sections for filters, hooks, examples.
- CRUD table wording (schema_out formatting, notes clarified).
- Auth resolution notes now include M2M fallback logic.
- Internal view registration: per-relation flags extracted (
m2m_add/remove/get replaced by schema attributes).
- Error message spacing adjusted in
_check_m2m_objs.
- Refactored internal function docs (more concise, purpose-focused).
- Dynamic filter/path schemas built through unified
_generate_schema.
Removed
- Class attributes:
m2m_add, m2m_remove, m2m_get.
- Tuple-based
m2m_relations formats.
- Legacy verbose examples inside
views() docstring.
- Redundant
m2m_auth entry in auth table (moved to core attributes table).
Internal
- Added per-method docstrings (
create_view, list_view, retrieve_view, update_view, delete_view, _m2m_views, _add_views, etc.).
_crud_views now described as a mapping.
- Added storage of
self.m2m_filters_schemas during init.
- GET M2M handler applies optional per-relation filter hook if present.
- Manage M2M handler chooses input schema dynamically (
M2MSchemaIn / M2MAddSchemaIn / M2MRemoveSchemaIn).
Migration Notes
Old:
python
m2m_relations = [
(Tag, "tags"),
(Category, "categories", "article-categories"),
(Author, "authors", "co-authors", [AdminAuth()])
]
m2m_add = True
m2m_remove = True
m2m_get = True
New:
```python
from ninja_aio.schemas import M2MRelationSchema
m2m_relations = [
M2MRelationSchema(model=Tag, related_name="tags"),
M2MRelationSchema(model=Category, related_name="categories", path="article-categories"),
M2MRelationSchema(model=Author, related_name="authors", path="co-authors", auth=[AdminAuth()])
]
Disable ops per relation if needed:
M2MRelationSchema(model=Tag, related_name="tags", add=False, remove=False, get=True)
```
Per‑relation filters:
```python
M2MRelationSchema(
model=Tag,
related_name="tags",
filters={"name": (str, "")}
)
async def tags_query_params_handler(self, queryset, filters):
if filters.get("name"):
queryset = queryset.filter(name__icontains=filters["name"])
return queryset
```
Breaking Changes
m2m_relations must use M2MRelationSchema (no tuples).
- Removed
m2m_add, m2m_remove, m2m_get (use per-relation flags).
- Any code unpacking relation tuples must be updated to attribute access.
Summary
Release focuses on granular M2M configuration, per‑relation filtering, cleaner internals, and clearer documentation for extensibility. |
v0.11.4 diff | 2025-10-28 |  Release notes0.11.4 - 2025-10-28
Changed
- Documentation heading renamed from
# API ViewSet to # APIViewSet.
- Docs rewritten: long examples replaced with concise endpoint table and structured attribute sections.
- Core attributes table expanded (added
pagination_class, query_params, disable, endpoint doc strings).
- Clarified authentication resolution; explicit mention of
m2m_auth.
Added
- Per-relation M2M configuration: support for 3- and 4-element tuples in
m2m_relations.
- 3 elements:
(model, related_name, custom_path)
- 4 elements:
(model, related_name, custom_path, per_relation_auth)
- Per-relation auth override (local
m2m_auth inside _m2m_views loop).
- Documentation of M2M path/auth resolution rules.
Removed
- Global
m2m_path attribute (replaced by per-relation path tuple element).
- Old
m2m_relations signature list[tuple[ModelSerializer | Model, str]].
Internal Implementation
- M2M loop updated:
for m2m_data in self.m2m_relations: with dynamic tuple length parsing.
- Path resolution:
python
rel_path = rel_util.verbose_name_path_resolver() if not m2m_path else m2m_path
- Auth passed to decorators as
auth=m2m_auth instead of auth=self.m2m_auth.
- Continued use of
@unique_view(...) for stable handler naming.
Migration Notes
```python
Before
m2m_relations = [(Tag, "tags")]
m2m_path = "custom-tags" # no longer supported
After
m2m_relations = [
(Tag, "tags"), # auto path + fallback auth
(Category, "categories", "custom-categories"), # custom path
(Author, "authors", "article-authors", [AdminAuth()]) # custom path + custom auth
]
``
- Remove anym2m_path` usage.
- 2-element tuples remain valid (no breaking change).
Summary
Improved flexibility and granularity for M2M relation configuration and streamlined documentation. |
v0.11.3 diff | 2025-10-28 |  Release notes[0.11.3] - 2025-10-28
Added
- M2M Path Customization: Added
m2m_path attribute to APIViewSet for custom many-to-many relationship endpoint paths
- Default: empty string (uses auto-generated path from model verbose name)
- Allows overriding the default path resolution for M2M endpoints
Changed
APIViewSet Class Attributes
- m2m_relations type annotation: Changed from
tuple[ModelSerializer | Model, str] to list[tuple[ModelSerializer | Model, str]]
- More flexible and mutable data structure
- Allows dynamic modification of M2M relations at runtime
Code Quality & Formatting
- Consistent blank lines: Added blank lines after function returns for better code readability
- Applied to:
create_view(), list_view(), retrieve_view(), update_view(), delete_view()
- Removed extra blank line: Cleaned up unnecessary blank line in
delete_view() method
- M2M views refactoring: Improved code structure for many-to-many relationship views
- Applied
@unique_view decorator to M2M endpoints (get_related, manage_related)
- Removed manual
__name__ assignment in favor of decorator pattern
- Better separation of concerns between GET and POST operations
- Moved conditional M2M add/remove logic outside of the GET endpoint block
M2M Endpoint Generation
- Dynamic path resolution: M2M endpoints now respect custom
m2m_path attribute
```python
rel_path = (
rel_util.verbose_name_path_resolver()
if not self.m2m_path
else self.m2m_path
)
|
v0.11.1 diff | 2025-10-28 |  Release notes[0.11.1] - 2025-10-28
Fixed
- Fixed typo in module name: renamed
decoratos.py to decorators.py
- Updated import statement in
views.py to use correct decorators module name
Changed
Documentation
- Homepage Examples - Updated traditional approach comparison
- Changed from Django REST Framework serializers to Django Ninja ModelSchema
- Simplified example from
UserSerializer to UserSchemaOut
- Simplified example from
UserCreateSerializer to UserSchemaIn
- Updated view examples to use Django Ninja's
@api.get() and @api.post() decorators
- Replaced class-based views (
UserListView, UserCreateView) with function-based views
- Removed
sync_to_async wrapper calls in favor of native async Django ORM operations
- Simplified user creation with direct
acreate() usage
- Updated response format to use tuple-based status code returns
(201, user)
- Made code examples more concise and modern
Technical Details
Module Renaming
```python
Before (v0.11.0)
from .decoratos import unique_view
After (v0.11.1)
from .decorators import unique_view |
v0.11.0 diff | 2025-10-26 |  Release notes[0.11.0] - 2025-10-26
Added
Documentation
- Complete documentation website with MkDocs Material theme
- Custom domain configuration (ninja-aio.dev) via CNAME
- Getting Started Guide
- Installation instructions
- Quick start tutorial with screenshots
- Auto-generated Swagger UI examples
- Tutorial Series (4 comprehensive steps)
- Step 1: Define Your Model - Complete guide to ModelSerializer with relationships, custom fields, and lifecycle hooks
- Step 2: Create CRUD Views - APIViewSet usage, custom endpoints, query parameters, and error handling
- Step 3: Add Authentication - JWT setup with RSA keys, role-based access control, and ownership validation
- Step 4: Add Filtering & Pagination - Advanced filtering, full-text search, ordering, and performance optimization
- API Reference Documentation
- Authentication guide (965 lines) covering AsyncJwtBearer, JWT validation, RBAC, and integrations
- ModelSerializer reference (806 lines) with schema generation and relationship handling
- ModelUtil reference (1,066 lines) detailing CRUD operations and data transformations
- APIView documentation for custom endpoints
- APIViewSet documentation (327 lines) for complete CRUD operations
- Pagination guide (750 lines) with custom pagination examples
- Contributing guidelines
- Logo and branding assets
- Extra CSS styling for code blocks
Core Features
- NotFoundError Exception
- New exception class for 404 errors with model-aware error messages
- Automatically includes model verbose name in error response
- Status code 404 with structured error format
Utilities
- Decorators Module (
ninja_aio/decoratos.py)
aatomic decorator for asynchronous atomic transactions
AsyncAtomicContextManager for async transaction context management
unique_view decorator for generating unique view names based on model metadata
- Support for both singular and plural model naming conventions
Examples
- Example 1 (
examples/ex_1/)
- Basic User model without relationships
- Simple ViewSet implementation
- Basic URL configuration
- Example 2 (
examples/ex_2/)
- User and Customer models with ForeignKey relationship
- JWT authentication setup with RSA keys
- Complete auth configuration with mandatory claims
- Related field serialization examples
Development Tools
- MkDocs configuration (
mkdocs.yml)
- Material theme with deep purple color scheme
- Dark/light mode support with auto-detection
- Navigation tabs and integrated TOC
- Code highlighting with Pygments
- Admonitions and superfences support
- Documentation requirements file
- Custom CSS for documentation styling
Changed
Core Models
- ModelSerializer
- Enhanced docstring (113 lines) with comprehensive API documentation
- Detailed explanation of schema generation and relationship handling
- Examples for CreateSerializer, ReadSerializer, and UpdateSerializer
- Documented sync and async lifecycle hooks
- ModelUtil
- Enhanced docstring (79 lines) documenting all CRUD operations
- Detailed method documentation for
parse_input_data, parse_output_data, and CRUD methods
- Performance notes and error handling documentation
- Updated to use
NotFoundError instead of generic SerializeError for 404 cases
Views
- APIViewSet
- Applied
@unique_view decorator to all generated CRUD methods (create, list, retrieve, update, delete)
- Removed manual
__name__ assignment in favor of decorator-based approach
- Cleaner method definitions without post-definition name mutations
- APIView
- Added comprehensive docstring explaining base class functionality
Authentication
- AsyncJwtBearer
- Enhanced docstring (71 lines) with detailed attribute and method documentation
- Security considerations and best practices
- Integration examples with Auth0, Keycloak, and Firebase
Project Structure
- Reorganized documentation structure with clear separation of concerns
Fixed
- Consistent error handling using
NotFoundError for object not found scenarios
- Proper async context management for database transactions
Documentation Improvements
Tutorial Content
- 4,435 total lines of tutorial content
- 120+ code examples across all tutorials
- 50+ API usage examples with curl commands
- Comprehensive error handling examples
- Performance optimization tips and best practices
API Reference
- 3,994 total lines of API reference documentation
- Complete method signatures with parameter descriptions
- Return type documentation
- Error handling specifications
- Integration examples
Visual Assets
- Swagger UI screenshots for all CRUD operations
- Logo and branding images
- Diagram examples (where applicable)
Notes
Breaking Changes
None - This is a documentation and enhancement release
Migration Required
None - All changes are backward compatible
Known Issues
None reported
Links
- Documentation: https://caspel26.github.io/django-ninja-aio-crud/
|
v0.10.3 diff | 2025-09-23 |  Release notes[0.10.3] - 2025-09-23
🔧 Changed
- ModelUtil Refactoring: Extracted model field handling logic into separate property
- Added
model_fields property to encapsulate [field.name for field in self.model._meta.get_fields()]
- Updated
serializable_fields property to use new model_fields property for non-ModelSerializerMeta models
🛠️ Fixed
- Custom Field Filtering: Enhanced custom field detection logic to prevent conflicts with actual model fields
- Custom fields are now filtered to exclude fields that exist in the actual Django model
- Added
k not in self.model_fields condition to both custom field dictionary comprehension and iteration logic
- Prevents custom serializer fields from overriding or conflicting with real model fields
📈 Improvements
- Code Organization: Better separation of concerns with dedicated
model_fields property
- Field Conflict Prevention: More robust handling of custom vs model field distinction
- Code Readability: Improved maintainability by reducing code duplication in field name extraction
🔄 Technical Details
- The
customs dictionary now only includes truly custom fields that don't exist on the model
- Custom field processing in the main loop now respects model field boundaries
- Better encapsulation of model introspection logic
|
v0.10.2 diff | 2025-09-18 | -Release notes |
v0.10.1 diff | 2025-09-18 | -Release notes |
v0.10.0 diff | 2025-09-15 | -Release notes |
0.9.2 diff | 2025-08-25 | -Release notes |
v0.9.1 diff | 2025-08-25 | -Release notes |
v0.9.0 diff | 2025-07-18 | -Release notes |
v0.8.4 diff | 2025-06-20 | -Release notes |
v0.8.3 diff | 2025-06-18 | -Release notes |
v0.8.2 diff | 2025-06-18 | -Release notes |
v0.8.1 diff | 2025-06-18 | -Release notes |
v0.8.0 diff | 2025-05-15 | -Release notes |
v0.7.8 diff | 2025-03-21 | -Release notes |
v0.7.7 diff | 2025-03-05 | -Release notes |
v0.7.6 diff | 2025-02-24 | -Release notes |
v0.7.5 diff | 2025-02-22 | -Release notes |
v0.7.4 diff | 2025-02-20 | -Release notes |
v0.7.3 diff | 2025-02-19 | -Release notes |
v0.7.2 diff | 2025-01-30 | -Release notes |
v0.7.1 diff | 2025-01-29 | -Release notes |
v0.7.0 diff | 2025-01-29 | -Release notes |
v0.6.4 diff | 2025-01-22 | -Release notes |
v0.6.3 diff | 2025-01-22 | -Release notes |
v0.6.2 diff | 2025-01-19 | -Release notes |
v0.6.1 diff | 2025-01-13 | -Release notes |
v0.6.0 diff | 2025-01-12 | -Release notes |
v0.5.0 diff | 2025-01-09 | -Release notes |
v0.4.0 diff | 2025-01-08 | -Release notes |
v0.3.1 diff | 2024-11-07 | -Release notes |
v0.3.0 diff | 2024-10-10 | -Release notes |
v0.2.2 diff | 2024-10-03 | -Release notes |
v0.2.1 diff | 2024-10-02 | -Release notes |
v0.2.0 diff | 2024-10-01 | -Release notes |
v0.1.4 diff | 2024-09-29 | -Release notes |
v0.1.3 diff | 2024-09-28 | -Release notes |
v0.1.2 diff | 2024-09-26 | -Release notes |
v0.1.1 diff | 2024-09-26 | -Release notes |