Prevenir envíos de formularios múltiples en Django

Estoy buscando una forma genérica para evitar envíos de formularios múltiples. Encontré este enfoque que parece prometedor. Mientras que no quiero incluir este fragmento en todos mis puntos de vista. Probablemente sea más fácil hacerlo con un procesador de solicitudes o un middleware.

¿Alguna recomendación de mejores prácticas?

Lado del cliente, comience con JavaScript. Nunca puedes confiar en el cliente, pero es un comienzo.

es decir

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

En el lado del servidor, puede “insertar” algo en una base de datos, es decir, una sum de comprobación. Si es un registro que está insertado en una base de datos, use model.objects.get_or_create() para forzar la unicidad en el nivel de la base de datos, debe usar unique_together.

Por último : HTTPRedirect es lo mejor. El método que utilizo cuando un usuario procesa un pago es emitir un HTTPRedirect () a la página de agradecimiento / conformación. De esta manera, la actualización del formulario no se volverá a enviar y, si vuelven e intentan enviar el formulario nuevamente (sin actualizar el formulario), la Solicitud de falsificación de sitios cruzados de Django (CSRF) fallará, ¡perfecto!

También trato de encontrar una buena manera de evitar la doble generación de registros cuando un usuario hace doble clic en un botón de envío. No se trata del problema de PRG que se resuelve fácilmente mediante la redirección.

Por lo tanto, con respecto a esta preocupación básica, la solución con HTTPRedirect en el lado del servidor no ayuda.

En el lado del cliente, encontré dos problemas cuando deshabilito el botón antes de enviar:

  1. Con la validación de HTML5, form.submit() será interrumpido por el navegador si el formulario no es válido => el botón de envío aún está disabled=true .
  2. Cuando el usuario envía el formulario y realiza una copia de respaldo en el historial del navegador, el DOM se cargará desde la memoria caché del navegador => el botón enviar aún está disabled=true .

Así que aquí está mi solución para el primer problema del lado del cliente (validación HTML5):

 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(); } 

Intento encontrar una solución alternativa del lado del cliente para el segundo problema del lado del cliente (caché del navegador, el DOM) pero nada funcionó (antes de descargar, …). Por lo tanto, la solución que uso actualmente para el problema de “caché del navegador” es agregar una decoración @never_cache en la parte superior de las vistas en cuestión (desde el lado del servidor, indicar al lado del cliente y no al almacenamiento en caché). Por favor, hágamelo saber si tiene una solución mejor.

Por último, pero no por ello menos importante, realmente agradecería solucionar este problema en el lado del servidor . La solución CSRF no parece adecuada, ya que se genera un token CSRF por sesión (no para cada formulario). Así que aquí está el estado de mi trabajo y mi pregunta:

  • Arreglar este problema en el lado del cliente está bien, pero no me parece una buena solución. ¿Cómo podríamos evitar validar esta presentación de formularios múltiples en el lado del servidor?

Déjame saber si tienes una buena solución para eso.


Edición 1: puede ser una pequeña parte de la respuesta: Sincronizador (o Déjà vu) Token

Pero no encontré ninguna implicación de eso en Django.

Utilice HttpResponseRedirect

cree una nueva vista (digamos thank_you por thank_you ) para que un mensaje exitoso se muestre después del envío del formulario y devuelva una plantilla.

Después de enviar correctamente el formulario, devuelva HttpResponseRedirect (“/ thank-you /”) a la nueva vista de agradecimiento

 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)) 

y en urls.py

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

El envío de formularios múltiples ocurre porque cuando la página actualiza los mismos aciertos de url, que llaman a esa misma vista una y otra vez y, por lo tanto, múltiples entradas guardadas en la base de datos. Para evitar esto, estamos obligados a redirigir la respuesta a la nueva url / vista, de modo que la próxima vez que la página se actualice, llegará a esa nueva url / vista.

  1. Después de un envío exitoso, responda con una redirección para que una recarga no cause otro envío
  2. Deshabilitar el botón de enviar en el formulario de presentación

En cuanto al número 2, es mejor hacerlo en el controlador onsubmit y no en onclick para el botón de envío. Esto manejará casos como los botones de envío múltiple o si hay alguna validación de formulario del lado del cliente HTML5. En jQuery se vería algo así como:

 $('#edit_form').submit( function(event) { // disable to avoid double submission $('#submit_button').attr('disabled', true); }); 

Sin embargo, un problema que no se tiene en cuenta es si el usuario cancela el envío de forma parcial. Por ejemplo, si hace clic en “enviar” e inmediatamente presiona “Esc”, el navegador detendrá el envío pero el botón “enviar” ya estará deshabilitado.

Además, es posible tener un formulario que se envía presionando “enter” y esta solución no evitaría que el formulario se envíe dos veces.