Prevent multiple form submissions in Django

18,069

Solution 1

Client side, start with JavaScript. You can never trust the client, but its a start.

i.e.

onclick="this.disabled=true,this.form.submit();

Server side you 'could' insert something into a database i.e. a checksum. If its a records you are insert into a database use model.objects.get_or_create() to force the uniqueness on database level you should use unique_together.

Lastly: HTTPRedirect is best, The the method I use when a user processes a payment is to just issue a HTTPRedirect() to the thank you/conformation page. This way refreshing the form won't resubmit and if they go back and try to submit the form again (without refreshing the form) the Django Cross Site Request Forgery (CSRF) will fail, perfect!

Solution 2

I also try to find a good way to prevent double records generation when a user dbl-click on a submit button. It's not about the PRG issue that is easily fixed by redirection.

So, regards on this basic concern, the solution with HTTPRedirect on server-side doesn't help.

On client-side, I found two problems when I disable the button before submit:

  1. With HTML5 validation, the form.submit() will be interupted by browser if the form is invalid => submit button is still disabled=true.
  2. When the user submit the form and do a back in browser history, the DOM will be loaded from browser's cache => submit button is still disabled=true.

So here is my workaround for the first client-side problem (HTML5 validation):

isFormHtml5Valid(form) {
  for(var el of form.querySelectorAll('input,textarea,select')){
    if(!el.checkValidity())
      return false;
  }
  return true;
}

mySubmitButton.onclick = function() {
  if(this.form && isFormHtml5Valid(this.form))
    this.disabled=true;
  this.form.submit();
}

I try to find a client-side workaround for the second client-side problem (browser cache the DOM) but nothing worked (onbeforeunload, ...). So the workaround I currently use for "browser cache" issue is add a @never_cache decoration on the top of concerned views (from server-side, indicate to client-side to not caching). Please let me know if you have a better workaround.

Last but not least, I would really appreciate to fix this issue on server side. The CSRF solution seems not suitable since a CSRF token is generated by session (not for each form). So here is the status of my work and my question:

  • Fix this issue on client-side is OK but doesn't look like a good solution to me. How could we avoid to validate this multiple form submition on server-side?

Let me know if you have a good solution for that.


Edit 1: May be a small part of the answer: Synchronizer (or Déjà vu) Token

But I didn't find any implemantation of that in Django.

Solution 3

Use HttpResponseRedirect

create a new view(lets say thank_you) for successful message to display after form submission and return a template.

After successful form submission do return HttpResponseRedirect("/thank-you/") to the new thank-you view

from django.http import HttpResponseRedirect

def thank_you(request, template_name='thank-you.html'):
    return render_to_response(template_name,locals(),context_instance=RequestContext(request))

and in urls.py

url(r'^thank-you/$','thank_you', name="thank_you")

Multiple form submission happens because when page refreshes that same url hits, which call that same view again and again and hence multiple entries saved in database. To prevent this, we are required to redirect the response to the new url/view, so that next time page refreshes it will hit that new url/view.

Share:
18,069
Thomas Kremmel
Author by

Thomas Kremmel

Updated on June 07, 2022

Comments

  • Thomas Kremmel
    Thomas Kremmel about 2 years

    I'm looking for a generic way to prevent multiple form submissions. I found this approach which looks promising. Whereas I do not want to include this snippet in all of my views. Its probably easier to do this with a request processor or a middleware.

    Any best practice recommendations?

    • krak3n
      krak3n about 11 years
      You could create a base form class which all your forms inherit from which could perform the session check in a custom validation method so form.valid() would return false if the user has already entered data in the form.
    • Dai Doan
      Dai Doan about 11 years
      I don't understand that gist at all. It isn't updating the session.
    • Thomas Kremmel
      Thomas Kremmel about 11 years
      You are right. The GET part where the session has to be updated is not shown. But as he creates a hash-string based upon the csrf token, which has to be set in the GET request, it will probably work.
  • Thomas Kremmel
    Thomas Kremmel about 11 years
    Thanks for your reply. HTTPRedirect does not really help for this case, as the re-direct happens after the form has been validated in the post request. I want to prevent the user from submitting the same form twice (by clicking twice on the submit button within a short time frame - while thinking the form has not been submitted as server did not yet send a resp). Nevertheless its best practice to do a re-direct after a post submit, but for other reasons. unique_together does not help. think of a form to create tasks. a double click on form submit task leads to two tasks with the same values..
  • Thomas Kremmel
    Thomas Kremmel about 11 years
    ..which is ok since the user should be able to create tasks with for example the same name. No need for database uniqueness. The only valid approach for this case might be the javascript approach which I will give a try.
  • Thomas Kremmel
    Thomas Kremmel about 11 years
    Finally I achieved multiple form submission using angular.js. See this question for details: stackoverflow.com/questions/15807471/…
  • Michael
    Michael over 10 years
    Does HTTPRedirect and CSRF really work to avoid a user pressing the back button and post the form again? Because I think I am trying it but it does not work. See my post stackoverflow.com/questions/20412818/…
  • AdamG
    AdamG about 8 years
    It did not prevent a form re-submit for me.
  • Gagik Sukiasyan
    Gagik Sukiasyan over 7 years
    This is not quite working as when you disable the submit button browser just cancels the form submittion
  • Gagik Sukiasyan
    Gagik Sukiasyan over 7 years
    I think this should be marked as the best answer for this question ! On client side I do also other thing, which is showing overlay div element over submit button or whole page, which eats all clicks and prevents submit button being clicked again
  • luke
    luke over 5 years
    For the serverside, how about writing middleware that hashes the POST data and caches it for a short time?