HTF
HTF

Reputation: 7260

Django set form field after POST request

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:

  1. 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.

  2. 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.

  3. Use GET for this type of form - my understanding was that we should use POST for data modification

Upvotes: 7

Views: 11554

Answers (7)

Sardorbek Imomaliev
Sardorbek Imomaliev

Reputation: 15370

Google and other similar searches use GET params to pass query string

Exmaple:

  1. Google test
  2. Result url will be 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

Apollo Data
Apollo Data

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

Todor
Todor

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

SebCorbin
SebCorbin

Reputation: 1723

From your update: I think this is the process you are looking for:

  1. user fills the form with empty search term
  2. user POST the form with search term
  3. you query other sites in this POST request, and display your form again with term
  4. user is presented with the pre-filled form and results (no redirection or you would lose context)

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

Jay
Jay

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

vadimchin
vadimchin

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

SmileyChris
SmileyChris

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

Related Questions