Indexing or filtering Wagtail blog posts by MULTIPLE blog tags

Viewed 857

I created a basic Wagtail blog page using Wagtail's tutorial. However, now I want to create a filter on the blog page that allows me to index blog pages by MULTIPLE blog tags.

Wagtail already comes with Django REST Framework and Django-Taggit. Is there an easy method to creating a filter in models.py and HTML template using the prepackaged dependencies?

If not, what other methods can I use to index Wagtail's blog pages by multiple blog tags?

from django.db import models

from modelcluster.fields import ParentalKey
from modelcluster.contrib.taggit import ClusterTaggableManager
from taggit.models import TaggedItemBase

from wagtail.core.models import Page, Orderable
from wagtail.core.fields import RichTextField
from wagtail.admin.edit_handlers import FieldPanel, InlinePanel, MultiFieldPanel
from wagtail.images.edit_handlers import ImageChooserPanel
from wagtail.search import index


class BlogIndexPage(Page):
    intro = RichTextField(blank=True)

    def get_context(self, request):
        context = super().get_context(request)
        blogpages = self.get_children().live().order_by('-first_published_at')
        context['blogpages'] = blogpages
        return context


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


class BlogPage(Page):
    date = models.DateField("Post date")
    intro = models.CharField(max_length=250)
    body = RichTextField(blank=True)
    tags = ClusterTaggableManager(through=BlogPageTag, blank=True)

    def main_image(self):
        gallery_item = self.gallery_images.first()
        if gallery_item:
            return gallery_item.image
        else:
            return None

    search_fields = Page.search_fields + [
        index.SearchField('intro'),
        index.SearchField('body'),
    ]

    content_panels = Page.content_panels + [
        MultiFieldPanel([
            FieldPanel('date'),
            FieldPanel('tags'),
        ], heading="Blog information"),
        FieldPanel('intro'),
        FieldPanel('body'),
        InlinePanel('gallery_images', label="Gallery images"),
    ]


class BlogTagIndexPage(Page):

    def get_context(self, request):

        # Filter by tag
        tag = request.GET.get('tag')
        blogpages = BlogPage.objects.filter(tags__name=tag)

        # Update template context
        context = super().get_context(request)
        context['blogpages'] = blogpages
        return context


class BlogPageGalleryImage(Orderable):
    page = ParentalKey(BlogPage, on_delete=models.CASCADE, related_name='gallery_images')
    image = models.ForeignKey('wagtailimages.Image', on_delete=models.CASCADE, related_name='+')
    caption = models.CharField(blank=True, max_length=250)

    panels = [
        ImageChooserPanel('image'),
        FieldPanel('caption'),
    ]
2 Answers

The url query string parameters might occur more than once. Eg:

https://domain.tld/path/to/your/blog_tag_index_page/?tag=foo&tag=bar&tag=ni

The way to get multiple values form the request is with request.GET.getlist('tag')

def get_context(self, request): 
    tags = request.GET.getlist('tag')
    blogpages = BlogPage.objects.filter(tags__name__in=tags)

    # Update template context
    context = super().get_context(request)
    context['blogpages'] = blogpages
    return context

Next, construct the url in the frontend. Create a from with method get and checkboxes. The checkboxes should all have the same name="tag" attribute.

<!DOCTYPE html>
<html>
<body>
<form action="." method="get">  
  <label>
      <input type="checkbox" name="tag" value="foo">
      Foo
  </label><br>

  <label>
      <input type="checkbox" name="tag" value="bar">
      Bar
  </label><br>

  <label>
      <input type="checkbox" name="tag" value="ni">
      Ni
  </label><br>

  <input type="submit" value="Submit">
</form>
</body>
</html>

If you do not want checkboxes you can create some other interface and make Javascript update the url.

Of course, add all_tags to your page context and iterate over all_tags to get the desired html.

{% for tag in all_tags %}
   <label>
      <input type="checkbox" name="tag" value="{{ tag }}">
      {{ tag }}
  </label><br>
{% endfor %}

This is untested code, but in theory it should work TM.

Happy coding.

Well first of all, you are not using the rest framework. I'm not sure why you mention that.

Do you want to filter on the front or in the admin? On the front you can add a button adding ?filter=yyyyy and you can catch that context in the get_context part. There you simple filter on it.

Related