Type-Safe Pydantic Schemas for Django JSONFields
django-pydantic-field provides a way to use Pydantic models as schemas for Django's JSONField.
It offers full support for Pydantic v1 and v2, type safety and integration with Django's ecosystem, including Forms and Django REST Framework.
Highlights
- Unified API: Transparent support for Pydantic v1 and v2 through the
SchemaAdapters. - Type-Safe: Support for static type checking (ty/mypy/pyright) with type inference for models and annotations.
- Forward References: Lazy resolution of forward references, allowing schemas to be defined anywhere.
- Django Integration: Support for Django Forms and the Admin interface.
- DRF Support: Typed Serializers, Parsers, and Renderers with automatic OpenAPI schema generation via DRF's native schema generator.
Installation
pip install django-pydantic-field
Add "django_pydantic_field" to INSTALLED_APPS in your settings.py to enable automatic integrations (such as Django REST Framework ModelSerializer field mapping and drf-spectacular OpenAPI extensions):
INSTALLED_APPS = [
...,
"django_pydantic_field",
]
Basic Usage
The SchemaField can be used by passing the schema as the first argument (Django-like style) or by using type annotations.
import pydantic
import typing
from django.db import models
from django_pydantic_field import SchemaField
class Foo(pydantic.BaseModel):
count: int
slug: str = "default"
class MyModel(models.Model):
# Django-like style (explicit schema)
bar = SchemaField(Foo, default={"count": 5})
# Annotation-based style (Pydantic-like)
foo: Foo = SchemaField()
# Supports standard Python types and annotations
items: list[Foo] = SchemaField(default=list)
# null=True correctly infers t.Optional[Foo] for type checkers
optional_foo = SchemaField(Foo, null=True, default=None)
model = MyModel(foo={"count": 42})
model.save()
# Data is automatically parsed into Pydantic models
assert isinstance(model.foo, Foo)
assert model.foo.count == 42
typing.assert_type(model.optional_foo, Foo | None)
Supported Types
Any type supported by Pydantic can be used as a schema:
pydantic.BaseModelandpydantic.RootModel(v2)- Standard Python types (
list[str],dict[int, float], etc.) dataclasses.dataclassandTypedDictprotocolstyping.Annotatedwith metadata.
from typing import Annotated
from pydantic import Field
class AdvancedModel(models.Model):
# Annotated with validation rules
positive_ints: Annotated[list[int], Field(min_length=1)] = SchemaField()
Forward References & Lazy Resolution
SchemaField supports forward references via string literals or typing.ForwardRef. Resolution is deferred until the first time the field is accessed.
import typing
class MyModel(models.Model):
foo = SchemaField(typing.ForwardRef("DeferredFoo"))
another_foo: "DeferredFoo" = SchemaField()
class DeferredFoo(pydantic.BaseModel):
...
Error Handling & Schema Evolution
By default, SchemaField validates data strictly when reading from the database or assigning attributes (errors="raise"). When schemas evolve or legacy rows contain invalid data, you can configure how validation errors are handled via the errors argument:
"raise"(default): Raisesdjango.core.exceptions.ValidationErroron invalid data."ignore": Returns the raw unvalidated value (e.g.,dictorlist) when validation fails, allowing models to be queried, updated, or deleted without crashing."coerce": Falls back to the field'sdefaultvalue, orNoneifnull=True(raisesValidationErrorif neither is configured).
class EvolvingModel(models.Model):
# Keep raw data in-place if existing database rows do not match Foo
legacy_foo: Foo = SchemaField(errors="ignore")
# Fall back to default value or None on validation failure
fallback_foo: Foo = SchemaField(default=Foo(count=0), errors="coerce")
nullable_foo: Foo | None = SchemaField(null=True, errors="coerce")
Pydantic Version Support
The package automatically detects the Pydantic version in your environment and adapts accordingly.
For Pydantic v2 environments, you can still explicitly use Pydantic v1 models by importing from the .v1 subpackage:
from pydantic import v1 as pydantic_v1
from django_pydantic_field.v1 import SchemaField as SchemaFieldV1
class LegacySchema(pydantic_v1.BaseModel):
...
class LegacyModel(models.Model):
legacy_field = SchemaFieldV1(LegacySchema)
Django Forms & Admin
It is possible to create Django forms, which would validate against the given schema:
from django import forms
from django_pydantic_field.forms import SchemaField
class FooForm(forms.Form):
field = SchemaField(Foo)
form = FooForm(data={"field": '{"slug": "asdf", "count": 1}'})
assert form.is_valid()
django-jsonform support
For a better user experience in the Admin, you can use django-jsonform, which provides a dynamic editor based on the Pydantic model's JSON schema.
from django.contrib import admin
from django_pydantic_field import fields
from django_jsonform.widgets import JSONFormWidget
class MyModelAdmin(admin.ModelAdmin):
formfield_overrides = {
fields.PydanticSchemaField: {"widget": JSONFormWidget},
}
Django REST Framework
Serializers
SchemaField supports explicit schemas, serializer-level type annotations, and automatic resolution inside ModelSerializer (when "django_pydantic_field" is in INSTALLED_APPS or django_pydantic_field.rest_framework is imported):
from rest_framework import serializers
from django_pydantic_field.rest_framework import SchemaField
class MySerializer(serializers.Serializer):
pydantic_field = SchemaField(Foo)
annotated_field: list[Foo] = SchemaField()
class MyModelSerializer(serializers.ModelSerializer):
class Meta:
model = MyModel
fields = ("bar", "foo", "items", "optional_foo")
Typed Views (Parsers & Renderers)
You can use SchemaParser and SchemaRenderer to handle Pydantic models directly in your views.
from rest_framework.decorators import api_view, parser_classes, renderer_classes
from rest_framework.response import Response
from django_pydantic_field.rest_framework import SchemaParser, SchemaRenderer
@api_view(["POST"])
@parser_classes([SchemaParser[Foo]])
@renderer_classes([SchemaRenderer[list[Foo]]])
def foo_view(request):
# request.data is a Foo instance
instance: Foo = request.data
return Response([instance])
OpenAPI Generation
drf-spectacular
If you use drf-spectacular with Pydantic v2, django-pydantic-field automatically registers an OpenApiSerializerFieldExtension for django_pydantic_field.rest_framework.SchemaField out of the box—no additional configuration is required.
pip install "django-pydantic-field[spectacular]"
DRF Core AutoSchema
django-pydantic-field also provides an AutoSchema for DRF's built-in schema generator that automatically generates OpenAPI definitions for your Pydantic-backed DRF components.
from django_pydantic_field.rest_framework import AutoSchema
class SampleView(generics.RetrieveAPIView):
serializer_class = MySerializer
schema = AutoSchema()
System Checks
The field performs validation during Django's manage.py check command:
pydantic.E001: Schema resolution errors.pydantic.E002: Default value serialization errors.pydantic.W003: Data integrity warnings forinclude/excludeconfigurations.pydantic.W004: Warning whenerrors="coerce"is set on a non-nullable field without a default value.
Contributing
To get django-pydantic-field up and running in development mode:
- Install
uv; - Install the project and its dependencies:
uv sync; - Setup
pre-commit:pre-commit install. - Run tests:
make test. - Run linters:
make lint.
Acknowledgement
- Churkin Oleg for his Gist as a source of inspiration
Metadata
Release files for django-pydantic-field 0.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| django_pydantic_field-0.6.0.tar.gz | 32.9 kB | Without provenance | Details for django_pydantic_field-0.6.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| django_pydantic_field-0.6.0-py3-none-any.whl | Python 3 | none | any | Details for django_pydantic_field-0.6.0-py3-none-any.whl |
Total release size: 78.8 kB
Release files / django_pydantic_field-0.6.0.tar.gz
| Download URL | django_pydantic_field-0.6.0.tar.gz |
|---|---|
| Size | 32.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
bfeda1d07ba9b528aba57b60bc4d9484f861afecca9cc337e20725d244192bed
|
|
BLAKE2b-256 checksum How to use checksums |
0ee9bb982b8c3b519928d63226febb79f6d6c46c33bbcdd4a68a03ef0692afc2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / django_pydantic_field-0.6.0-py3-none-any.whl
| Download URL | django_pydantic_field-0.6.0-py3-none-any.whl |
|---|---|
| Size | 45.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
2b595e5db66f9304e1c53dea965e00b818590aef6b3c74434d2af760c51e4384
|
|
BLAKE2b-256 checksum How to use checksums |
1c4492924e20b9d7ab00245c360d99605d2cd75ec3154f707583d508446e7a31
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for django-pydantic-field 0.6.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.