wagtail-tips-why-you-should-avoid-using-parentalmanytomanyfield-fields.jpg

Wagtail Tip #1: How to replace ParentalManyToManyField with InlinePanel

Last updated on by michaelyin

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.

If you still have question about this part or your project, just send me message and I will check if I can help you.

Thanks.

Wagtail Ebook

For people who like to read ebook instead of blog posts, I have published a book on leanpub´╝îwhere you can get pdf, epub, mobi version of this Wagtail book Build Blog With Wagtail CMS.

Send Me Message

Tell me more about your project and see if I can help you.

Contact Me