{"id":1999,"library":"django-modelcluster","title":"Django Modelcluster","description":"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.","status":"active","version":"6.4.1","language":"en","source_language":"en","source_url":"https://github.com/wagtail/django-modelcluster","tags":["django","orm","models","relationships","formsets","wagtail"],"install":[{"cmd":"pip install django-modelcluster","lang":"bash","label":"Install stable version"}],"dependencies":[{"reason":"Core dependency as it's a Django extension.","package":"Django","optional":false},{"reason":"Often used with `ClusterTaggableManager` for tagging functionality in Wagtail contexts. Not strictly required for basic `django-modelcluster` usage.","package":"django-taggit","optional":true}],"imports":[{"symbol":"ClusterableModel","correct":"from modelcluster.models import ClusterableModel"},{"note":"ParentalKey is a specialized ForeignKey for in-memory child objects, enabling 'clustering'.","wrong":"from django.db.models import ForeignKey","symbol":"ParentalKey","correct":"from modelcluster.fields import ParentalKey"},{"symbol":"ParentalManyToManyField","correct":"from modelcluster.fields import ParentalManyToManyField"},{"note":"Used for handling forms with clustered models, particularly in Django's admin or Wagtail.","symbol":"ClusterForm","correct":"from modelcluster.forms import ClusterForm"}],"quickstart":{"code":"from django.db import models\nfrom modelcluster.models import ClusterableModel\nfrom modelcluster.fields import ParentalKey\n\nclass Band(ClusterableModel):\n    name = models.CharField(max_length=255)\n\n    def __str__(self):\n        return self.name\n\nclass BandMember(models.Model):\n    band = ParentalKey(\n        'Band', \n        related_name='members',\n        on_delete=models.CASCADE\n    )\n    name = models.CharField(max_length=255)\n\n    def __str__(self):\n        return self.name\n\n# Example usage (in a Django shell or view):\nbeatles = Band(name='The Beatles')\n# Related objects can be assigned to the in-memory 'members' attribute\nbeatles.members = [\n    BandMember(name='John Lennon'),\n    BandMember(name='Paul McCartney'),\n    BandMember(name='George Harrison'),\n    BandMember(name='Ringo Starr'),\n]\n\n# Accessing in-memory members (behaves like a QuerySet subset)\nprint([member.name for member in beatles.members.all()])\n# Output: ['John Lennon', 'Paul McCartney', 'George Harrison', 'Ringo Starr']\n\n# To save the cluster and its members to the database:\n# beatles.save()\n# This would also save all associated BandMember instances via ParentalKey","lang":"python","description":"This example demonstrates defining a `ClusterableModel` (Band) and a related model (`BandMember`) connected via `ParentalKey`. It shows how to create a cluster of related objects in memory and access them before saving them to the database."},"warnings":[{"fix":"Explicitly define `formsets` or `exclude_formsets` within the `Meta` class of your `ClusterForm` to control which child relations have formsets.","message":"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.","severity":"breaking","affected_versions":">=6.0"},{"fix":"Always check the release notes (`CHANGELOG.txt`) for version compatibility and ensure your `django-modelcluster` version aligns with your Django and Python environment.","message":"`django-modelcluster` frequently drops support for older Django and Python versions. For example, v6.4 removed Django 3.2 and Python 3.8 support.","severity":"breaking","affected_versions":"All versions"},{"fix":"Ensure the model referenced by `ParentalKey` (e.g., the 'Band' in `ParentalKey('Band', ...)`) inherits `modelcluster.models.ClusterableModel`.","message":"A `ParentalKey` field must point to a model that inherits from `ClusterableModel`. Failing to do so will result in a Django system check error.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Provide a meaningful `related_name` for all `ParentalKey` fields, or omit it to let Django infer one.","message":"Using `related_name='+'` is not allowed on `ParentalKey` fields, as `ParentalKey` requires an accessor name for its internal mechanisms.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Be aware of these limitations when querying in-memory relations. For complex logic, ensure the parent model and its children are saved, or adapt your code to work with lists/iterators where full queryset functionality isn't available.","message":"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.","severity":"gotcha","affected_versions":"All versions"},{"fix":"Ensure that any objects intended to be related via `ParentalManyToManyField` are persisted to the database before attempting to establish the in-memory relationship with the parent `ClusterableModel`.","message":"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.","severity":"gotcha","affected_versions":"All versions"}],"env_vars":null,"last_verified":"2026-04-09T00:00:00.000Z","next_check":"2026-07-08T00:00:00.000Z"}