Faceted search by django-filter and postgres full-text search

Hello! I want to create faceted search and filter.
I use django-filter and postgres full-text search for this. It works good (I mean search and filter). But I don’t know how to build faceted search. In this case, for language, book_type and subject. It will be wonderful if it can count facet. Is it possible? Can you give any advice? Perhaps it is better to do it another way if there will be postgres full-text search?
It takes to me a lot of time. I tried to use only views for this, but it does not work well. Also, I use django-haystack. It works well. But there is no support postgres full-text search.

models.py

class Book(models.Model):
    title = models.CharField(max_length=255)
    author = models.ForeignKey(
        "users.CustomUser",
        on_delete=models.SET_NULL,
        null=True,
        blank=True,
    )
    coauthor = models.ManyToManyField(
        "users.CustomUser",
        blank=True,
        related_name="coauthor",
    )
    abstract = models.TextField(blank=True, null=True)
    content = models.TextField(blank=True, null=True)
    language = models.ForeignKey("Language", on_delete=models.SET_NULL, null=True)
    book_type = models.ForeignKey(
        "Book_type", on_delete=models.SET_NULL, null=True, blank=True
    )
    subject = TreeManyToManyField("Subject")

class Language(models.Model):
    name = models.CharField(max_length=70, null=True, blank=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["-name"]

class Book_type(models.Model):
    name = models.CharField(max_length=70, null=True, blank=True)

    def __str__(self):
        return self.name

    class Meta:
        ordering = ["-name"]

class Subject(MPTTModel):
    name = models.CharField(
        max_length=1000,
        unique=True,
        verbose_name=_("category name"),
        help_text=_("format: required, max-100"),
    )

filters.py

class BookFilter(django_filters.FilterSet):
    search = django_filters.CharFilter(
        method="my_custom_filter",
        widget=TextInput(
            attrs={
                "class": "form-control form-search",
                "placeholder": _("Type to search"),
            }
        ),
    )
    language = django_filters.ModelMultipleChoiceFilter(
        field_name="language",
        queryset=Language.objects.order_by("name"),
        widget=forms.CheckboxSelectMultiple(),
    )
    book_type = django_filters.ModelMultipleChoiceFilter(
        field_name="article_type",
        queryset=Book_type.objects.all(),
        widget=forms.CheckboxSelectMultiple(),
    )
    subject = django_filters.ModelMultipleChoiceFilter(
        field_name="subject",
        queryset=Subject.objects.all(),
        widget=autocomplete.ModelSelect2Multiple(
            url="subject", attrs={"data-placeholder": _("Choose subject")}
        ),
    )

    def my_custom_filter(self, queryset, name, value):
        q = value
        vector = (
            SearchVector("title", weight="A")
            + SearchVector("author__first_name", weight="A")
            + SearchVector("author__last_name", weight="A")
            + SearchVector("coauthor__first_name", weight="A")
            + SearchVector("coauthor__last_name", weight="A")
            + SearchVector("abstract", weight="C")
        )
        return (
            queryset.annotate(
                rank=SearchRank(vector, q),
            )
        )

views.py

def BookListView(request):
    book = Book.objects.all()

    filter = BookFilter(request.GET, queryset=book)

    # pagination
    book = filter.qs
    paginator = Paginator(book, 10)
    page = request.GET.get("page", 1)
    try:
        books = paginator.page(page)
    except PageNotAnInteger:
        books = paginator.page(1)
    except EmptyPage:
        books = paginator.page(paginator.num_pages)

    context = {
        "filter": filter,
        "profile": profile,
        "books": books,
    }

    return render(request, "books.html", context)

Can you show me how you display the Checkbox form in the .html? I’m having problems with this, as I only get the options, but no actualt Checkbox.

Have already read the Simon Willison’s article “Implementing faceted search with Django and PostgreSQL” ?

1 Like

Paolo Melchiorre, hello!
Thanks for your kind reply! I read this article, and it was it that got me started on how it could be done. And also your articles and videos about full-text search. Thank you very much.

1 Like

Lookszz, hello!
I think you should create a new topic because people can more easily find the information they need.
But I’ll answer here, if nobody minds. In my html it looks like this:

books.html

<label for="{{ form.book_type.id_for_label }}">Book Type</label>
  {{ form.book_type  }}