drf-writable-nested

Writable nested model serializers for Django REST Framework, letting a single serializer create and update deeply related models in one call.

Library
PyPI
v0.7.2
1,140stars
BSD-2-Clause

Repository Health

Pre-computed score based on development activity, maintenance, community, maturity, and trend momentum.How we score it →
46/100Fair
Development Activity0
Maintenance20
Community76
Maturity60
Momentum28

Technical Analysis

AI-assessed by reading the actual repository — architecture, code quality, innovation, and documentation.How we score it →
76/100Good
Architecture78
Code Quality85
Innovation62
Learning Curve80

drf-writable-nested extends Django REST Framework’s ModelSerializer with mixins that make nested relationships writable instead of read-only. By default, DRF serializers can only read nested objects; writing a parent object along with its related children normally requires overriding create() and update() by hand for every serializer that has nested data. This package supplies that logic once, as a set of reusable mixins.

NestedCreateMixin and NestedUpdateMixin (combined in WritableNestedModelSerializer) inspect a serializer’s declared fields at save time, split out direct relations (ForeignKey, OneToOne) from reverse relations (reverse FK, ManyToMany, GenericRelation), and recursively create or update each related serializer before assembling the parent instance. On update, it also diffs the incoming related data against what already exists in the database and deletes or nulls out rows that were dropped from the payload, respecting the field’s on_delete behavior (CASCADE, SET_NULL, SET_DEFAULT, PROTECT).

A separate UniqueFieldsMixin solves a specific DRF pain point: UniqueValidator normally runs during validation, before nested writes happen, which breaks legitimate updates to nested objects that reuse their own unique value. The mixin strips those validators out of the field-level validation pass and re-runs them at save time instead, against the correct pre-save state.

The library has no runtime dependencies beyond Django and DRF themselves, ships type stubs (py.typed) with an accompanying mypy configuration, and is widely used wherever an API needs to accept nested JSON payloads and persist them as related Django model rows without hand-writing that traversal logic per endpoint.

What You Get

  • WritableNestedModelSerializer — a ready-to-use base class combining create and update support for nested relations in one serializer.
  • NestedCreateMixin and NestedUpdateMixin — usable independently if a serializer only needs to support one of create or update.
  • Support for OneToOne, ForeignKey, ManyToMany, and GenericRelation, in both direct and reverse form.
  • Automatic cleanup of orphaned related rows on update, honoring the field’s on_delete policy (CASCADE, SET_NULL, SET_DEFAULT, PROTECT).
  • UniqueFieldsMixin to defer UniqueValidator checks to save time so nested updates on models with unique fields don’t fail validation incorrectly.
  • Pass-through save() kwargs so values can be injected into specific nested serializers from the parent’s .save() call.
  • Type stubs (py.typed) plus a maintained mypy configuration for typed DRF projects.

Common Use Cases

  • Building a single API endpoint that creates a parent record and all of its related child records (e.g. an order with line items) from one POST body.
  • Accepting a PATCH/PUT payload that edits a parent object’s nested collection — adding, updating, and removing children — without writing custom update() logic per view.
  • Exposing a form-like nested object graph (profile with addresses, tags, access keys) through DRF without denormalizing it into flat, disconnected endpoints.
  • Migrating hand-rolled nested create/update overrides in existing serializers to a shared, tested implementation to reduce per-serializer bugs.
  • Supporting unique constraints on nested child models during partial updates where DRF’s default UniqueValidator would otherwise reject legitimate re-saves.

Under The Hood

Architecture The package is organized as two small modules: mixins.py, which holds the actual traversal and persistence logic, and serializers.py, which composes the mixins into a public WritableNestedModelSerializer. BaseNestedModelSerializer._extract_relations walks self.fields at save time, using Django’s model _meta API to classify each declared serializer field as a direct relation (FK/O2O), a reverse relation (reverse FK/M2M/GenericRelation), or neither, popping the nested payloads out of validated_data so DRF’s own ModelSerializer.create/update only ever sees flat, model-native attributes. NestedCreateMixin.create writes direct relations first (so the parent’s FK columns are populated before the parent row is inserted), creates the parent, then processes reverse relations against the new instance. NestedUpdateMixin.update follows the same shape but adds delete_reverse_relations_if_need, which diffs current child primary keys against the submitted set and deletes, nulls, or defaults the difference depending on the relation’s on_delete — closing the loop that a naive nested-write implementation usually leaves open. There is no external state or I/O beyond the ORM calls DRF’s own serializers already make, so the abstraction is a pure orchestration layer over Django’s relation metadata.

Tech Stack Python 3.9-3.13, targeting Django 4.2 through 5.2 and Django REST Framework 3.14/3.15, tested with tox across that full matrix via GitHub Actions. The runtime has zero third-party dependencies beyond django and djangorestframework themselves. Packaging is plain setuptools, versioned from __init__.py via a regex read in setup.py, distributed with py.typed for consumers running mypy with the django-stubs/djangorestframework-stubs plugins configured in mypy.ini.

Code Quality Tests live under tests/ and cover the mixins extensively — test_writable_nested_model_serializer.py alone runs over a thousand lines across the relation types (one-to-one, FK, M2M, reverse M2M, generic relations, on_delete variants), with test_unique_fields_mixin.py and test_nested_validation.py covering the two other public behaviors. Tests run under pytest/pytest-django with coverage uploaded to Codecov, and the CI matrix spans five Python versions and four Django versions per push. Error handling favors explicit ValidationError propagation with field-scoped error dictionaries rather than silent failures. Naming and structure are conventional DRF-mixin style (create/update overrides calling super()), and a dedicated mypy tox environment type-checks both the library and its example project.

What Makes It Unique Most DRF nested-write solutions in the ecosystem stop at handling ManyToMany or a single level of ForeignKey; this library’s differentiator is going further into reverse relations, GenericRelation, and the delete/orphan-cleanup side of updates — actually reconciling what related rows should still exist after an update, not just what should be created. The UniqueFieldsMixin addresses a specific, well-documented DRF validation ordering bug (unique-field validation running before nested saves) rather than a generic feature, which is a narrow but genuinely useful fix that’s hard to get right without deep knowledge of DRF’s validator internals.

Join founders buildingwith open source

Opinionated takes, migration guides, cost-saving tips, and insights from the open source ecosystem.

Subscribe on Substack
Join 750+ subscribers

Search