Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel

Michael Yin

Last updated on December 11 2021

Table of Contents

Wagtail Tutorial Series:

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL
  10. Add Full Text Search to Wagtail
  11. Add Markdown Support to Wagtail
  12. Add LaTeX Support & Code Highlight In Wagtail
  13. How to Build Form Page in Wagtail
  14. How to Create and Manage Menus in Wagtail
  15. Wagtail SEO Guide
  16. Source code: https://github.com/AccordBox/wagtail-bootstrap-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 

More Wagtail articles and eBooks written by me

Introduction

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),
    ]


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

    panels = [
        FieldPanel('name'),
        FieldPanel('slug'),
    ]

    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 = [
        SnippetChooserPanel('blog_category'),
    ]

    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).

Conclusion

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.

Wagtail Tutorial Series:

  1. Create Wagtail Project
  2. Dockerizing Wagtail App
  3. Add Blog Models to Wagtail
  4. How to write Wagtail page template
  5. Add Bootstrap Theme to Wagtail
  6. How to use StreamField in Wagtail
  7. Wagtail Routable Page
  8. Add pagination component to Wagtail
  9. Customize Wagtail Page URL
  10. Add Full Text Search to Wagtail
  11. Add Markdown Support to Wagtail
  12. Add LaTeX Support & Code Highlight In Wagtail
  13. How to Build Form Page in Wagtail
  14. How to Create and Manage Menus in Wagtail
  15. Wagtail SEO Guide
  16. Source code: https://github.com/AccordBox/wagtail-bootstrap-blog

Wagtail Tips:

  1. Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel
  2. Wagtail Tip #2: How to Export & Restore Wagtail Site

Write style in Wagtail:

  1. How to use SCSS/SASS in your Django project (Python Way)
  2. How to use SCSS/SASS in your Django project (NPM Way)

Other Wagtail Topics:

  1. How to make Wagtail project have good coding style
  2. How to do A/B Testing in Wagtail CMS 
  3. How to build a landing page using Wagtail CMS 
  4. How to support multi-language in Wagtail CMS 

More Wagtail articles and eBooks written by me

Launch Products Faster with Django

SaaS Hammer helps you launch products in faster way. It contains all the foundations you need so you can focus on your product.

Michael Yin

Michael is a Full Stack Developer from China who loves writing code, tutorials about Django, and modern frontend tech.

He has published some ebooks on leanpub and tech course on testdriven.io.

He is also the founder of the AccordBox which provides the web development services.

Table of Contents

Django SaaS Template

It aims to save your time and money building your product, developed by Michael Yin

Learn More

This book will teach you how to build a SPA (single-page application) with React and Wagtail CMS

Read More

Subscribe

Get notified about new great Web Development Tutorial