Add Full Text Search to Wagtail

Table of Contents

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-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 
  5. Add Bootstrap Theme to Wagtail

More Wagtail articles and eBooks written by me

Objective

By the end of this chapter, you should be able to:

  1. Understand how Wagtail full text search works.
  2. Display search keywords on the search result page.

Backend

Update wagtail_app/settings.py

WAGTAILSEARCH_BACKENDS = {
    'default': {
        'BACKEND': 'wagtail.search.backends.database',
    }
}

Model

Update wagtail_app/blog/models.py

from wagtail.search import index


class PostPage(Page):

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

Notes:

  1. Here we defined search_fields so Wagtail would know which fields need to be processed when indexing.
  2. Do not forget to put from wagtail.search import index at the top of the file.

After this is done, let's run command to build the index.

$ docker-compose up -d
$ docker-compose logs -f

$ docker-compose exec web python manage.py update_index

Notes:

  1. We need to do this manually when we first setup WAGTAILSEARCH_BACKENDS
  2. The command would extract text from the search_fields, and write index to the search backend.
  3. By default, when we do db operation (create page, edit page), the search index would also be updated. That is why the search function can work with latest data even we do not run update_index command.

Let's test in the Django shell.

$ docker-compose exec web python manage.py shell
>>> from wagtail_app.blog.models import PostPage

# run search method to do full text search
>>> PostPage.objects.search('wagtail')
<SearchResults [<PostPage: PostPage3>, <PostPage: PostPage2>, <PostPage: PostPage1>]>

Route

Let's add a route to make our blog support full text search.

Update wagtail_app/blog/models.py

class BlogPage(RoutablePageMixin, Page):
    # code omitted for brevity

    @route(r"^search/$")
    def post_search(self, request, *args, **kwargs):
        search_query = request.GET.get("q", None)
        self.posts = self.get_posts()
        if search_query:
            self.posts = self.posts.search(search_query)
        return self.render(request)

Notes:

  1. We added a route to the BlogPage, which handle the search request.
  2. We get the keywords from the querystring q

Update wagtail_app/templates/blog/components/sidebar.html

{% load blogapp_tags wagtailroutablepage_tags %}

<div class="w-full sm:w-1/3 md:w-1/4 lg:w-4/12 px-2">
  {% if blog_page %}

    <div class="mb-4 border rounded-lg border-opacity-75 border-gray-300 shadow-xl overflow-hidden dark:border-gray-500">
      <div class="bg-gray-100 text-gray-900 px-6 py-4 dark:bg-gray-700 dark:text-gray-400">
        <h4 class="text-base font-medium">Search</h4>
      </div>
      <div class="px-6 py-4">
        <form role="search" method="get" action="/search/">
          <div class="relative text-gray-700">
            <input type="search"
              class="w-full h-10 pl-3 pr-8 text-base placeholder-gray-600 border rounded-lg dark:bg-gray-700 dark:text-white dark:placeholder-gray-400"
              name="q" placeholder="Search&hellip;"
              title="Search for:" />
            <button class="absolute inset-y-0 right-0 flex items-center px-4 text-white bg-blue-500 rounded-r-lg border-blue-500 hover:bg-blue-600" type="submit">Go</button>
          </div>
        </form>
      </div>
    </div>

    {% categories_list %}

    {% tags_list %}

  {% endif %}
</div>

Notes:

  1. In the sidebar, we added a search form above the Category widget.
  2. We can even put the code in a new template file to make the sidebar.html cleaner.
  3. The form action is the url of the post_search route.
  4. When we submit the form, it would send GET request which has url like http://127.0.0.1:8000/search/?q=wagtail

Display Filter Keywords

To display filter keywords on the search result page.

Update wagtail_app/blog/models.py

from django.utils.dateformat import DateFormat
from django.utils.formats import date_format


class BlogPage(RoutablePageMixin, Page):

    @route(r'^tag/(?P<tag>[-\w]+)/$')
    def post_by_tag(self, request, tag, *args, **kwargs):
        self.filter_type = 'tag'
        self.filter_term = tag
        self.posts = self.get_posts().filter(tags__slug=tag)
        return self.render(request)

    @route(r'^category/(?P<category>[-\w]+)/$')
    def post_by_category(self, request, category, *args, **kwargs):
        self.filter_type = 'category'
        self.filter_term = category
        self.posts = self.get_posts().filter(categories__blog_category__slug=category)
        return self.render(request)

    @route(r"^(\d{4})/$")
    @route(r"^(\d{4})/(\d{2})/$")
    @route(r"^(\d{4})/(\d{2})/(\d{2})/$")
    def post_by_date(self, request, year, month=None, day=None, *args, **kwargs):
        self.filter_type = 'date'
        self.filter_term = year
        self.posts = self.get_posts().filter(post_date__year=year)
        if month:
            df = DateFormat(datetime.date(int(year), int(month), 1))
            self.filter_term = df.format('F Y')
            self.posts = self.posts.filter(post_date__month=month)
        if day:
            self.filter_term = date_format(datetime.date(int(year), int(month), int(day)))
            self.posts = self.posts.filter(post_date__day=day)
        return self.render(request)

    @route(r"^search/$")
    def post_search(self, request, *args, **kwargs):
        search_query = request.GET.get("q", None)
        self.posts = self.get_posts()
        if search_query:
            self.filter_term = search_query
            self.filter_type = 'search'
            self.posts = self.posts.search(search_query)
        return self.render(request)

Notes:

  1. We added filter_term to make it represent the search keywords, category or tag
  2. We added filter_type to make it represent the filter type.

Update wagtail_app/templates/blog/blog_page.html

{% extends "base.html" %}

{% load wagtailcore_tags wagtailimages_tags blogapp_tags %}

{% block content %}

  {% if page.filter_term %}
    <div class="px-4 py-3 leading-normal text-blue-700 bg-blue-100 rounded-lg mb-4" role="alert">
      {{ page.filter_type }}: {{ page.filter_term }}
    </div>
  {% endif %}

    ...

{% endblock %}

Notes:

  1. If the page.filter_term is not None, display the filter messages.

Wagtail Tutorial Series:

To learn more about Wagtail CMS, please check Build Blog With Wagtail CMS (4.0.0)

  1. Create Wagtail Project
  2. Modern Frontend Techs for Wagtail
  3. Dockerizing Wagtail App
  4. Add Blog Models to Wagtail
  5. How to write Wagtail page template
  6. Create Stylish Wagtail Pages with Tailwind CSS
  7. How to use StreamField in Wagtail
  8. Wagtail Routable Page
  9. Add Pagination Component to Wagtail
  10. Customize Wagtail Page URL
  11. Add Full Text Search to Wagtail
  12. Add Markdown Support to Wagtail
  13. Add LaTeX Support & Code Highlight In Wagtail
  14. How to Build Form Page in Wagtail
  15. How to Create and Manage Menus in Wagtail
  16. Wagtail SEO Guide
  17. Online Demo http://wagtail-blog.accordbox.com/
  18. Source code: https://github.com/AccordBox/wagtail-tailwind-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 
  5. Add Bootstrap Theme to Wagtail

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.

Django SaaS Template

It aims to save your time and money building your product

Learn More

Build Jamstack web app with Next.js and Wagtail CMS.

Read More
© 2018 - 2024 AccordBox