Skip to main content

django-dto-field

PyPI Tests typing & lint

wemake-python-styleguide

Your DTO's, at home in Django models.

Save a your DTO object (dict, dataclass, etc) and get it back—including nested objects. Let the field handle conversion and schema validation while you work with familiar objects and Django framework.

Why use it?

Django's JSONField stores structured data, but does not reconstruct your typed objects or validate their annotated members. Applications often repeat that conversion in model methods, services, and forms.

django-dto-field puts that work at the field boundary. Write a DTO, read a DTO, and keep schema validation consistent across model saves, queryset updates, and bulk writes. Choose native JSON, text, or binary storage to suit your database.

  • DTO's work out of the box, with nested objects and strict member validation.
  • Dictionaries DTO's work without a schema when you need flexible structured data.
  • JSON fields retain Django's key lookups and projections.
  • ModelForms display JSON, and Django JSON fixtures round-trip DTO values.
  • Explicit adapters provide an extension point for other DTO libraries.

DTO support

  • Python dataclasses — nested DTOs, annotated-member validation, and reconstruction on reads.
  • Python dict — flexible JSON-compatible data without a custom schema.
  • Pydantic — planned model adapter.
  • Marshmallow — planned schema adapter.
  • attrs — planned object adapter.
  • Adaptix — planned conversion adapter.

The adapter architecture is already in place for these integrations. Want to help bring your favorite library to Django fields? See Extend DTO support and our contributing guide.

Install

Requires Python 3.10+ and Django 4.2+.

pip install django-dto-field

No additional entry in INSTALLED_APPS is needed. Add the fields to your models, then create and apply migrations as usual.

Quick start

dataclass

Let's give an order a typed customer and address. Put these classes in your app's models.py (or another importable module) so Django can use them in migrations:

from dataclasses import dataclass
from django.db import models
from django_dto_field.fields.json import DTOJSONField

@dataclass
class Address:
    city: str

@dataclass
class Customer:
    name: str
    address: Address

class Order(models.Model):
    customer = DTOJSONField(schema=Customer)

After creating and applying migrations, try this in the Django shell:

from myapp.models import Address, Customer, Order

order = Order.objects.create(customer=Customer("Alice", Address("London")))
order.refresh_from_db()
assert order.customer == Customer("Alice", Address("London"))
assert isinstance(order.customer.address, Address)

Order.objects.filter(customer__address__city="London")
Order.objects.values_list("customer__address", flat=True)  # Returns dictionaries.

You can pass a dictionary too. full_clean() turns it into your dataclass on the model instance:

order = Order(customer={"name": "Bob", "address": {"city": "Paris"}})
order.full_clean()
assert isinstance(order.customer, Customer)
order.save()

Invalid annotated members raise Django ValidationError during validation or literal writes—even when Python permits constructing the dataclass:

from django.core.exceptions import ValidationError

try:
    Order.objects.create(customer=Customer("Alice", Address(city=123)))
except ValidationError as error:
    print(error)

dict

For data that doesn't need a typed schema, leave out schema:

from django.db import models
from django_dto_field.fields.json import DTOJSONField

class Document(models.Model):
    metadata = DTOJSONField(default=dict, blank=True)

After migrating, use the field in the Django shell or application code:

from myapp.models import Document

document = Document.objects.create(metadata={"author": "Ada", "tags": ["math"]})
document.refresh_from_db()
assert document.metadata == {"author": "Ada", "tags": ["math"]}

Document.objects.filter(metadata__author="Ada")
Document.objects.values_list("metadata__tags", flat=True)  # Returns lists.

Use callable defaults such as dict so each instance gets its own value.

Choose your storage field

The schema and adapter options work with all three fields:

Import Django base Stored value
django_dto_field.fields.json.DTOJSONField JSONField Native JSON, encoded by Django
django_dto_field.fields.char.DTOCharField CharField UTF-8 JSON text
django_dto_field.fields.binary.DTOBinaryField BinaryField UTF-8 JSON bytes
from django_dto_field.fields.binary import DTOBinaryField
from django_dto_field.fields.char import DTOCharField

# Inside a model definition:
summary = DTOCharField(max_length=2000, default=dict, blank=True)
archive = DTOBinaryField(schema=Customer, null=True, blank=True)

DTOCharField.max_length measures the serialized text. DTOBinaryField retains Django's editable=False default. Package initializers do not re-export classes; use the module paths above.

A few things worth knowing

  • Validation timing: attribute assignment does not validate. Conversion and literal writes validate the schema, including update() and bulk writes. Call full_clean() for Django field validators; save() does not run them all.
  • In-memory values: saving dictionary input prepares it for storage without replacing the attribute. Use full_clean() or refresh_from_db() to obtain a DTO.
  • Field validators: receive the storage value—text, bytes, or a JSON-compatible mapping. null and blank retain Django semantics.
  • JSON lookups: partial/scalar operands bypass whole-DTO validation. Key projections return native JSON values, not partial DTO's.
  • Nulls: None stores SQL NULL when permitted. DTOJSONField also supports explicit JSON null through Django's Value(None, output_field=...).
  • SQL expressions: arbitrary database expressions are not schema-validated before execution; complete values are validated when read back.
  • Schema changes: changing a schema does not rewrite existing data. Plan a data migration when stored values no longer satisfy the new schema.

Extend DTO support

If you'd like to build your custom integration supply an importable adapter class:

from django_dto_field.fields.json import DTOJSONField
from myapp.dto import MyAdapter, MySchema

payload = DTOJSONField(schema=MySchema, adapter=MyAdapter)

Implement the DTOAdapter protocol from django_dto_field.adapters.base:

  • __init__(schema): configure and validate the schema.
  • is_instance(instance): recognize a DTO using TypeGuard.
  • to_data(instance): produce a JSON-compatible dictionary.
  • from_data(payload): validate a complete dictionary and reconstruct a DTO.

The schema and result classes may differ. Adapters are instantiated per converter and preserved in migrations, with no global registry. Translate integration errors into library exceptions from django_dto_field.exceptions.base. At the Django boundary, these become DTOFieldError from django_dto_field.exceptions.django, which also inherits Django ValidationError.

See the test adapters for small working examples.

Help and contribute

Have a question, spotted a bug, or want a new DTO integration? We'd love to hear from you in the issue tracker. For bugs, include a minimal example, your Python/Django versions, and the expected and actual behavior.

Read CONTRIBUTING.md for setup, testing, and pull request guidance. Small improvements count too—clearer examples, documentation fixes, and tests are all welcome. The project uses unit, property-based, and Django integration tests with 100% statement/branch coverage, plus Ruff, WPS, and strict mypy checks.

License

Available under the MIT license.

Metadata

Release files for django-dto-field 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-dto-field 0.1.0
File Size Uploaded Provenance Actions
django_dto_field-0.1.0.tar.gz 11.2 kB Without provenance Details for django_dto_field-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for django-dto-field 0.1.0
File Interpreter ABI Platform Actions
django_dto_field-0.1.0-py3-none-any.whl Python 3 none any Details for django_dto_field-0.1.0-py3-none-any.whl

Total release size: 29.8 kB

Release files / django_dto_field-0.1.0.tar.gz

Download URL django_dto_field-0.1.0.tar.gz
Size 11.2 kB
Tags Source
SHA-256 checksum
How to use checksums
2d2f6d49eafc9c37f8d0284b5da67d960e6de931a764867e370508722a8f4b9e
BLAKE2b-256 checksum
How to use checksums
c3f36ce5bf39c50785abc56233b608c9afd8c20e858e5bc0e8387ce2e4003e5a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / django_dto_field-0.1.0-py3-none-any.whl

Download URL django_dto_field-0.1.0-py3-none-any.whl
Size 18.6 kB
Tags Python 3
SHA-256 checksum
How to use checksums
082627501812bcbad40590cab4784ea0b1b3a5d4472c3fab19168aa13251c78b
BLAKE2b-256 checksum
How to use checksums
6bc412f0697006dc19916794102c13f3cba6f98b7b6fb03ddd938418e2681708
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via uv/0.12.21 {"installer":{"name":"uv","version":"0.12.21","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for django-dto-field 0.1.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.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page