Model with M2M Relations¶
Learn how to create flexible many-to-many relationships between your content models.
This tutorial builds on the Article with APPhooks and CMS Plugins tutorial.
Overview¶
We’ll add authors to our articles using a
RelationField:
Create a
Persongrouper/content pair to represent authorsDeclare an
authorsrelation on theArticlegrouperDisplay authors on article pages
The key idea: relations are declared on the grouper (Article), target
another grouper (Person), and are anchored to stable primary keys so
they survive versioning.
Step 1: Create the Person Model¶
Add to my_content/models.py:
from django.db import models
from djangocms_custom_content.models import (
AbstractCustomGrouper,
AbstractCustomContent,
)
# Existing Article and ArticleContent models...
# (from the basic_setup tutorial)
class Person(AbstractCustomGrouper):
"""An author or contributor."""
class Meta:
verbose_name = "Author"
verbose_name_plural = "Authors"
def __str__(self):
person_content = self.get_admin_content()
return person_content.full_name if person_content else "Unknown"
class PersonContent(AbstractCustomContent):
"""Author profile information."""
person = models.ForeignKey(Person, on_delete=models.CASCADE)
full_name = models.CharField(max_length=200)
bio = models.TextField(blank=True)
avatar = models.ImageField(upload_to="authors/", null=True, blank=True)
email = models.EmailField(blank=True)
class CMSConfig:
enable_versioning = True
def __str__(self):
return self.full_name
Step 3: Register Person with Admin¶
Add to my_content/admin.py:
Person is a grouper, so register it with the grouper admin pattern (see
Register the grouper admin). For the authors autocomplete on the Article
admin to find people, PersonAdmin must define search_fields:
from cms.admin.utils import GrouperModelAdmin
from django.contrib import admin
from djangocms_custom_content.admin import CustomGrouperAdminMixin
from .models import Person, PersonContent
@admin.register(Person)
class PersonAdmin(CustomGrouperAdminMixin, GrouperModelAdmin):
content_model = PersonContent
list_display = ("content__full_name", "content__email")
search_fields = ("content__full_name", "content__email")
Step 5: Create Migrations¶
python manage.py makemigrations my_content
python manage.py migrate my_content
Key Concepts¶
RelationField
Declaring authors = RelationField("my_content.Person", ...) on Article:
Creates a dedicated through table anchored to grouper primary keys
Installs a forward
authorsaccessor onArticleInstalls a reverse
authored_articlesaccessor onPerson(because ofrelated_name) —Persondeclares nothing itself
article.authors.all() # Person groupers for this article
person.authored_articles.all() # Article groupers by this person
Why the grouper?
One Person can be linked to many Articles, and vice versa
The same Person grouper can be targeted by other content types too
Because edges store grouper primary keys, they survive version copies
Next Steps¶
Learn more in the Set Up Many-to-Many Relations how-to guide
Explore Relations Explained to understand how relations work under the hood
Check Reference for the complete API reference