Library / SDK
beda-software/drf-writable-nested avatar
beda-software/drf-writable-nested

drf-writable-nested, nested writes for Django REST Framework

Writable nested model serializer for Django REST Framework

1,139 stars115 forksPythonNOASSERTION

At a glance

What is it?
drf-writable-nested is a small serializer mixin set that lets a Django REST Framework serializer create and update related objects from nested payloads. It covers the usual relations, and it comes with two documented rough edges around unique fields and form-data updates.
Who is it for?
drf-writable-nested fits a Django API team that wants one endpoint to create a parent and its children in a single request, and that is willing to write a small serializer tree rather than keep a hand rolled save loop. It does not fit a project that updates nested objects through multipart form-data, and it does not fit a schema with unique fields in a child model unless you add the UniqueFieldsMixin and accept that uniqueness moves to save time.
Can I use it commercially?
Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
Is it still maintained?
Yes. The repository last received commits 45 days ago.
What is it written in?
Mainly Python, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 24, 2026, and from our analysis. They are not legal advice.

Editorial analysis

One payload, one save, a whole object graph

The default Django REST Framework behaviour is to treat a nested serializer as read only unless you write the save logic yourself, which is the repetitive part every API team ends up copying between projects. This package is the copy, packaged. It is a writable nested model serializer that allows you to create and update your models with related nested data, and the intended user is a developer who has decided that a single request should carry the whole graph, for example a user with a profile, an access key, a set of sites and a list of avatars. The library does not change how Django relationships work or how DRF validates the data it receives. It changes what happens when save is called: the nested structures are written as real related rows instead of being rejected or ignored.

Which relations the mixins will walk

The README lists the supported relations, and the list is worth reading closely because the exclusions matter as much as the inclusions. OneToOne relations are supported in both direct and reverse direction, and so are ForeignKey relations. ManyToMany is supported direct and reverse, except for many to many relations that use a through model, which are not handled. GenericRelation is supported, and the README notes that it is always only reverse. The entry points are a WritableNestedModelSerializer base class for both operations, and NestedCreateMixin or NestedUpdateMixin when you want to support only one half of the lifecycle. That split is the first design decision to understand, because a read only endpoint that accidentally inherits update behaviour is a bug you will write yourself.

Installing it and saving a nested graph

The package installs from PyPI with a single command.

bash
pip install drf-writable-nested

The usage pattern is that a serializer inherits the writable base and declares its relations as ordinary nested serializers. The README's example runs through a Profile model that has a many to many to Site, a one to one to User, a foreign key to AccessKey, and a reverse foreign key to Avatar.

python
class ProfileSerializer(WritableNestedModelSerializer):
    # Direct ManyToMany relation
    sites = SiteSerializer(many=True)

    # Reverse FK relation
    avatars = AvatarSerializer(many=True)

    # Direct FK relation
    access_key = AccessKeySerializer(allow_null=True)

    class Meta:
        model = Profile
        fields = ('pk', 'sites', 'avatars', 'access_key',)

The parent serializer then nests that one, and the payload is validated and saved the ordinary way.

python
user_serializer = UserSerializer(data=data)
user_serializer.is_valid(raise_exception=True)
user = user_serializer.save()

The README states that this automatically creates all nested relations and yields a complete instance with filled data, and the printed serializer output shows the primary keys assigned to every created row.

Passing values down at save time

Sometimes a nested object needs a value that only the caller knows, such as a tenant identifier or a current user, and repeating it in every nested payload is ugly. The package supports passing values through to nested serializers from the base serializer's save method, and the README is specific that these kwargs must be of type dict.

python
user = user_serializer.save(
    profile={
        'access_key': {'key': 'key2'},
    },
)
print(user.profile.access_key.key)

The result is the value key2. The README also states an important behavioural detail: the same value will be used for all nested instances like a default value, but with higher priority. That wording has consequences. The value applies to every nested instance of that field rather than to one of them, which is fine for a shared value and wrong for a per row one. If your nested children each need a distinct value, this mechanism does not do that, and the field has to be carried in the payload instead.

Unique fields and the mixin that defers them

The first documented problem is validation of nested serializers with unique fields on update. The package ships a special mixin for it, UniqueFieldsMixin, and the mechanism is described plainly: the mixin moves UniqueValidator instances from the validation stage to the save stage. The reason this is necessary is that a unique validator on a child serializer cannot know at validation time whether the row being checked is the one being updated, so the standard DRF behaviour rejects valid updates. Applying the mixin means the uniqueness check happens when the row is written.

python
class ChildSerializer(UniqueFieldsMixin, serializers.ModelSerializer):
    class Meta:
        model = Child

Two rules come with it. The mixin must be applied only to a serializer that has unique fields, and when it is combined with NestedCreateMixin or NestedUpdateMixin, it must be listed first, so the ordering is UniqueFieldsMixin ahead of the other mixin. Getting the order wrong is not a style preference, because a mixin that defers validation has to be in a position where the rest of the pipeline sees the deferred behaviour.

Form-data updates and through models are the excluded cases

The second documented problem is about updates rather than creation. The README describes a special problem when trying to update a model object with nested fields inside it through PUT or PATCH using form-data, and states that in that case the update cannot be performed. This is a hard boundary for anyone whose API is consumed by a browser form or by a client that submits multipart bodies, and the README does not document a workaround, only the acknowledgement. The through model exclusion is the other boundary. A many to many relation defined with a through model carries extra columns that a nested payload has no obvious place for, so the library declines to walk it, and a schema with such a relation needs a different path for that field. GenericRelation is a narrower case rather than a broken one, since it is supported but only in the reverse direction. Taken together, the three cases tell you which schemas this package is comfortable with: plain relations, written as JSON, where the child rows can be created from the payload itself.

Packaging, version support and a licence question

The repository is not archived and the last push was on 2026-09-10, while the newest release tag is v0.7.2 from 2025-03-10, preceded by v0.7.1 from 2024-10-29 and v0.7.0 from 2022-08-11, so the work sits ahead of the last tagged release. The package metadata gives the version through a regex that reads the __version__ string out of the package __init__.py, and the setup.py declares python_requires of 3.10 or later while the README lists Python 3.9 through 3.13, which is a discrepancy worth resolving against your own interpreter. The supported stack per the README is Django 4.2, 5.0, 5.1 and 5.2 with djangorestframework 3.14 or later, while the classifiers in setup.py also advertise Django 6.0 and 6.1. Development status is declared as Beta. The tests run in a virtual environment with pytest, and the pinned test dependencies are pytest 9.1.1, pytest-django 4.14.0 and pytest-cov 7.1.0.

bash
python3 -m venv envname
source envname/bin/activate

pip install django
pip install django-rest-framework
pip install -r requirements.txt

py.test

The licence is the one loose end. setup.py declares license BSD and includes the BSD classifier, but the licence file in the repository is a custom one that GitHub could not classify, so the metadata and the file may not say the same thing. The package also ships a py.typed marker and the repository carries a mypy.ini, so type checking is a supported workflow rather than an accident.

Editorial conclusion

drf-writable-nested fits a Django API team that wants one endpoint to create a parent and its children in a single request, and that is willing to write a small serializer tree rather than keep a hand rolled save loop. It does not fit a project that updates nested objects through multipart form-data, and it does not fit a schema with unique fields in a child model unless you add the UniqueFieldsMixin and accept that uniqueness moves to save time. Verify three things before adopting it: the Python and Django versions your project actually runs, since the README and setup.py disagree on the supported Python range, the behaviour of the two known problems in the README against your own payload shape, and the actual contents of LICENSE.md, because the packaging metadata says BSD while the licence file itself is one GitHub could not classify.

Frequently asked questions

What does drf-writable-nested do?

It is a writable nested model serializer for Django REST Framework that allows you to create and update your models with related nested data. OneToOne, ForeignKey, ManyToMany and GenericRelation relations are supported.

How do I install drf-writable-nested?

It is installed from PyPI with pip install drf-writable-nested. The README lists Python 3.9 to 3.13, Django 4.2 through 5.2 and djangorestframework 3.14 or later as the supported stack, while setup.py declares python_requires of 3.10 or later.

How do I handle unique fields on nested models during update?

The package provides a UniqueFieldsMixin that moves UniqueValidator instances from the validation stage to the save stage. It must be applied only to a serializer with unique fields, and it must be listed before NestedCreateMixin or NestedUpdateMixin when both are used.

Can drf-writable-nested update nested fields sent as form-data?

The README documents this as a known problem: updating a model object with nested fields through PUT or PATCH using form-data cannot be performed. The README acknowledges the issue and does not document a workaround for it.

Official sources

  1. beda-software/drf-writable-nested on GitHub
  2. Issues
  3. README
  4. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/beda-software-drf-writable-nested.svg)](https://hysenlabs.com/projects/beda-software-drf-writable-nested)

Was this useful?