Search functionality django - django

I've been following the tango with django book and I came across the search chapter using Bing's Search API. I tried to use it but it seems like Bing no longer offers those services. Now I want to make the search functionality local so that it could just search through my categories in the rango app but I don't know how to do this without the Bing search API. Anyone who could help me with a way around it if there is. Thanks in advance.

Here's an example to implement basic search in django:
1) templates/base.html
note: use GET method to get search input from form.
<form name="exampleform" method="GET" action="{% url 'search' %}">
2) views.py
def search(request):
try:
if 'q' in request.GET:# this will be GET now
querystring = request.GET.get('q')# passing in the query value to search view using the 'q' parameter
if len(querystring) == 0:
return redirect('index')
else:
pass
except:
pass
results = {}
if 'q' in request.GET:
querystring = request.GET.get('q')
if querystring is not None:
results = UserModel.objects.filter(
Q(email__icontains=querystring) |
Q(first_name__icontains=querystring) |
Q(last_name__icontains=querystring)).order_by('pk')# filter returns a list
context = {'results': results}
template = 'templates/search_result.html'
return render(request, template, context)
else:
return redirect('index')
context = {}
else:
return render(request, "templates/search_result.html")
2) urls.py
url(r'^search',views.search, name='search'),
3) templates/search_result.html
{% for each_object in results %} // results is list here so pick each element object using for loop
<a href="{% url 'user_detail' pk=each_object.pk %}">
<!--Upon successful search object image with hyperlink appears -->
<img src="{{each_object.image.url}}" alt="No Image"></a>
<p>{{each_object.email}}</p>
<p>{{each_object.first_name}}</p>
<p>{{each_object.last_name}}</p>
{% endfor %}
There are a few projects around to do search. Here's a non-exhaustive list: djangosearch, django-search (with a dash), django-sphinx.

Related

django, views direct to a another html page

I am using Django for develop a website. The website is intended to use to search information stored in a MySQL database.
This is the current basic flow of the web site.
1) index.html - this has a form to select an option
2) according the option, users will redirect to search.html (include a form)
3) once the user provides the criteria, the result will be displayed in reply.html
In my views.py , I have two functions.
from django.shortcuts import render
from website.models import WebsiteRepository
from .forms import SearchForm
from .forms import SelectTypeForm
def Search(request):
if request.method == 'POST':
#do something
return render(request, 'reply.html', {'env_dict':env_dict})
else:
#do something
return render(request, 'search.html', context = context)
def index(request):
if request.method =='POST':
#do something
return render(request, 'search.html', context = context)
else:
#do something
return render(request, 'index.html', context= context)
When I go to index.html page, I can select a option and it will direct me to search.html. After, I fill the form there and submit, it wont give me the reply.html page.
I have a feeling that, I could make this work by changing urls.py.
from django.urls import path
from website import views
urlpatterns = [
path('', views.index, name='index'),
#path('search/', view.Search, name ='Search')
]
I tried to google it. But its too much details and Iam kind of lost.
Do any of you guys know how to achieve this?
Thanks
search.html
{% extends "base_generic.html" %}
{% block content %}
<h3>Welcome to search information Repository</h3>
<form method="post">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
{% endblock %}
index.html
{% block content %}
<h3>Welcome to information Repository</h3>
<form method="post">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
just for clarify things more, ill add the forms.py too
from django import forms
from .models import WebsiteRepository
class SearchForm(forms.Form):
websiterepository = WebsiteRepository
env_indicators = websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
indicator = forms.ChoiceField(choices=env_indicators,label = 'Indicator' )
OPTIONS = (('2000','2000'),('2001','2001'),('2002','2002'), ('2003','2003'),('0000','0000'),)
year = forms.ChoiceField(choices=OPTIONS)
class SelectTypeForm(forms.Form):
OPTIONS = (('1', 'Envirnmental Indicators'),('2','Economic Indicators'),('3','Social Indicators'),)
types = forms.ChoiceField(choices=OPTIONS)
Your code is wrong on many points.
First thing first: for a search, you want a GET request, not a POST (POST is for updating the server's state - adding or updating your database mostly). This is the semantically correct method (since you want to GET data), and it will allow a user to bookmark the url.
Second point: you don't want to submit the search form to the index view but to the search view. No need for redirects etc, just use the {% url %} templatetag to fill the action attribute of your form (you of course need to have a 'Search' url in your urls.py):
<form method="get" action="{% url 'Search' %}">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>
if you want to have this form on more than one page (which is often the case for search forms), use an inclusion tag tha will take care of creating an unbound SearchForm and render the template fragment.
Then in your search view, you only want GET requests, and do not use two different templates, this will only lead to useless duplication.
def Search(request):
form = SearchForm(request.GET)
# use the form's data - if any - to get search results
# and put those results (even if empty) in you context
return render(request, 'reply.html', {'env_dict':env_dict})
And finally, your search form is totally broken:
class SearchForm(forms.Form):
# this is totally useless
websiterepository = WebsiteRepository
# this will only be evaluated once at process startup, so you will
# get stale data in production - and probably different data
# per process, in a totally unpredictable way.
# You need to either put this in the form's __init__ or wrap it
# in a callable and pass this callable
env_indicators = websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
indicator = forms.ChoiceField(choices=env_indicators,label = 'Indicator' )
# are you going to manually add a new year choice every year ???
OPTIONS = (('2000','2000'),('2001','2001'),('2002','2002'), ('2003','2003'),('0000','0000'),)
year = forms.ChoiceField(choices=OPTIONS)
For the "indicators" ChoiceField you want something like:
def get_indicators_choices():
return Websiterepository.objects.filter (key_aspect='Environmental').values_list('repo_id','indicator')
class SearchForm(forms.Form):
# IMPORTANT : we are NOT calling the function here, just
# passing it (python functions are objects) to the field, which
# will call it everytime the form is instanciated, so you don't
# have stale data
indicator = forms.ChoiceField(
choices=get_indicator_choices,
label='Indicator')
As a last note: be consistent with your namings (ie why name one view in all lower (index) and capitalize the other (Search) ? Whichever convention you choose (I strongly suggest respecting pep8 here), at least stick to it for the whole project.
The problem is that code is not redirecting to /search, instead rendering search.html after post from index.html.
Try doing like-
views.py-
#your code
def index(request):
#do something
if request.method == 'POST':
return redirect('Search')
else:
#render index.html
def search(request):
#do something
if request.method == 'POST':
#render reply.html
else:
#render search.html
Another way to achieve this is if you specify action in your form so that form posts on /search.
search.html
<form method="post" action="/search">
{% csrf_token %}
{{form.as_p}}
<button type = 'submit'>submit</button>
</form>

Creating url out of user's choices from the form. Django gives NoReverseMatch, saying i need at least two arguments and i have none

So basically i have form on my homepage that asks users to choose two cities : where they are now, and where they want to go. I display all the available options with ModelChoiceField() easily, but when i try to use user's choices to make arguments for url, i get NoReverseMatch. I did a little research and found out due to the fact that at the time when page is loaded, user hasn't chosen anything, so there are no arguments. After that, i took different approach - i tried to set /search/ as url for the form. There, i extracted user's choices and tried to redirect back to the main url with these two arguments. Error still persists
Traceback Url :
http://dpaste.com/34E3S2V
Here's my forms.py :
class RouteForm(forms.Form):
location = forms.ModelChoiceField(queryset=Location.objects.all())
destination = forms.ModelChoiceField(queryset=Destination.objects.all())
Here's my template :
<p> From where to where ? </p>
<form action="{% url 'listings:search' %}" method="POST">
{{ form.as_p }}
{% csrf_token %}
<input type="submit" value="Let's go!">
</form>
My urls.py :
urlpatterns = [
path('', views.index, name="index"),
path('<location>/<destination>', views.route, name="route"),
path('search/', views.search, name="search")
]
and views.py :
def index(request):
form = forms.RouteForm()
listings = Listing.objects.all()
context = {"listings" : listings, "form" : form }
def route(request, location, destination):
current_location = Location.objects.get(city=location)
future_destination = Destination.objects.get(city=destination)
context = {"current_location" : current_location, "future_destination" : future_destination}
return render(request, 'listings/route.html', context)
def search(request, location, destination):
chosen_location = Location.objects.get(pk=request.POST['location'])
chosen_destination = Destination.objects.get(pk=request.POST['destination'])
return HttpResponseRedirect(reverse('listings:route', args=[chosen_location, chosen_destination]))
What am i missing?
You need to show the full traceback in your question. Actually, the code you've shown wouldn't give that error; instead you would get a TypeError for the search view.
Nevertheless, you have quite a few things wrong here.
Firstly, you need to decide how you want to represent those fields in the URL. You can't just put a Location object in a URL. Do you want to use numeric IDs, or string slugs? Assuming you want to use slugs, your URL would be:
path('<slug:location>/<slug:destination>', views.route, name="route"),
Secondly, you shouldn't have location and destination as parameters to the search function. They aren't being passed in the URL, but you in the POST data.
Next, you need to actually use the Django form you've defined, and get the values from that form cleaned_data. Using the form - in particular calling its is_valid() method - ensures that the user actually chooses options from the fields. So the search function needs to look like this:
def search(request):
if request.method == 'POST':
form = RouteForm(request.POST)
if form.is_valid():
chosen_location = form.cleaned_data['location']
chosen_destination = form.cleaned_data['destination']
return HttpResponseRedirect(reverse('listings:route', args=[chosen_location.city, chosen_destination.city]))
else:
form = RouteForm()
return render(request, 'search.html', {'form': form})

Pagination and get parameters

Django 1.11.4
I have build a search form with method="get".
Search form has a lot of forms.
Then this input values are transmitted as get parameters in url.
The problem is how to get pagination. The database contains thousands of objects. Pagination is necessary.
This is what the documentation tells us:
https://docs.djangoproject.com/en/1.11/topics/pagination/#using-paginator-in-a-view
It suggests like this:
previous
But this will ruin all the get parameters.
What I have managed to invent is:
previous
This works. But this is goofy. If one switches pages forward and backward, it produces urls ending like this:
page=2&page=3&page=2
I have had a look at how Google managed this problem. In the middle of the url they have start=30. And change this parameter: start=20, start=40. So, they switch.
Could you help me understand how preserve get parameters and switch pages in Django? In an elegant way, of course.
The generic solution is to define a "custom template tag" (a function) which keeps the complete URL but updates the GET parameters you pass to it.
After registration, you can use this function in your templates:
previous
To define and register the custom template tag, include this code in a python file:
from django import template
register = template.Library()
#register.simple_tag(takes_context=True)
def query_transform(context, **kwargs):
query = context['request'].GET.copy()
for k, v in kwargs.items():
query[k] = v
return query.urlencode()
*Thanks to Ben for the query_transform code. This is an adapation for python 3 from his code.
Why this method is better than reconstructing the URLs manually:
If you later decide that you need additional parameters in your URLs: 1. you don't have to update all the links in your templates. 2. You don't need to pass all the params required for recostructing the URL to the templates.
Typically, to preserve GET parameters you simply re-write them manually. There shouldn't be many cases where having to do this will matter.
&page={{page}}&total={{total}}
You can abstract this away into a template include or a custom template tag.
https://docs.djangoproject.com/en/1.11/howto/custom-template-tags/
Additionally, you could create a string filter that takes URL params as a string as well as a dict of values you want to change. The filter could then parse the params string, update the value, then recombine the string back into URL params.
{{ request.get_full_path | update_param:'page=8' }}
This is what I did and find easier. Many not be better approach though!
param = ""
if search_programs:
qs = qs.filter(title__icontains=search_programs)
param = param + f"&search_programs={search_programs}"
if level:
qs = qs.filter(level__icontains=level)
param = param + f"&level={level}"
if category:
qs = qs.filter(sector__icontains=category)
param = param + f"&category={category}"
paginator = Paginator(qs, 16)
page_number = self.request.GET.get('page')
page_obj = paginator.get_page(page_number)
context = {
'page_obj': page_obj,
"param": param
}
return render(self.request, self.template_name, context)
# in html
next
Subclassing
I solved this problem by subclassing Django's Paginator class and adding the stuff I needed there.
You need to override the init method to allow yourself to pass the request and form objects from the view, so that you can retrieve the request.GET parameters and compare them to form.fields. You want to only allow GET parameter names that are predefined in your form. Otherwise you remove them.
Once you have subclassed the Pagination class, you can use it in your views.py files, passing the extra request and form parameters to it. Then you can render your template as suggested in the Django documentation, but now you have access to the paginator.first_params, paginator.last_params, page.previous_params, and page.next_params variables that you can use.
pagination.py
import urllib
from django.core.paginator import Paginator as DjangoPaginator, EmptyPage
class Paginator(DjangoPaginator):
def __init__(self, *args, **kwargs):
request = kwargs.pop('request', None)
form = kwargs.pop('form', None)
super().__init__(*args, **kwargs)
self.params = {key: value for key, value in request.GET.items() if key in form.fields.keys()}
self.first_params = urllib.parse.urlencode({**self.params, 'page': 1})
self.last_params = urllib.parse.urlencode({**self.params, 'page': self.num_pages})
def get_page(self, *args, **kwargs):
page = super().get_page(*args, **kwargs)
try:
page.previous_page_number = page.previous_page_number()
except EmptyPage:
page.previous_page_number = None
try:
page.next_page_number = page.next_page_number()
except EmptyPage:
page.next_page_number = None
page.previous_params = urllib.parse.urlencode({**self.params, 'page': page.previous_page_number})
page.next_params = urllib.parse.urlencode({**self.params, 'page': page.next_page_number})
return page
views.py
from .pagination import Paginator
paginator = Paginator(queryset, per_page, request=request, form=form)
page = paginator.get_page(page_number)
pagination.html
{% if page %}
<nav id="pagination">
{% if page.has_previous %}
« First
Previous
{% endif %}
<span class="current">
Page {{ page.number }} of {{ page.paginator.num_pages }}.
</span>
{% if page.has_next %}
Next
Last »
{% endif %}
</nav>
{% endif %}

Search result fails with pagination

In my django project I'm using Django Endless Pagination for pagination and haystack + elasticsearch for searching. When I search a specific content the request method is POST and the result is correct, but when I try to paginate through the search result, next request is received as GET and the search result is lost and the whole content is iterated.
Here is my code:
views.py
#login_required(login_url="/")
#page_template('students/students_listing_block.html')
def students(request, template='students/students_listing.html', extra_context=None, *args, **kwargs):
sqs = SearchQuerySet().models(Student)
if request.POST:
searchcontent = request.POST.get('content', None)
if searchcontent:
sqs = sqs.filter(content=searchcontent)
students = sqs.order_by('-created_at')
context = {
'students': students,
}
if extra_context is not None:
context.update(extra_context)
return render_to_response(template, context,
context_instance=RequestContext(request))
and my template
{% load endless %}
{% lazy_paginate students %}
{% for student in students %}
// Do the displaying here
{% endfor %}
{% show_more %}
It is get request because the standard request method for search is GET. My recommendation is to change the request method for search into GET.
Or, if you want to keep using POST, you need to change the request method for every page link. This is a good library to change the hyperlink request method. https://github.com/rails/jquery-ujs
TEST

django csrf_token in search result url

Have csrf in search result url. Don't know why is there and how to remove it. Search works nice. Here is URL
/search/?csrfmiddlewaretoken=675d1340034e094866d15a921407e3fc&q=testing
here is view:
def search(request):
query = request.GET.get('q', '')
rezult = []
if query:
qset1 = (
Q(title__icontains=query)
)
result = Product.objects.filter(qset1).distinct()
if result.count() == 1:
return HttpResponseRedirect(result[0].get_absolute_url())
return render_to_response('search/search.html',{'query': query, 'result': result, },context_instance=RequestContext(request))
Thanks
Remove {% csrf_token %} from your form in the template, you don't need it since you're making a GET request.
you added {% csrf_token %} in your form. if you dont need csrf remove this from your form and add csrf_exempt.
look at this sample of django:
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
def my_view(request):
return HttpResponse('Hello world')
I would assume that you've added the {% csrf_token %} within one of the search form's input element. That would cause the token to be submitted along with the form.
Check your search form template.