Django ORM Model Search Ordered By Search Term Relevance
Use this simple method of Django ORM search for a quick and easy site search.
Search Function
This function uses stem_phrase from a common Python Porter Stemmer implementation.
from django.db.models import Q, When, ExpressionWrapper, F, Value, FloatField, Case
from porter_stemmer import stem_phrase
def search(query_set, query):
keywords = stem_phrase(query).split()
if len(keywords) == 0:
return query_set.none()
def build_q(_keyword):
return Q(keywords_stemmed__icontains=_keyword)
q = build_q(keywords[0])
for keyword in keywords[1:]:
q = q | build_q(keyword)
query_set = query_set.filter(q)
cases = []
length = len(keywords)
if length > 4:
length = 4
for words in range(1, length):
for subset in itertools.combinations(keywords, words):
if len(subset) != 0:
q = build_q(subset[0])
for item in subset[1:]:
q = q & build_q(item)
cases.append(When(
q,
then=ExpressionWrapper(
Value(words),
output_field=FloatField()
)
))
cases.reverse()
query_set = query_set.annotate(value=Case(*cases, default=Value(0), output_field=FloatField()))
results = []
for r in query_set:
for keyword in keywords:
r.value += r.keywords_stemmed.count(' {} '.format(keyword)) * .01
results.append(r)
results = sorted(results, key=lambda result: -result.value)
return results
Model Setup
class Post(models.Model):
title = models.CharField(max_length=80)
content = models.TextField()
keywords_stemmed = models.TextField(default='', blank=True)
def save(self, *args, **kwargs):
# Add all fields you would like to pull keywords from here
self.keywords_stemmed = ' ' + ' '.join([
stem_phrase(self.title.lower()),
stem_phrase(self.content.lower()),
])
return super().save(*args, **kwargs)
Usage
def view_func(request, **kwargs):
results = search(Post.objects.all(), request.GET.get('query'))