How to use annotate() with Haystack in Django? - 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.

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

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)

Dealing with additional kwargs in Django models

I want to pass some parameters with kwargs to get_or_create() function in Django models but if there are some additional key-values in kwargs that do not exist in models, an error will apear. Is there any way to pass the dictionary and the function itself handles the additional keys?
I think in Django source code you can find a better solution but, you also can create a function to remove extra keys from your data.
def remove_extra_keys(model, data: dict) -> dict:
for filed_name in data: # <- possibly for better reading u can use here data.keys()
if not getattr(model, filed_name, None):
data.pop(filed_name)
return data
p = MyModel.objects.get_or_create(**remove_extra_keys(model=MyModel, data=my_data))

Partial matching search in Wagtail with Postgres

I've got a wagtail site powered by Postgres and would like to implement a fuzzy search on all documents. However, according to wagtail docs "SearchField(partial_match=True) is not handled." Does anyone know of a way I can implement my own partial matching search?
I'm leaving this question intentionally open-ended because I'm open to pretty much any solution that works well and is fairly scalable.
We’re currently rebuilding the Wagtail search API in order to make autocomplete usable roughly the same way across backends.
For now, you can use directly the IndexEntry model that stores search data. Unfortunately, django.contrib.postgres.search does not contain a way to do an autocomplete query, so we have to do it ourselves for now. Here is how to do that:
from django.contrib.postgres.search import SearchQuery
from wagtail.contrib.postgres_search.models import IndexEntry
class SearchAutocomplete(SearchQuery):
def as_sql(self, compiler, connection):
return "to_tsquery(''%s':*')", [self.value]
query = SearchAutocomplete('postg')
print(IndexEntry.objects.filter(body_search=query).rank(query))
# All results containing words starting with “postg”
# should be displayed, sorted by relevance.
It doesn't seem to be documented yet, but the gist of autocomplete filtering with Postgres, using a request object, is something like
from django.conf import settings
from wagtail.search.backends import get_search_backend
from wagtail.search.backends.base import FilterFieldError, OrderByFieldError
def filter_queryset(queryset, request):
search_query = request.GET.get("search", "").strip()
search_enabled = getattr(settings, 'WAGTAILAPI_SEARCH_ENABLED', True)
if 'search' in request.GET and search_query:
if not search_enabled:
raise BadRequestError("search is disabled")
search_operator = request.GET.get('search_operator', None)
order_by_relevance = 'order' not in request.GET
sb = get_search_backend()
try:
queryset = sb.autocomplete(search_query, queryset, operator=search_operator, order_by_relevance=order_by_relevance)
except FilterFieldError as e:
raise BadRequestError("cannot filter by '{}' while searching (field is not indexed)".format(e.field_name))
except OrderByFieldError as e:
raise BadRequestError("cannot order by '{}' while searching (field is not indexed)".format(e.field_name))
The line to note is the call to sb.autocomplete.
If you want to use custom fields with autocomplete, you'll also need to add them into search_fields as an AutocompleteField in addition to a SearchField -- for example
search_fields = Page.search_fields + [
index.SearchField("field_to_index", partial_match=True)
index.AutocompleteField("field_to_index", partial_match=True),
...
This solution is working for Wagtail 2.3. If you using an older version, it is unlikely to work, and if you are using a future version, hopefully the details will be incorporated into the official documents, which currently state that autocomplete with Postgres is NOT possible. Thankfully, that has turned out to not be true, due to the work of Bertrand Bordage in the time since he wrote the other answer.

Django URLs: Can't get the keyword arg into my view?

The URL pattern having issues is:
url(r'^$', business_list, name='business_list_home'),
url(r'^(?P<param>\w+)$', business_list, name='business_list_results'),
My view is:
#render_to('app/business_list.html')
def business_list(request, param=None):
queryset = Business.objects.all()
search_form = SearchForm
print request.GET
if param in request.GET:
param = request.GET.get('param')
if queryset.filter(city__iexact=param).exists():
queryset = queryset.filter(city__iexact=param)
elif queryset.filter(category__iexact=param).exists():
queryset = queryset.filter(category__iexact=param)
print queryset
else:
queryset = None
print queryset
return {'business_list': queryset, 'search_form': search_form}
Essentially, I don't understand why when I go to /Miami I don't have access to it via request.GET['param'] in the view? print request.GET prints <QueryDict: {}>
The reason I want to do this is to have a nice URL scheme for displaying results of businesses for the city or category (and that's why I check if it's a city or category in the view too) in the url. Let me know if there's a better way to accomplish this like
url(r'^$', business_list),
url(r'^(?P<city>\w+)$', business_list),
url(r'^(?P<category>\w+)$', business_list),
url(r'^(?P<category>\w+)/(?P<city>\w+)$', business_list),
Thanks for any help!
business_list is a function that takes a request and a parameter called "param" (should probably give it a better name as to make it a little less generic). In Django, the URL routes can define what values get passed to the parameters of the view functions.
In the situation above, when a user goes to /Miami, Django will try to match the string "Miami" with any of the regular expressions defined in the URL routes. In this case, the matching expression is ^(?P<param>\w+)$. When this match is made, the string Miami is captured into param.
Django will then call business_list(request, param="Miami"). Note that there were no query parameters passed in the URL (e.g., /Miami?color=blue).
The issue in the code you've written above is that you're checking to see not if param exists, but rather that param was passed in the query parameters. Try going to the URL /Miami?param=test and it'll probably work the way that you expected.
The real fix here is to not reference request.GET, because using GET parameters is exactly what you're trying to avoid. So, instead of
if param in request.GET:
param = request.GET.get('param')
if queryset.filter(city__iexact=param).exists():
queryset = queryset.filter(city__iexact=param)
elif queryset.filter(category__iexact=param).exists():
queryset = queryset.filter(category__iexact=param)
print queryset
Just do:
if param:
if queryset.filter(city__iexact=param).exists():
queryset = queryset.filter(city__iexact=param)
elif queryset.filter(category__iexact=param).exists():
queryset = queryset.filter(category__iexact=param)