Reputation: 7260
I'm using class-based views (FormView) and I would like to keep a search keyword after form is submitted (POST request). I tried this in form_valid
method:
def form_valid(self, form):
self.initial['search'] = form.data['search']
...
but this will show it for all users. This is quite common solution on many web search forms (not to mention Google search) so I wonder how this can be done in Django.
Update: Mon 19 Jun 13:18:42 UTC 2017
Based on some answers below I'll have to rephrase my question.
I have a simple form with a few input fields, after a form is submitted it will query other sites to get the results based on a search term. I would like to store some results into database, mainly to produce stats, re-display the form with selected fields and show results.
Currently data resides on a class object and it's passed from POST to GET. This is not a good solution for obvious security reasons:
class SearchView(FormView):
...
data = dict()
def form_valid(self, form):
....
self.data['results'] = results
def get_context_data(self, **kwargs):
context = super(IndexView, self).get_context_data(**kwargs)
context['data'] = self.data.pop('results', None)
return context
Question:
What would be the best way to display form (with selected fields) and results on the same page, preferably without sessions or storing them in database between POST and GET.
Points that I already considered:
Don't redirect the user (render the template with the current context right away, while we still have the response object) - I don't like the fact that page refresh will re-submit the form.
Save the response into some key-value store like Redis and redirect the user to something like results/{result_id}, where we can retrieve the response from database to pre-fill the form with data and show the results - this sounds reasonable but I'll have to add another component just to pass the results from POST to GET.
Use GET for this type of form - my understanding was that we should use POST for data modification
Upvotes: 7
Views: 11554
Reputation: 15370
Google and other similar searches use GET
params to pass query string
Exmaple:
test
https://www.google.ru/?q=test
So basically you need to use same approach and pass you search string as GET
param.
Here is general Idea
class SearchView(View):
success_url = reverse('result-view')
def get_success_url(self):
success_url = super().get_success_url()
return '{0}?search={1}'.format(success_url, self.form.cleaned_data['search'])
...
class ResultView(View):
def get(self, request, *args, **kwargs):
search_string = request.GET.get('search', '')
Upvotes: 5
Reputation: 1426
I'm still a bit iffy on what you want to do (searches are usually done via GET and then Django exhibits the desired behavior). But I think you should be able to do this by overwriting get_initial():
and pulling the value(s) out of self.request.POST
.
If all you want to do it keep the submitted value in the initialization of the next value, that's the best approach.
I.e. something like this inside your View class for Python 3 (your super call needs to be a bit different in Python 2):
def get_initial(self):
initial = super().get_initial()
initial['search'] = self.request.POST.get('search', '')
return initial
Upvotes: 1
Reputation: 16010
Use GET for this type of form - my understanding was that we should use POST for data modification
Use GET only to populate form.initial
values, you can still submit the form as POST.
# for python3
# from urllib.parse import urlencode
from urllib import urlencode
class SearchView(FormView):
def get_initial(self):
return {'search': self.request.GET.get('search')}
def form_valid(self, form):
search = self.request.POST.get('search')
url = self.get_success_url() + '?' + urlencode({'search': search})
return HttpResponseRedirect(url)
Upvotes: 2
Reputation: 1723
From your update: I think this is the process you are looking for:
You basically need to prevent the redirection when form is valid:
class SearchPageView(FormView):
def form_valid(self, form):
# do your things (3) here
context = self.get_context_data(form=form)
context['results'] = ['lorem', 'ipsum']
return self.render_to_response(context)
Upvotes: 4
Reputation: 61
Here's an implementation that I believe satisfies all your requirements. Especially, we can reprompt the user's input by having form_valid()
return super(SearchView, self).get(self.request)
. This way, the Django server behaves as processing a normal POST operation from the user's perspective.
views.py
class SearchView(FormView):
template_name = 'yourapp/search.html'
form_class = SearchForm
results = '' # for {{ view.results }}
def form_valid(self, form):
self.results = form.get_results()
return super(SearchView, self).get(self.request)
forms.py
class SearchForm(forms.Form):
input = forms.CharField()
def get_results(self):
input_data = self.cleaned_data['input']
results = "results for %s" % input_data # <-- modify this process as you need
# ... (your operations for input_data and results here)
return results
yourapp/search.html
<form action="" method="post">{% csrf_token %}
{{ form.as_p }}
<input type="submit" value="Search" />
</form>
<p>{{ view.results }}</p>
Upvotes: 2
Reputation: 1497
views.py
class SearchView(FormView):
template_name = 'apps/frontend/search_view.j2'
def get_form_class(self):
class _Form(forms.Form):
search = forms.CharField(label='search')
return _Form
def form_valid(self, form):
return self.render_to_response(self.get_context_data(form=form))
html
<h1>current:{{ form.search.value()|default('') }} - jinja2 format</h1>
<form action="." method="post">
{% csrf_token %}
{{ form }}
<button type="submit">submit</button>
</form>
Upvotes: 2
Reputation: 10794
You didn't make it clear exactly how long you want to keep the keyword stored.
If it's just that response, as long as you're rendering the form correctly the keyword will still be there:
>>> from django import forms
>>> class MyForm(forms.Form):
... search = forms.CharField()
...
>>> f = MyForm(data={'search': 'KEYWORD'})
>>> print(f['search'])
<input id="id_search" name="search" type="text" value="KEYWORD" />
If you want to keep the keyword across requests but just for that user, use the sessions framework:
def form_valid(self, form):
self.request.session['search'] = form.data['search']
...
def get_form(self, form_class):
form = super(self, YourView).get_form(form_class)
form.initial['search'] = self.request.session.get('search', '')
return form
Upvotes: 3