Wagtail/Django Taggit - Unique Tag pool for each set of child pages

For the sake of example, let's say we wanted to create multiple blogs, but in the same app. Each blog has blog posts that are children of each respective blog. Is there away to set up Taggit so that each blogs pool of tags remain separate from each other. Say if one is a food blog, and the other is a car blog, you don't want auto-completion suggestions from one showing up on the other.

I have attempted the "Custom Tag Models" example on the Wagtail docs here. With no luck.

I've also tried the suggestion on this stackoverflow question.

Here is the code from the Wagtail docs above:

from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey
from taggit.models import TagBase, ItemBase

class BlogTag(TagBase):
    class Meta:
        verbose_name = "blog tag"
        verbose_name_plural = "blog tags"


class TaggedBlog(ItemBase):
    tag = models.ForeignKey(
        BlogTag, related_name="tagged_blogs", on_delete=models.CASCADE
    )
    content_object = ParentalKey(
        to='demo.BlogPage',
        on_delete=models.CASCADE,
        related_name='tagged_items'
    )

class BlogPage(Page):
    ...
    tags = ClusterTaggableManager(through='demo.TaggedBlog', blank=True)

Thanks in advance!

This how it works on my site - 2 blog page classes (TechBlogDetailPage & PersonalBlogDetailPage) that both inherit a base blog page class (BlogDetailPage). There is a tag class for each type that creates a parental key on the 2 blog page classes (not the inherited model).

# tags.py
from django.db import models
from modelcluster.models import ParentalKey
from taggit.models import TaggedItemBase

class TechBlogPageTag(TaggedItemBase):
    content_object = ParentalKey(
        'TechBlogDetailPage',
        related_name='tagged_items',
        on_delete=models.CASCADE,
    )

class PersonalBlogPageTag(TaggedItemBase):
    content_object = ParentalKey(
        'PersonalBlogDetailPage',
        related_name='tagged_items',
        on_delete=models.CASCADE,
    )
# models.py
from .tags import PersonalBlogPageTag, TechBlogPageTag

class TechBlogDetailPage(BlogDetailPage):
    parent_page_types = ['blog.TechBlogListingPage']
    ....
    tags = ClusterTaggableManager(through=TechBlogPageTag, blank=True)
    ....

class PersonalBlogDetailPage(BlogDetailPage):
    parent_page_types = ['blog.PersonalBlogListingPage']
    ....
    tags = ClusterTaggableManager(through=PersonalBlogPageTag, blank=True)
    ....

Tags created in Tech Blog Pages are only available on Tech Blog Pages and not available on Personal Blog Pages & vice versa.

Here is how we create per-site tags. Perhaps you can adapt it for your use case.

from django.db import models
from modelcluster.contrib.taggit import ClusterTaggableManager
from modelcluster.fields import ParentalKey, ParentalManyToManyField
from taggit.models import TaggedItemBase, TagBase
from wagtail.models import Page, Site


class SiteSpecificTag(TagBase):
    """
    Our custom Tag model, which lets us associate tags with a specific Site.
    """
    site = models.ForeignKey(Site, related_name='tags', on_delete=models.CASCADE)
    # We need to redefine the name field to make it non-unique.
    name = models.CharField(verbose_name='Name', max_length=100)
    # We prefix each slug with the hostname of the Site, so it can be quite a bit longer than the tag's name.
    slug = models.SlugField(verbose_name='Slug', unique=True, max_length=200)

    class Meta:
        verbose_name = 'Tag'
        verbose_name_plural = 'Tags'

    def slugify(self, tag, i=None):
        """
        We override TagBase.slugify() because the 'slug' field is unique, so we need to prefix all slugs with the
        Site's hostname.
        """
        slug = "{}~{}".format(self.site.hostname, slugify(unidecode(tag)))
        if i is not None:
            slug += "_{}".format(i)
        return slug


class SiteNewsTaggedItem(TaggedItemBase):
    # Unlike the image and document *TaggedItem classes, which use a GenericForeignKey for content_object,
    # Page tags need to use a ParentalKey.
    content_object = ParentalKey('core.SiteNewsPage', related_name='tagged_pages', on_delete=models.CASCADE)
    tag = models.ForeignKey(SiteSpecificTag, related_name='%(app_label)s_%(class)s_items', on_delete=models.CASCADE)


class SiteNewsPage(Page):
    body = RichTextField()
    tags = ClusterTaggableManager(
        through=SiteNewsTaggedItem,
        blank=True,
        help_text='Enter comma-separated tags for this article. Existing tags will autocomplete as you type them.'
    )
Back to Top