Django Modelcluster
django-modelcluster is a Django extension that allows working with 'clusters' of related models as a single unit, independently of the database. It introduces `ParentalKey` and `ClusterableModel` to enable in-memory manipulation of related objects, which is particularly useful for features like previews and revisions in content management systems like Wagtail. The current version is 6.4.1, and it maintains a consistent release schedule, often aligning with Django and Python version support.
Common errors
-
ParentalKey must point to a subclass of ClusterableModel.
cause A `ParentalKey` field is defined in a model that attempts to link to another model which does not inherit from `modelcluster.models.ClusterableModel`. `ParentalKey` relies on the parent model having `ClusterableModel`'s in-memory object handling capabilities.fixEnsure that the model referenced by the `ParentalKey` (the 'parent' model in the relationship) inherits from `modelcluster.models.ClusterableModel`. For example, `class MyParentModel(ClusterableModel): ...` -
related_name='+' is not allowed on ParentalKey fields
cause Unlike regular Django `ForeignKey` fields where `related_name='+'` can be used to explicitly disable the reverse relation, `ParentalKey` fields require a valid `related_name` (or none, allowing Django to generate a default). Using `related_name='+'` is not supported or meaningful for `ParentalKey`'s in-memory relation management.fixRemove `related_name='+'` from the `ParentalKey` definition or provide a specific, descriptive `related_name` attribute. For instance, `ParentalKey('ParentModel', related_name='children', on_delete=models.CASCADE)`. -
ValueError: '...' does not resolve to an item that supports prefetching - this is an invalid parameter to prefetch_related().
cause `django-modelcluster`'s `ParentalManyToManyField` does not fully support Django's `prefetch_related` mechanism for in-memory object clusters, leading to this `ValueError` when `prefetch_related` is called on it. While `ParentalManyToManyField` manages the *relations* in memory, the related objects themselves still typically need to exist in the database for standard prefetching to work.fixDirect `prefetch_related` for `ParentalManyToManyField` is generally not supported for in-memory relations. Instead, ensure the related objects are saved to the database independently if they need to be prefetched, or manually fetch and attach them after retrieving the parent `ClusterableModel` instance. -
IntegrityError: null value in column "..." violates not-null constraint
cause This error occurs when a child object, intended to be managed by a `ParentalKey`, is attempted to be saved with a `NULL` value for its foreign key pointing to a parent that has not yet been saved to the database. While `ParentalKey` defers database saving until the parent is saved, this error can still occur if child objects are created or assigned incorrectly (e.g., trying to save children independently or not associating them with the parent's in-memory manager before the parent is saved).fixEnsure all child objects connected via `ParentalKey` are assigned to the parent `ClusterableModel` instance (e.g., `parent_instance.children_related_name = [child1, child2]`) *before* calling `parent_instance.save()`. This allows `django-modelcluster` to manage the relationships in memory and save them as a single unit when the parent is committed to the database.
Warnings
- breaking In `ClusterForm` (v6.0 and later), child formsets are no longer built by default if neither `formsets` nor `exclude_formsets` is specified in the Meta class. This changes previous behavior where all child relations would automatically get a formset.
- breaking `django-modelcluster` frequently drops support for older Django and Python versions. For example, v6.4 removed Django 3.2 and Python 3.8 support.
- gotcha A `ParentalKey` field must point to a model that inherits from `ClusterableModel`. Failing to do so will result in a Django system check error.
- gotcha Using `related_name='+'` is not allowed on `ParentalKey` fields, as `ParentalKey` requires an accessor name for its internal mechanisms.
- gotcha While `django-modelcluster` provides a `QuerySet`-like API for in-memory child objects, it's a 'fake' QuerySet with limitations. Advanced queryset operations (e.g., complex `order_by` traversals, `distinct()` before v6.3, or raw SQL queries) may not work as expected or are not supported on unsaved instances, particularly in contexts like content previews.
- gotcha For `ParentalManyToManyField`, only the *relationships* between the parent and the related objects are managed in memory. The *related objects themselves* (e.g., `Actor` instances in a `Movie.actors = ParentalManyToManyField(Actor)`) must already exist in the database before they can be associated in memory with the parent.
Install
-
pip install django-modelcluster
Imports
- ClusterableModel
from modelcluster.models import ClusterableModel
- ParentalKey
from django.db.models import ForeignKey
from modelcluster.fields import ParentalKey
- ParentalManyToManyField
from modelcluster.fields import ParentalManyToManyField
- ClusterForm
from modelcluster.forms import ClusterForm
Quickstart
from django.db import models
from modelcluster.models import ClusterableModel
from modelcluster.fields import ParentalKey
class Band(ClusterableModel):
name = models.CharField(max_length=255)
def __str__(self):
return self.name
class BandMember(models.Model):
band = ParentalKey(
'Band',
related_name='members',
on_delete=models.CASCADE
)
name = models.CharField(max_length=255)
def __str__(self):
return self.name
# Example usage (in a Django shell or view):
beatles = Band(name='The Beatles')
# Related objects can be assigned to the in-memory 'members' attribute
beatles.members = [
BandMember(name='John Lennon'),
BandMember(name='Paul McCartney'),
BandMember(name='George Harrison'),
BandMember(name='Ringo Starr'),
]
# Accessing in-memory members (behaves like a QuerySet subset)
print([member.name for member in beatles.members.all()])
# Output: ['John Lennon', 'Paul McCartney', 'George Harrison', 'Ringo Starr']
# To save the cluster and its members to the database:
# beatles.save()
# This would also save all associated BandMember instances via ParentalKey