Django Ninja¶
Django Ninja schemas are Pydantic models under the hood, so
pydantic-versions can work with ninja.Schema classes for request and
response payload compatibility.
This is an integration target, not a runtime dependency for the core package. Django Ninja is useful when API schema versions need to be decoupled from application releases in the same way config schema versions are.
The compatibility tests cover both schema-level behavior and a minimal real
NinjaAPI:
- generated historical schemas work as request body types;
- current generated schemas work as request body types;
- route handlers can upgrade historical payloads into the current schema;
- generated historical models appear in the OpenAPI schema with historical field names.
ModelSchemaclasses preserve Django-derived field metadata such as string length constraints.
Versioned Ninja Schemas¶
from ninja import Schema
from pydantic_versions import (
field_default,
field_renamed,
schema_version,
validate_versioned,
versioned_schema,
)
@versioned_schema(name="task_payload", versions=["v1", "v2"], current="v2")
@schema_version(
"v1",
patches=[
field_default("timeout", 5.0),
field_renamed("completed", "is_completed"),
],
)
class TaskPayload(Schema):
title: str
completed: bool = False
timeout: float = 10.0
Historical input validates and upgrades into the current Ninja schema:
result = validate_versioned(
TaskPayload,
{"schema_version": "v1", "title": "Import", "is_completed": True},
)
assert result.current_model == TaskPayload(
title="Import",
completed=True,
timeout=5.0,
)
Generated Historical Schemas¶
model_for_version() returns a generated Pydantic model that Django Ninja can
inspect for JSON Schema/OpenAPI generation:
from pydantic_versions import model_for_version
TaskPayloadV1 = model_for_version(TaskPayload, "v1")
schema = TaskPayloadV1.model_json_schema()
assert "is_completed" in schema["properties"]
assert "completed" not in schema["properties"]
This supports explicit route-level schemas:
@api.post("/v1/tasks")
def create_task_v1(request, payload: TaskPayloadV1):
result = validate_versioned(TaskPayload, payload.model_dump(), version="v1")
task = result.current_model
...
Aliases¶
Django Ninja supports Pydantic aliases and additional dotted aliases for response schemas. Version renames intentionally take precedence for generated historical models:
from ninja import Field, Schema
@versioned_schema(name="task_alias", versions=["v1", "v2"], current="v2")
@schema_version("v1", patches=[field_renamed("done", "completed")])
class TaskAlias(Schema):
title: str
done: bool = Field(False, alias="is_done")
The current schema still exposes is_done, while the generated v1 schema exposes
completed.
ModelSchema¶
ninja.ModelSchema is also supported for the tested request-schema path:
from django.db import models
from ninja import ModelSchema
class Task(models.Model):
title = models.CharField(max_length=100)
is_done = models.BooleanField(default=False)
timeout = models.FloatField(default=10.0)
@versioned_schema(name="task_model", versions=["v1", "v2"], current="v2")
@schema_version(
"v1",
patches=[
field_default("timeout", 5.0),
field_renamed("is_done", "completed"),
],
)
class TaskPayload(ModelSchema):
class Meta:
model = Task
fields = ["title", "is_done", "timeout"]
The generated v1 schema exposes completed, keeps the historical timeout
default, and preserves the maxLength constraint derived from the Django model's
CharField.
What To Test In Applications¶
For Django Ninja projects, add application-level tests for:
- generated historical request schemas used in route signatures;
- OpenAPI output for every public API version;
- aliases and dotted aliases on response schemas;
ModelSchemaorcreate_schema()generated classes before decorating them;- validation from historical request payloads into current application models.
The current library tests cover plain ninja.Schema compatibility, generated
historical model JSON Schema, aliases, version rename precedence, minimal route
handling, ModelSchema, and OpenAPI request-body output. Larger applications
should still test their real routers, auth, response schemas, create_schema()
usage, and generated client contracts.