Skip to content

Instantly share code, notes, and snippets.

@jainmickey
Last active April 2, 2021 20:09
Show Gist options
  • Save jainmickey/db0378a6de3fcda9e3ebcb0a4d417e0f to your computer and use it in GitHub Desktop.
Save jainmickey/db0378a6de3fcda9e3ebcb0a4d417e0f to your computer and use it in GitHub Desktop.
# Standard Library
import re
# Third Party Stuff
from django.db import models
from django.contrib.postgres.indexes import GinIndex
from django.contrib.postgres.search import (
SearchQuery,
SearchRank,
SearchVector,
SearchVectorField,
TrigramSimilarity,
)
from django.db.models import CharField, F, Func, Value
from django.db.models.signals import post_save
from django.dispatch import receiver
def prepare_search_term(term: str) -> str:
"""Sanitize the input term for a search using postgres to_tsquery.
Cleans a search string to something acceptable for use with to_tsquery.
Appends ':*' so that partial matches will also be returned.
Args:
term: the search term to be cleaned and prepared
Returns:
the prepared search string
"""
query = re.sub(r"[!\'()|&]", " ", term).strip()
if query:
query = re.sub(r"\s+", " & ", query)
query += ":*"
return query
class ModelManager(models.Manager):
search_vectors = SearchVector("label", weight="A", config="english")
def search(self, text):
search_query = SearchQuery(
prepare_search_term(text), config="english", search_type="raw"
)
search_rank = SearchRank(F("search_vector"), search_query)
trigram_similarity = TrigramSimilarity("label", text)
return (
self.get_queryset()
.filter(search_vector=search_query)
.annotate(rank=search_rank + trigram_similarity)
.order_by("-rank")
)
class MyModel(TimeStampedUUIDModel):
# Fields
# =======================
label = models.CharField(_("label"), max_length=100)
search_vector = SearchVectorField(null=True)
objects = ModelManager()
class Meta:
ordering = ("-created_at",)
indexes = [GinIndex(fields=["search_vector",])]
# Signals does not get triggered on bulk save or update
@receiver(post_save, sender=MyModel)
def update_search_vector(sender, instance, **kwargs):
MyModel.objects.filter(pk=instance.pk).update(
search_vector=SearchVector(
Func(F("label"), Value("."), Value(" "), function="replace")
)
)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment