Django Haystack - Show results without needing a search query? - python

I would like to display all results which match selected facets even though a search query has not been inserted. Similar to how some shop applications work e.g. Amazon
e.g. Show all products which are "blue" and between $10-$100.
Haystack does not return any values if a search query is not specified.
Any ideas how I can get around it?
Thanks!

If anyone is still looking, there's a simple solution suggested in haystack code:
https://github.com/toastdriven/django-haystack/blob/master/haystack/forms.py#L34
class SearchForm(forms.Form):
def no_query_found(self):
"""
Determines the behavior when no query was found.
By default, no results are returned (``EmptySearchQuerySet``).
Should you want to show all results, override this method in your
own ``SearchForm`` subclass and do ``return self.searchqueryset.all()``.
"""
return EmptySearchQuerySet()

Why No Results?
I imagine you're using a search template similar to the one in the haystack getting started documentation. This view doesn't display anything if there is no query:
{% if query %}
{# Display the results #}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
The second problem is that the default search form's search() method doesn't actually search for anything unless there's a query.
Getting Results
To get around this, I'm using a custom search form. Here's an abbreviated sample:
class CustomSearchForm(SearchForm):
...
def search(self):
# First, store the SearchQuerySet received from other processing.
sqs = super(CustomSearchForm, self).search()
if not self.is_valid():
return sqs
filts = []
# Check to see if a start_date was chosen.
if self.cleaned_data['start_date']:
filts.append(SQ(created_date__gte=self.cleaned_data['start_date']))
# Check to see if an end_date was chosen.
if self.cleaned_data['end_date']:
filts.append(SQ(created_date__lte=self.cleaned_data['end_date']))
# Etc., for any other things you add
# If we started without a query, we'd have no search
# results (which is fine, normally). However, if we
# had no query but we DID have other parameters, then
# we'd like to filter starting from everything rather
# than nothing (i.e., q='' and tags='bear' should
# return everything with a tag 'bear'.)
if len(filts) > 0 and not self.cleaned_data['q']:
sqs = SearchQuerySet().order_by('-created_date')
# Apply the filters
for filt in filts:
sqs = sqs.filter(filt)
return sqs
Also, don't forget to change the view:
{% if query or page.object_list %}
{# Display the results #}
{% else %}
{# Show some example queries to run, maybe query syntax, something else? #}
{% endif %}
Actually, the view code is a little hackish. It doesn't distinguish query-less searches with no results from search with no parameters.
Cheers!

Look at SearchQuerySet.
This should be possible if color and price has been defined in your SearchIndex:
sqs = SearchQuerySet().filter(color="blue", price__range=(10,100))
You can limit the query to certain models by adding models(Model) to the SearchQuerySet. So if you want to limit your query to the model Item use:
sqs = SearchQuerySet().filter(color="blue", price__range=(10,100)).models(Item)

Following form display all the result if not query string is present. Now you can add custom filters.
from your_app.forms import NonEmptySearchForm
url(r'^your_url$',
SearchView(template='search.html',searchqueryset=sqs,form_class=NonEmptySearchForm), name='haystack_search'),
forms.py
#Overridding because the default sqs is always none if no query string is present
class NonEmptySearchForm(SearchForm):
def search(self):
if not self.is_valid():
return self.no_query_found()
sqs = self.searchqueryset.auto_query(self.cleaned_data['q'])
if self.load_all:
sqs = sqs.load_all()
return sqs

Stumpy Joe Pete's answer is pretty spot on, but as he mentioned, the template if query or page.object_list check is a little hacked. A better way of solving this would be to create your own SearchForm which would still find something if q is empty - will not repost that - AND to customize the SearchView with something like:
class MySearchView(SearchView):
def get_query(self):
query = []
if self.form.is_valid():
for field in self.form:
if field.name in self.form.cleaned_data and self.form.cleaned_data[field.name]:
query.append(field.name+'='+str(self.form.cleaned_data[field.name]))
return ' AND '.join(query)
In most cases, you won't even be using the query value, so you could just as well do a quick check if any of the fields is set and return True or something like that.. or of course you can modify the output any way you want (I'm not even 100% sure my solution would work for all field types, but you get the idea).

Related

How can I simplify this python statements

There's a model represents BBS.
I want to make change the value without update database. because I want to preserve the database value. To be precise at display time, I want it to be displayed as '[collabo]' + article.title this is what I am doing at the moment.
below is combine '[collabo]' and all of title with for loop
for article in articles:
article.title = '[collabo]'+article.title
is there any way to change the title value at one line of code? I don't want to change or update database. Or is there a better way.
If you want to do this in a single database query it's one line longer than what you have now!! but it's far more efficient.
from django.db.models import Value
from django.db.models.functions import Concat
Article.objects.annotate(new_title = Concat(V('[collabo]'),'title')))
The annotate method in the queryset is your friend here (with a little help from Concat and Value)
You could also do this at the template level
articles = Article.objects.all()
render('template.html',{'articles': articles})
And then
{% for article in articles %}
[collabo] {{ article.title }}
{% endfor %}
you may work with methods of the model class to give specific modification you want.
models.py
class Article(models.Model):
# some fields ...
def edited_title(self):
return '[collabo] {}'.format(self.title)
then you can exploit it in the templates with {{article.edited_title}}.

No Item matches the given query: dropdown options in Django

I asked this question earlier, but now I'm having trouble sorting out how to use drop downs (or even better, autofill fields) for one of the forms of a multi-form view.
The models in play are Book, BookDetails, and Genre. BookDetails is a linking table to Genre (and other similar tables) so that I can have a static list of genres etc. with unique IDs and foreign keys to BookDetails.
Right now I have this:
#views.py
def BookFormView(request):
genre = Genre.objects.all()
if request.method == "POST":
book_form = BookForm(request.POST, prefix='book')
bookdetails_form = BookDetailsForm(request.POST, prefix='bookdetails')
selected_genre = get_object_or_404(Genre, pk=request.POST.get('genre_id'))
genre.id = selected_genre
genre.save()
if book_form.is_valid() and bookdetails_form.is_valid():
book_form.save()
bookdetails_form.save()
return HttpResponseRedirect("/books/")
else:
book_form = bookForm(prefix='book')
bookdetails_form = BookDetailsForm(prefix='bookdetails)
return render(request, 'books/createbook.html',
{'book_form' : book_form,
'bookdetails_form': bookdetails_form,
'genre':genre,})
#createbook.html
<select name="genre", id="genre" form="bookform">
{% for entry in genre %}
<option value="{{ entry.id }}">
{{ entry.name }}
</option>
{% endfor %}
</select>
The form displays properly on the page, dropdown menu with options from the database included. However, when I hit submit to store the information to the database I get an error saying No Genre matches the given query The other posts on SO that regard this error don't seem to be from the same context. I think that it might be something to do with selecting a name but storing an id (for the genres), but otherwise I'm at a loss.
Normally, the way you'd do this with a form in django is not by manually pulling something out of the POST dict, but by using a ModelChoiceField:
https://docs.djangoproject.com/en/1.8/ref/forms/fields/#modelchoicefield
Was there a specific reason you didn't do that?
Also, it appears you're using the genre variable incorrectly for two different things. You initialize it with a queryset, but then try to treat it like a Genre instance later in the code. That's going to cause problems not to mention the fact that I don't think your genre.id = ... line is going to do what you expect it to.
Also, it's against style conventions to use title-casing for function names. If you're going to be doing much coding in Python, it's probably worth taking a look at the officially accepted PEP8 style guide here:
https://www.python.org/dev/peps/pep-0008/
There are a few other problems in the code but I'm not sure it's worth calling them out.

SqlAlchemy query in a template page

I have a query like this:
session.query(System).filter_by(id_system = s.id_system).join(Command).filter_by(id_command=c.id_command).first()
I'd like to do this query in a template page (I'm using mako), but it doesn't work:
% for c in s.commands:
code = session.query(System).filter....
% endfor
What is best way to do a query in pages? Or is it not possible?
Template is not a right place to do such operations. All business logic needs to be included into controllers according to MVC (or RV as Pyramid) paradigm.
Do query in your view function:
results = DBSession.query(model.Articles).all()
return dict(status=True, results=results)
or even better create module which contains common database operations (then import function and call it in view function) and return results to template by
import your_project.lib.dbfunctions
results = dbfunctions.get_articles()
return dict(status=True, results=results)
than use them into template:
<div>
% for r in results:
${r}
% endfor
</div>
I hope it will help you.
While it may be possible by injecting the session and other needed functions into the template context, this is not the right way to do things. The data, for the most part, should be handled outside the template, then passed to the context when rendering.
The view/function that is calling render for this template should be making this query and passing it to the template.

Why does this Google App Engine ndb ancestor query not work?

I've got a web app that's a simple wiki. It allows users who are logged in to edit existing topics or create new content if that topic doesn't have a page. I have been struggling with creating a version history page that lists the last n edits of a topic along with date/editor. However, I finally succeeded in displaying a list of n previous versions on the history page for a topic. My question is not exactly why what I'm doing now works... but why what I was trying before wasn't working.
Here is the handler class for a 'history' page. It's get method receives the topic as an arg in the form '/topic':
class History(Main):
""" Display previous versions of a topic if they exist """
def get(self, topic):
topic = topic[1:]
history = Version.get_topic_history(topic).fetch(10)
if history:
self.render('history.html', history=history, topic=topic)
else:
self.redirect('/%s' % topic)
Here is the model for storing all topic edits. It has a classmethod, get_topic_history. It queries the Version class for all entities by topic name, and then returns them ordered by creation date. This works. However, you can see just above that line, commented out, is what I was doing originally, that didn't work. In the first commented out line, I retrieved the key for all entities with the ancestor path that included the particular topic name(I think this is called an ancestor query, and at least, this is what I understand it to be doing). Then I returned a query by ancestor path, and ordered also by creation date/time.
This method is called from the History handler as you can see. This to me, looks like it should have returned the same result as my current approach, yet it returned nothing. Can anyone tell me why? And thanks in advance for all answers.
class Version(ndb.Model):
""" wiki version history """
created = ndb.DateTimeProperty(auto_now_add=True)
author = ndb.StringProperty(required=True)
topic = ndb.StringProperty(required=True)
content = ndb.TextProperty(required=True)
#classmethod
def get_topic_history(cls, topic):
# key = ndb.Key(cls, topic, parent=version_key())
# return cls.query(ancestor=key).order(-cls.created)
return cls.query(cls.topic == topic).order(-cls.created)
this is how I store the versions:
version = Version(topic=topic,
content=content,
author=User.get_user_name(self.user_id()),
parent=version_key())
version.put()
The version key function assigning parent keys in the code above is outside any class:
def version_key():
return ndb.Key('Group', 'version_group')
Finally here is a history.html template example. I am using Jinja2:
{% extends "base.html" %}
{% block content %}
Topic edit history :: {{topic}}
<div>
{% for version in history %}
<div>
{{version.content}}<br>
{{version.created}} - {{version.author}}
view
</div>
{% endfor %}
</div>
{% endblock %}
When you create the Version entity, the parent is version_key().
When you issue the ancestor query, the ancestor should be version_key() as well. However, you are querying with the ancestor ndb.Key(cls, topic, parent=version_key())
I believe what you want to do is:
cls.query(ancestor=version_key()).filter(topic=topic).order(-cls.created)
btw, having a single ancestor is going to be bad for your future performance of creating Version entities.

Django: Adding additional properties to Model Class Object

This is using Google App Engine. I am not sure if this is applicable to just normal Django development or if Google App Engine will play a part. If it does, would you let me know so I can update the description of this problem.
class MessageModel(db.Model):
to_user_id = db.IntegerProperty()
to_user = db.StringProperty(multiline=False)
message = db.StringProperty(multiline=False)
date_created = db.DateTimeProperty(auto_now_add=True)
Now when I do a query a get a list of "MessageModel" and send it to the template.html to bind against, I would like to include a few more properties such as the "since_date_created" to output how long ago since the last output, potentially play around with the message property and add other parameters that will help with the layout such as "highlight" , "background-color" etc...
The only way I thought of is to loop through the initial Query Object and create a new list where I would add the property values and then append it back to a list.
for msg in messagesSQL:
msg.lalaland = "test"
msg.since_created_time = 321932
msglist.append(msg)
Then instead of passing the template.html messagesSQL, I will now pass it msglist.
You should still be able to send it messagesSQL to the template after you've added elements to it via the for loop. Python allows that sort of thing.
Something else that might make sense in some cases would be to give your MessageModel methods. For instance, if you have a
def since_date_created(self):
'''Compute the time since creation time based on self.date_created.'''
Then (assuming you have "messagesSQL" in the template), you can use the function as
{% for msg in messagesSQL %}
{{ msg.since_date_created }}
{% endfor %}
Basically, you can call any method in the model as long as you it needs no arguments passed to it.
You can obtain that by defining methods in the model
like
class MessageModel(db.Model):
# Definition
def since_date_created(self):
# ...
Now in the template, you can use it like
Time since created {{ message.since_date_created }}

Categories

Resources