Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel

Last updated on Jan 20 2019 by Michael Yin


After reading this Wagtail tip article, you will get:

  1. The problem ParentalManyToManyField might bring to your project.

  2. How to replace ParentalManyToManyField using Wagtail InlinePanel

Step 1 - Add ParentalManyToManyField to your project

In Wagtail doc, ParentalManyToManyField is imported to help user build relationship between category and page. Below is the code snippet

class PostPage(Page):
    categories = ParentalManyToManyField('blog.BlogCategory', blank=True)

    content_panels = Page.content_panels + [
        FieldPanel('categories', widget=forms.CheckboxSelectMultiple),

class BlogCategory(models.Model):
    name = models.CharField(max_length=255)
    slug = models.SlugField(unique=True, max_length=80)

    panels = [

    def __str__(self):
        return self.name

    class Meta:
        verbose_name = "Category"
        verbose_name_plural = "Categories"

As you can see, we register BlogCategory as Wagtail snippet so we can add, edit in Wagtail snippet admin.

The magic here is the categories = ParentalManyToManyField('blog.BlogCategory', blank=True) and code in content_panels, now we can manage category of the post page using checkbox list.

Step 2 - Find out the problem

Cheers, now you have made Category work in your Wagtail project. but there are two problems with this solution.

  1. If the category list is very long, is there a way to solve this problem? Can we just add category that we want to choose? What if you want to do nested Category?

  2. If you want to migrate the data of your Wagtail project to another place using Django dumpdata&loaddata, you will find out the category are gone even there is no error during the process.

To clarify the second point, you can do this

./manage.py dumpdata --natural-foreign --indent 2 -e auth.permission -e contenttypes -e wagtailcore.groupcollectionpermission -e wagtailimages.rendition -e sessions > data.json 
# config to use new db
./manage.py migrate
./manage.py loaddata data.json
./manage.py runserver

Then you can check in Wagtail admin, you will see the category of the post is gone.

Note: You can get a Github project at the bottom of this article to test on your own.

If you check the data.json by yourself, you will see even we use natual-foreign to dump the data, the category info of page is still pk. I think that is why the loaddata fail here.

The only way to solve this problem is to write code to migrate data.

Step 3 - How to replace ParentalManyToManyField

What if you want to replace ParentalManyToManyField? Here I will show you how:

class PostPage(Page):
    content_panels = Page.content_panels + [
        InlinePanel('categories2', label='category'),

class BlogPageBlogCategory(models.Model):
    page = ParentalKey('blog.PostPage', on_delete=models.CASCADE, related_name='categories2')
    blog_category = models.ForeignKey(
        'blog.BlogCategory', on_delete=models.CASCADE, related_name='blog_pages')

    panels = [

    class Meta:
        unique_together = ('page', 'blog_category')

Here we create BlogPageBlogCategory as intermediate model, we can also add other fields as we like in the future.

The page is ForeignKey to our PostPage and blog_category is ForeignKey to BlogCategory.

unique_together here make sure no duplicate category are saved for one post.

Note: I use field name categories2 to distinguish with previous categoryies, but you should replace when copy&paste.

After you are done, please migrate your database and modify your template file to make that work.

If you have problem in this part, just feel free to contact me and I'd like to help if I have time.

Step 4 - Check again in Wagtail admin

With InlinePanel, now category is more flexible, and we can add more settings to let us search, add category , or reorder category.

What is more, if you want to build nested Category in your Wagtail project, you can also use this way (I will talk about it in another blog).


You can get all source code of this article here Wagtail ParentalManyToManyField Example

In this Wagtail tip, we learned why we need to avoid using ParentalManyToManyField and how to replace by creating intermediate model and InlinePanel.

You can also check the full list of my wagtail tutorial here wagtail tutorial series

Michael is a passionate Python developer from China and love writing code, articles about Django and Wagtail CMS.

Michael Yin

Full Stack Developer

Need any Help in your Project?

Sign up to our newsletter