How to make search more accurate in Django? - django

so on the way of learning process, I am making my first side-project on django.
I want to make my search more accurate, for example: when post body contains text with 3 words "I love stackoverflow" and someone searches for "I stackoverflow" (without word LOVE), result is not shown on the search page.
What could be the best approach in this case to get the result, even if the post body does not contain words in that order as a search query?
views.py
def search(request):
posts = Post.objects.all().order_by('title')
query = request.GET.get('q')
print(query)
if query:
posts = Post.objects.filter(
Q(title__icontains=query)|
Q(body__icontains=query)
)
context = {
"posts": posts,
}
return render(request, "search.html", context)

I'd recommend using full text search with django haystack with any search engine. But, to respond to your case, something like following would do the trick, though is not very optimised:
from django.db.models import Q
# consider only words which are having a length greater than 2
# also, words should be sanitised and cleaned before using for db queries.
# use a form for that.
parts = [i for i in request.GET.get('q').split(' ') if len(i) >= 3]
qs = Q()
query = [qs | Q(title__icontains=query) | Q(body__icontains=query) for q in parts]
result = Post.objects.filter(query).order_by().distinct()

Django provides multiple efficient ways to search on a postgreSQL database that you can find on the official docs.

Try to split your query
from itertools import chain
def search(request):
posts = Post.objects.all().order_by('title')
query = request.GET.get('q')
if query:
words = query.split(" ")
results = []
for word in words:
match = posts.filter(
Q(title__icontains=word)|
Q(body__icontains=word)
)
if match:
results.append(match)
posts = set(chain(*results))
context = {
"posts": posts,
}
return render(request, "search.html", context)

Related

Whats wrong with my search string? I am not getting proper data

I want to implement search on my django project. On my following queryset, with else condition its passing correct data. But with if condition whatever I search, it shows nothing.
def get_queryset(self):
category = self.request.GET['category']
query = self.request.GET['q']
if category == 'all':
products = Products.objects.filter(Q(name__icontains=query) | Q(category__name__icontains=query)).all()
else:
products = Products.objects.filter(Q(category__slug=category), Q(category__slug__icontains=self.request.GET['q']) | Q(name__icontains=self.request.GET['q']))

how to use (from itertools import chain) to search multiple model

i want to search field of a multiple model in a single search view here is what i tried i know this is not a clean and better way to do that's why i am looking for a clean and better way to do it i read it is possible with (from itertools import chain) but i did not completely understand how to use it in my function based views without passing so many context here is my view
def search_item(request):
search_item = request.GET.get('search')
if search_item:
story = Story.objects.filter(Q(title__icontains=search_item)|Q(written_by__icontains=search_item))
news = News.objects.filter(Q(title__icontains=search_item)|Q(written_by__icontains=search_item))
Stock = stock.objects.filter(Q(title__icontains=search_item)|Q(written_by__icontains=search_item))
return render(request, 'search_result.html', {'ttts':ttt,'story':story,'news':news,'stock':Stock,})
thank you
You don't need itertools.chain.
def search_item(request):
results = []
search_item = request.GET.get("search")
if search_item:
q = Q(title__icontains=search_item) | Q(written_by__icontains=search_item)
for model in (Story, News, Stock):
results.extend(model.objects.filter(q))
return render(request, "search_result.html", {"results": results})
would be a simple, DRY way to write what you have.

Multiple querystring parameters

I've created this simple search function:
def search(request):
if "q" in request.GET:
querystring = request.GET.get("q")
print(querystring)
if len(querystring) == 0:
return redirect("/search/")
posts = Blog.objects.filter(title__icontains=querystring | tagline__icontains=querystring | contents__icontains=querystring)
context= {"posts": posts}
return render(request, "kernel/search.html", context)
else:
return render(request, "kernel/search.html")
When I use only one condition, for example:
posts = Blog.objects.filter(title__icontains=querystring)
it's shown me the correct results. But when I use multiple parameters I have SyntaxError: invalid syntax.
I was sure the query corresponded to:
SELECT * FROM Post WHERE "title" is "key_search" or "tagline" is
"key_search" or "contents" is "key_search"
How I can resolve?
The above is incorrect Python syntax, you can not put operators between named parameters.
Django however has Q objects [Django-doc] to express "conditions", so you can wrap the conditions in Q objects, and use the | operator to express a logical or, like:
from django.db.models import Q
posts = Blog.objects.filter(
Q(title__icontains=querystring) |
Q(tagline__icontains=querystring) |
Q(contents__icontains=querystring)
)
This will result in a query that looks, more or less, like:
SELECT *
FROM Post
WHERE "title" LIKE "%key_search%"
OR "tagline" LIKE "%key_search%"
OR "contents" LIKE "%key_search%"

How to optimize django query filter?

Basically I have this url dispatcher that capture a search term with each word separated by + to be search in the query. I have done this this is works but I think this will hit the performance due to repeated search to the database. Is there a better way to do this?
def search(request, **kwargs):
context = RequestContext(request)
test = {}
result = BlogPage.objects.select_related('ImageMedia')
if 'search_content' in kwargs:
test['search_content'] = kwargs['search_content']
if kwargs['search_content'] != '0':
search_words = kwargs['search_content'].split('+')
for words in search_words:
result = result.filter(content__icontains=words)
context_dict = {'blog_list': result}
return render_to_response('blog/blog_search.html', context_dict, context)
You could pre-build your filter, like (untested):
from django.db import Q
search_content = kwargs.get('search_content', '')
myfilter = Q()
for term in search_content.split('+'):
myfilter |= Q(content__icontains=term)
result = BlogPage.objects.filter(myfilter).select_related('ImageMedia')
I think Python list to bitwise operations is simpler when you are using python 2.7
PS: reduce() function has been removed in Python 3, so you can't use it and it is no compatibility.reduce() has been move into module functools

How to use annotate() with Haystack in Django?

I have a function that search with hatstack, and I need to get the comments of each object that haystack get in the array, I have this:
def search(request):
if 'q' in request.GET and request.GET['q']:
q = request.GET['q']
results = SearchQuerySet().auto_query(q)
things = []
for r in results:
things.append(r.object)
return render_to_response('resultados.html',
{'things': things, 'query': q}, context_instance=RequestContext(request))
How I append to the results the number of comments that each object have?
If I add annotate, debugger throw me: SearchQuerySet has not 'annotate' attribute
SearchQuerySet isn't the ORM query set you're familiar with. It only imitates it. Annotations doesn't make sense with search engines as well. You need to put already prepared data to an index.
Just make another query using ORM.