Thursday, June 22, 2017

Django set form field after POST request

Leave a Comment

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

7 Answers

Answers 1

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 '{}?search={}'format(success_url, self.form.cleaned_data['search'])     ...  class ResultView(View):     def get(self, request, *args, **kwargs):         search_tring = request.GET.get('search', '') 

Answers 2

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> 

Answers 3

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> 

Answers 4

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) 

Answers 5

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 

Answers 6

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) 

Answers 7

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 
If You Enjoyed This, Take 5 Seconds To Share It

0 comments:

Post a Comment