Toast Driven

← Back to November 8, 2008

Quick & Dirty Search with Django

There's a lot of options out there for search. You can defer to the big boys like Google, you can go with the enterprise-y solutions like lucene/solr and there's plenty more smaller options, like full-text search within your database engine or smaller engines like Sphinx.

But in the scale of the some of the small sites I've produced, Django has an often overlooked option, the Q object. The Q object allows you to perform more difficult queries without leaving the comfort of Django's ORM layer. It allows lets you add simple search to your site without have to configure and run a separate daemon.

Adding it is easy. We'll start with the following code:

import datetime
from django.db import models


class NewsPost(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField()
    content = models.TextField()
    posted_date = models.DateTimeField(default=datetime.datetime.now)

To add search, we'll create a custom Manager and add the functionality there. This makes the most sense, as it is consistent with other Manager API usage like filter or get.

import datetime
import operator
from django.db import models
from django.db.models import Q


class NewsPostManager(models.Manager):
    def search(self, search_terms):
        terms = [term.strip() for term in search_terms.split()]
        q_objects = []
        
        for term in terms:
            q_objects.append(Q(title__icontains=term))
            q_objects.append(Q(content__icontains=term))
        
        # Start with a bare QuerySet
        qs = self.get_query_set()
        
        # Use operator's or_ to string together all of your Q objects.
        return qs.filter(reduce(operator.or_, q_objects))


class NewsPost(models.Model):
    title = models.CharField(max_length=255)
    slug = models.SlugField()
    content = models.TextField()
    posted_date = models.DateTimeField(default=datetime.datetime.now)
    objects = NewsPostManager()

Now searching your NewsPosts can be done with a simple call like so:

results = NewsPost.objects.search("quick search")

There's tons of ways to make simple improvements to this (skip words, customizable attribute search, aggregating searches between models, etc.) but it makes for a simple way to provide basic search without hassle.

Toast Driven