Skip to content

Django Modern Schemas

Declarative Pydantic v2 schemas generated from Django models.

Django Modern Schemas reads a model's field definitions — types, null, blank, default, choices, max_length — and builds a real Pydantic model from them. You get validation, serialization and JSON Schema without restating the field definitions you already wrote in models.py.

Get started Read the overview

Every example on this site is executed

Code blocks written as a console transcript (>>>) are run by the test suite on every commit, and the output shown is the output the library produced. See How these docs are tested.

Installation

pip install django-modern-schemas

Requires Python 3.10+, Django 3.2+ and Pydantic 2.13+. Nothing needs to be added to INSTALLED_APPS — the library is imported, not installed as an app.

A first schema

Given an ordinary Django model:

models.py
class Event(models.Model):
    title = models.CharField(max_length=100)
    category = models.OneToOneField(Category, null=True, on_delete=models.SET_NULL)

    class Meta:
        app_label = 'examples'

    def display_title(self) -> str:
        return f'Event: {self.title}'

Declare a schema that names the model in a nested Config class:

>>> class EventSchema(ModelSchema):
...     class Config:
...         model = models.Event
...         fields = ['id', 'title']

The result is a Pydantic model. It validates a Django instance directly:

>>> event = models.Event(id=1, title='DjangoCon')
>>> EventSchema.model_validate(event).model_dump()
{'id': 1, 'title': 'DjangoCon'}

It reads a JSON payload and writes it back through the ORM:

>>> class EventCreateSchema(ModelSchema):
...     class Config:
...         model = models.Event
...         fields = ['title']
>>> created = EventCreateSchema.model_validate({'title': 'PyCon'}).create()
>>> created.pk is not None
True

Input that the database would reject is refused first, with a Pydantic ValidationError you can return as a 400.

And it describes itself as JSON Schema:

>>> print(json.dumps(EventSchema.model_json_schema()['properties']['title'], indent=2))
{
  "description": "",
  "maxLength": 100,
  "title": "Title",
  "type": "string"
}

max_length=100 on the Django field became maxLength: 100 in the JSON Schema. Constraints are carried across rather than re-declared.

What you get

Generated fields

Django field types, null/blank, defaults and choices become Pydantic annotations. See the field reference.

Explicit relations

Relations serialize as primary keys by default, or as nested schemas when you ask for them with depth. See Relations.

Renamed and computed values

Source reads a dotted attribute path; MethodSource calls a model method. See Source and MethodSource.

Persistence

create(), update() and save() write validated data back through the ORM. See Persistence.

Where to go next

If you want to Read
Understand the pieces and their boundaries Overview
Follow a worked tutorial from an empty app Getting Started
Configure fields, exclude, optional, depth ModelSchema
Look up how a Django field is converted Field reference
Build schemas at runtime SchemaFactory

Scope

This library builds schemas from models and writes flat data back. It deliberately does not plan queries for you, and it does not perform nested writes. Those boundaries are stated in full under Overview → Boundaries.

Credits

Django Modern Schemas is maintained by Open Byte. It builds on the design of Ninja Schema by Tochukwu (@eadwinCode) — see Credits and Stewardship.