Skip to main content

If you had a data model like this:

class Band(models.Model):
    name = models.CharField(max_length=255)

class BandMember(models.Model):
    band = models.ForeignKey('Band', related_name='members', on_delete=models.CASCADE)
    name = models.CharField(max_length=255)

wouldn’t it be nice if you could construct bundles of objects like this, independently of the database:

beatles = Band(name='The Beatles')
beatles.members = [
    BandMember(name='John Lennon'),
    BandMember(name='Paul McCartney'),
]

Unfortunately, you can’t. Objects need to exist in the database for foreign key relations to work:

IntegrityError: null value in column "band_id" violates not-null constraint

But what if you could? There are all sorts of scenarios where you might want to work with a ‘cluster’ of related objects, without necessarily holding them in the database: maybe you want to render a preview of the data the user has just submitted, prior to saving. Maybe you need to construct a tree of things, serialize them and hand them off to some external system. Maybe you have a workflow where your models exist in an incomplete ‘draft’ state for an extended time, or you need to handle multiple revisions, and you don’t want to redesign your database around that requirement.

django-modelcluster extends Django’s foreign key relations to make this possible. It introduces a new type of relation, ParentalKey, where the related models are stored locally to the ‘parent’ model until the parent is explicitly saved. Up to that point, the related models can still be accessed through a subset of the QuerySet API:

from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey


class Band(ClusterableModel):
    name = models.CharField(max_length=255)

class BandMember(models.Model):
    band = ParentalKey('Band', related_name='members', on_delete=models.CASCADE)
    name = models.CharField(max_length=255)


>>> beatles = Band(name='The Beatles')
>>> beatles.members = [
...     BandMember(name='John Lennon'),
...     BandMember(name='Paul McCartney'),
... ]
>>> [member.name for member in beatles.members.all()]
['John Lennon', 'Paul McCartney']
>>> beatles.members.add(BandMember(name='George Harrison'))
>>> beatles.members.count()
3
>>> beatles.save()  # only now are the records written to the database

For more examples, see the unit tests.

Many-to-many relations

For many-to-many relations, a corresponding ParentalManyToManyField is available:

from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalManyToManyField

class Movie(ClusterableModel):
    title = models.CharField(max_length=255)
    actors = ParentalManyToManyField('Actor', related_name='movies')

class Actor(models.Model):
    name = models.CharField(max_length=255)


>>> harrison_ford = Actor.objects.create(name='Harrison Ford')
>>> carrie_fisher = Actor.objects.create(name='Carrie Fisher')
>>> star_wars = Movie(title='Star Wars')
>>> star_wars.actors = [harrison_ford, carrie_fisher]
>>> blade_runner = Movie(title='Blade Runner')
>>> blade_runner.actors.add(harrison_ford)
>>> star_wars.actors.count()
2
>>> [movie.title for movie in harrison_ford.movies.all()]  # the Movie records are not in the database yet
[]
>>> star_wars.save()  # Star Wars now exists in the database (along with the 'actor' relations)
>>> [movie.title for movie in harrison_ford.movies.all()]
['Star Wars']

Note that ParentalManyToManyField is defined on the parent model rather than the related model, just as a standard ManyToManyField would be. Also note that the related objects - the Actor instances in the above example - must exist in the database before being associated with the parent record. (The ParentalManyToManyField allows the relations between Movies and Actors to be stored in memory without writing to the database, but not the Actor records themselves.)

Introspection

If you need to find out which child relations exist on a parent model - to create a deep copy of the model and all its children, say - use the modelcluster.models.get_all_child_relations function:

>>> from modelcluster.models import get_all_child_relations
>>> get_all_child_relations(Band)
[<RelatedObject: tests:bandmember related to band>, <RelatedObject: tests:album related to band>]

This includes relations that are defined on any superclasses of the parent model.

To retrieve a list of all ParentalManyToManyFields defined on a parent model, use modelcluster.models.get_all_child_m2m_relations:

>>> from modelcluster.models import get_all_child_m2m_relations
>>> get_all_child_m2m_relations(Movie)
[<modelcluster.fields.ParentalManyToManyField: actors>]

Release files for django-modelcluster 6.5

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-modelcluster 6.5
File Size Uploaded
django_modelcluster-6.5.tar.gz 29.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for django-modelcluster 6.5
File Interpreter ABI Platform
django_modelcluster-6.5-py3-none-any.whl Python 3 none any Details

Total release size: 59.6 kB

Release files / django_modelcluster-6.5.tar.gz

Download URL django_modelcluster-6.5.tar.gz
Size 29.7 kB
Tags Source
SHA-256 checksum
How to use checksums
459cbf0fb3bbc8c15ea9a2a062abc0d1ef935a38e04aa8ac3cb241c826bbc6dd
BLAKE2b-256 checksum
How to use checksums
340fc5fd0c280a10224d619325783bfaab5a54903f82340e41ae21ab3127ebc9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 1, 2026.

Transparency log

Release files / django_modelcluster-6.5-py3-none-any.whl

Download URL django_modelcluster-6.5-py3-none-any.whl
Size 29.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
469b380a294027d5211d0e5d5746e0381f445b058d2229977a58fe0f1c58a596
BLAKE2b-256 checksum
How to use checksums
cd57cdcdea9a56d114e9217d52ce9a8af19ad7fe8a0996faf8480d3f286c02e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.12

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.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on May 1, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

6.5 This release

2 release files

6.4.1

2 release files

6.4

2 release files

6.3

2 release files

6.2.1

2 release files

6.2

2 release files

6.1

2 release files

6.0

2 release files

5.3

2 release files

5.2

2 release files

5.1

2 release files

5.0.2

2 release files

5.0.1

2 release files

5.0

2 release files

4.4.1

2 release files

4.4

2 release files

4.3

2 release files

4.2

1 release file

4.1

1 release file

4.0

1 release file

3.1

1 release file

3.0.1

1 release file

3.0

1 release file

2.0

1 release file

1.1

1 release file

1.0

1 release file

0.6.2

1 release file

0.6.1

1 release file

0.5

1 release file

0.4

1 release file

0.3

1 release file

0.2

1 release file

0.1

1 release file

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