Multiple querystring parameters - django

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%"

Related

How to exclude empty values when querying a DRF endpoint URL [Django]

Similar to an exclude filter that looks like this:
MyObject.objects.exclude(my_field="")
I know that I can modify the view to exclude the value in the queryset but that's not dynamic:
def get_queryset(self):
return Client.objects.exclude(my_field="")
But I'm looking for a less static way of querying via URL. So something like this:
/api/object?my_field__isempty=False
#Hashem's answer is good, but you could also use Q statements
from django.db.models import Q
# Q allows for complex queries with:
# & (AND)
# | (OR)
# ^ (XOR)
# Exclude method
ModelName.objects.exclude(Q(field_name__isnull=True) | Q(field_name__exact=''))
# Filter Method with ~ (NOT)
ModelName.objects.filter(~Q(field_name__isnull=True) & ~Q(field_name__exact=''))
Just throwing out another option, and knowing about Q statements is really beneficial
Docs: https://docs.djangoproject.com/en/4.1/topics/db/queries/#complex-lookups-with-q
Edit
Here's an extra tidbit, you'll find this handy, you can use pointers and filter by a dictionary. The dictionary can be created dynamically
filterDict = {'my_field':''}
Client.objects.exclude(**filterDict)
# Client.objects.exclude(**filterDict) === Client.objects.exclude(my_field=='')
I'm not sure how you are doing Views, but if you have a "normal" one with the request object you you can fetch the GET parameters as a QueryDict:
def myview_with_dynamic_pointer(request):
print(request.GET)
# Should work
Client.objects.filter(**request.GET)
# will work
Client.objects.filter(**{k:v for k,v in request.GET.items()})
def myview_with_dynamic_Q(request):
print(request.GET)
from django.db.models import Q
dynamicquery = Q()
for key, value in request.GET.items():
dynamicquery = dynamicquery & Q({k:v})
# Can also do OR:
# dynamicquery = dynamicquery | Q({k:v})
Client.objects.filter(dynamicquery)
If you are using class based views, it'll be more like:
class MyListView(ListView):
model = MyModel
def get_queryset(self):
print(self.request.GET)
return self.model.objects.filter(**self.request.GET)
It might be a good idea to look over the GET before shoving it right into a filter.. Safety wise
If I got you correctly,
If you need to exclude null values and empty strings
ModelName.objects.exclude(field_name__isnull=True).exclude(field_name__exact='')
Or you can use one of them, this equal to
NOT field_name='' AND field_name IS NOT NULL

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 make search more accurate in 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)

What is the command to search a content in all the model fields at once?

I am developing an app with Django.
I have developed a search bar to filter the database contents.
I want that, when the user clicks on the search button, the indicated content is searched in all the model fields.
My model has 16 fields, so my filter command will be a very long line, like:
selected_entries = glossary_entry.objects.filter(Q(field1__icontains=query) | Q(field2__icontains=query)) | ...ETC ETC... | Q(field16__icontains=query))
Here you can see this line in my view function, in views.py:
def glossario(request):
query = request.GET.get('q')
template = "glossario.html"
# query applied
if query:
query = request.GET.get('q')
selected_entries = glossary_entry.objects.filter(Q(field1__icontains=query) | Q(field2__icontains=query)) | ...ETC ETC... | Q(field16__icontains=query))
return render(request, template, {'all_entries':selected_entries})
# no query
else:
all_entries = glossary_entry.objects.all
return render(request, template, {'all_entries':all_entries})
Is there a shorter command to do the same?
Like:
selected_entries = glossary_entry.objects.filter(Q(ALL_MODEL_FIELDS_ICONTAINS(model=MyModel)=query))
Note: ALL_MODEL_FIELDS_ICONTAINS is my invention
You will have to specify all the fields anyway. There isn't a shortcut. But you could concatenate all fields and just do one lookup:
from django.db.models import CharField, Value as V
from django.db.models.functions import Concat
glossary_entry.objects.annotate(
search_string=Concat(
'field1', V(' '), 'field2', V(' '), 'field3', ...,
output_field=CharField()
)
).filter(search_string__icontains=q)
Note: I've added spaces between each field using Value although that's not strictly necessary with icontains, it just helps with debugging if you're looking at the value of search_string.

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