This is why you should use Django-Watson

Ruhshan Ahmed Abir
4 min readSep 3, 2017

--

In developing a django project its very common to use queryset filters with various combination of parameters to retrieve one or more objects of interest. Retrieving objects using filter is done in the views or model managers and parameters are determined to meet the implementation of business logic behind the scene. For example, if you are building a blog you may need to write filters like this:

BlogPost.objects.filter(author_id=34)

To retrieve all posts by someone whose author_id is 34. To find only published posts by the same author you may write a filter like:

BlogPost.objects.filter(author_id=34, is_published=True)

If you want to find all posts that has the word “Flood” in its title you would do:

BlogPost.objects.filter(title__contains=”Flood”)

Right? Based on the scenarios you may also need to use F() or for more complex situations Q() lookups.

What if, you want to add a search bar in your project and you will filter results based on search string entered by user. You will need to breakup the search string to individual words then look up each word in relevant fields and finally do something like this:

word_list = “Some funny string”BlogPost.objects.filter(
reduce(operator.and_,
(Q(author_name__icontains=w) for w in word_list)) |
reduce(operator.and_,
(Q(title__icontains=w) for w in word_list))
reduce(operator.and_,
(Q(content__icontains=w) for w in word_list))
)

Looks pretty right? Someday you may need to work with a model containing many more fields. Then you’ll have to write bigger chunk of codes, deal with bigger and/or bugs, get bigger performance worries when database grows for larger queries.
What if, you could write the above chunk of code within one line? You surely can do by removing all the newlines, but wouldn’t it be great getting same results with some additional useful infos using lesser code? Like this:

search_result = SomeMethod(SomeModel, “Some funnier string :p”)

If you think this as a good idea then you might want to play with django-watson.

First things first. Let’s see how we can integrate django-watson in a new or existing project.

  1. pip install django-watson
  2. Add ‘watson’ in INSTALLED_APPS
  3. Make migrations, migrate
  4. Python manage.py installwatson

After installing watson, you need to do register the models you want to be searchable using watson.

To do this open the apps.py file from your app’s root directory. If your app’s name is… say ‘BlogPosts’ then your apps.py might look like this:

from __future__ import unicode_literals
from django.apps import AppConfig
class BlogPostsConfig(AppConfig):
name = 'blogposts'

Now import watson in the file and register the model inside ready() method like this:

from __future__ import unicode_literals
from django.apps import AppConfig
from watson import search as watson_search
class BlogPostsConfig(AppConfig):
name = 'blogposts'
def ready(self):
blogpost_model = self.get_model(“BlogPost”)
watson_search.register(blogpost_model)

In this case we have assumed that there is a model called “BlogPost” inside our app.

Now to test if search is working fire up django-shell and type:

>>> from watson import search as watson
>>> search_results = watson.search("some search string")
>>> print(search_results)
<QuerySet [<SearchEntry: SearchEntry object>.......]>

Uh oh! That’s not actually what we wanted right? It’s printing search entry objects, we want something that we can work with. So try this:

>>> for result in search_results:
print(result.title)

This will print the title of the model objects that has hit with the search string. Basically from watson search entry objects you can have three attributes “title”, “url”, and “meta‘ . But if your model do not have “title” or “url” field these attributes will remain empty. So, how can you make watson to retrieve the fields you want to from your model? You just need to tell watson about the field while you register the models. Like this:

watson_search.register(blogpost_model, store=("field_1", "field_2"))

Then from the search results you can access them from ‘meta’ attribute:

>>> for result in search_results:
print(result.meta.field_1, result.meta_field_2)

If you don’t feel like changing model registration then you can do this:

>>> search_results = watson.filter(Your_model, "Your search text")
>>> for result in search_results:
print(result.your_model_field_1, result.your_model_field_2)

But keep in mind, this method will search through only one model that you specified and the above will search through all registered models.

But there is another catch. You can access your attributes doesn’t means that search indexes are also built using those attributes. By default watson indexes all the ‘CharField’ and ‘TextField’. So, if there are other fields like date, integer or foreignkey those are not indexed. Which mean if your search string are meant to be present in the date or some other fields except char or text, watson won’t be able to find any hit. To enable that, simply specify the fields during registering the models like this:

watson_search.register(blogpost_model, fields=["field_1", "field_2"])

Or you might want to exclude one or more fields from search index:

watson_search.register(blogpost_model, exclude=["field_1", "field_2"])

Django watson best works with PostgreSQL and then MySQL according to developer’s documentation. To search using django-watson needs lesser amount of code doesn’t mean it is an alternative to the tools already present in django. For simpler cases built in tools are much efficient than django-watson.

--

--

Ruhshan Ahmed Abir

Started with poetry, ended up with codes. Have a university degree on Biotechnology. Works and talks about Java, Python, JS. www.buymeacoffee.com/ruhshan