我正在寻找一种通用的方法来防止多次提交表单。我发现这种方法看起来很有希望。而我不想在我的所有视图中包含这个片段。使用请求处理器或中间件可能更容易做到这一点。
有什么最佳实践建议吗?
客户端,从 JavaScript 开始。你永远不能相信客户,但它是一个开始。
IE
onclick="this.disabled=true,this.form.submit();
服务器端您“可以”将某些内容插入数据库,即校验和。如果它是您插入数据库的记录,model.objects.get_or_create()
用于强制数据库级别的唯一性,您应该使用 unique_together。
最后:HTTPRedirect 是最好的,我在用户处理付款时使用的方法是向感谢/确认页面发出 HTTPRedirect()。这种方式刷新表单不会重新提交,如果他们返回并尝试再次提交表单(不刷新表单),Django Cross Site Request Forgery (CSRF) 将失败,完美!
我还尝试找到一种好方法来防止在用户 dbl-click 提交按钮时生成双重记录。 这与通过重定向很容易解决的 PRG 问题无关。
因此,关于这个基本问题,在服务器端使用 HTTPRedirect 的解决方案没有帮助。
在客户端,我在提交前禁用按钮时发现了两个问题:
form.submit()
如果表单无效 => 提交按钮仍然是 ,浏览器将中断disabled=true
。disabled=true
.所以这是我针对第一个客户端问题(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();
}
我尝试为第二个客户端问题(浏览器缓存 DOM)找到客户端解决方法,但没有任何效果(onbeforeunload,...)。所以我目前用于“浏览器缓存”问题的解决方法是在相关视图的顶部添加一个@never_cache 装饰(从服务器端,指示客户端不缓存)。如果您有更好的解决方法,请告诉我。
最后但并非最不重要的一点是,我非常感谢在服务器端解决此问题。CSRF 解决方案似乎不适合,因为CSRF 令牌是由会话生成的(不是针对每个表单)。所以这是我的工作状态和我的问题:
让我知道你是否有一个好的解决方案。
编辑1: 可能是答案的一小部分:Synchronizer (or Déjà vu) Token
但我没有在 Django 中找到任何实现。
使用HttpResponseRedirect
创建一个新视图(让我们说thank_you
)以在表单提交后显示成功消息并返回模板。
成功提交表单后,将 HttpResponseRedirect("/thank-you/")返回到新的感谢视图
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))
并在 urls.py
url(r'^thank-you/$','thank_you', name="thank_you")
发生多个表单提交是因为当页面刷新相同的 url 命中时,它会一次又一次地调用相同的视图,因此在数据库中保存了多个条目。为了防止这种情况,我们需要将响应重定向到新的 url/view,以便下次页面刷新时它会点击新的 url/view。
至于#2,最好在onsubmit
处理程序中执行此操作,而不是onclick
提交按钮。这将处理多个提交按钮或任何 HTML5 客户端表单验证等情况。在 jQuery 中,它看起来像:
$('#edit_form').submit( function(event) {
// disable to avoid double submission
$('#submit_button').attr('disabled', true);
});
但是,这没有考虑的一个问题是用户是否中途取消了提交。例如,如果您单击“提交”,然后立即按“Esc”,浏览器将停止提交,但“提交”按钮已被禁用。
此外,可以通过点击“输入”来提交表单,并且此解决方案不会阻止该表单提交两次。