使用后退按钮后,防止重新填充和/或重新提交Django表单

时间:2017-04-06 17:55:53

标签: python django django-forms browser-cache back-button

问题

我们有以下设置。

  • 非常标准的基于Django类的视图(继承自CreateView,这就是我现在称之为的形式)。
  • 成功完成POST和表单验证后,将创建该对象,并且用户redirect_to并且已创建记录的DetailView
  • 有些用户认为他们对输入的数据不满意。他们按下后退按钮。
  • CreateView生成的HTML从浏览器缓存中获取,并使用他们输入的数据重新填充。
  • 对于用户来说,这感觉就像是一个编辑,因此他们会更改数据并再次提交。
  • 结果是2条记录,略有不同。

我们尝试了什么?

  1. 起初我认为Django使用的Post-Redirect-Get(PRG)模式应该可以防止这种情况发生。在调查之后,似乎PRG只是为了防止可怕的“你想重新提交表格吗?”#34;对话。死路。

  2. 点击后退按钮后,所有内容都从缓存中获取,因此我们无法从Django代码与用户交互。为了尝试阻止本地缓存,我们使用CreateView修饰了@never_cache。这对我们没有任何作用,页面仍然是从缓存中检索的。

  3. 我们在考虑什么?

    我们正在考虑对window.referrer进行onLoad检查的脏JavaScript技巧,如果引用者看起来像前面提到的DetailView,则手动清理表单和/或通知用户。当然这感觉完全错了。然后,我们的数据库中的半重复记录也是如此。

    然而,似乎不太可能我们第一个被这个困扰,我想在这里询问StackOverflow。

    理想情况下,我们会告诉浏览器缓存表单是一个很大的NO,浏览器会监听。我们再次使用@never_cache,但显然这还不够。适用于Chrome,Safari和Firefox。

    期待任何见解!谢谢!

2 个答案:

答案 0 :(得分:1)

如果POST请求来自同一页面以外的推荐人,可能不会处理该请求吗?

from urllib import parse

class CreateView(...):
  def post(self, *args, **kwargs):
    referer = 'HTTP_REFERER' in self.request.META and parse.urlparse(self.request.META['HTTP_REFERER'])
    if referer and (referer.netloc != self.request.META.get('HTTP_HOST') or referer.path != self.request.META.get('PATH_INFO')):
      return self.get(*args, **kwargs)

    ...

答案 1 :(得分:0)

我知道我参加这个聚会很晚,但这可能会帮助其他人寻找答案。

在解决同一问题时发现了这个问题,这是我使用人为因素而不是技术因素的解决方案。如果从CreateView提交后,用户最终将使用新创建的对象的UpdateView(除了标题和底部的按钮外看起来完全一样),将不使用后退按钮。

一种技术解决方案可能是创建一个模型字段来保存UUID,并创建一个传递给创建表单的UUID作为隐藏字段。当按下Submit时,form_valid可以在数据库中检入具有该UUID的对象,并拒绝创建重复的对象(unique=True将在数据库级别强制执行该操作)。

这里是示例代码(略作删除以删除雇主可能不希望在公共场所看到的东西)。它使用django-crispy-forms使事情变得轻松漂亮。通过从客户表上的按钮进入创建视图,该视图传递客户帐号,而不是其记录的Django ID。

网址

url(r'enter/(?P<customer>[-\w]+)/$', JobEntryView.as_view(), name='job_entry'),
url(r'update1/(?P<pk>\d+)/$',  JobEntryUpdateView.as_view(), name='entry_update'), 

观看次数

class JobEntryView( LoginRequiredMixin, CreateView):
    model=Job
    form_class=JobEntryForm
    template_name='utils/generic_crispy_form.html' # basically just {% crispy form %}

    def get_form( self, form_class=None):
        self.customer = get_object_or_404( 
            Customer, account = self.kwargs.get('customer','?') )
        self.crispy_title = f"Create job for {self.customer.account} ({self.customer.fullname})"       
        return super().get_form( form_class)

    def form_valid( self, form):  # insert created_by'class
        #form.instance.entered_by = self.request.user
        form.instance.customer = self.customer
        return super().form_valid(form)

    def get_success_url( self):
        return reverse( 'jobs:entry_update', kwargs={'pk':self.object.pk, } )

# redirect to this after entry ... user hopefully won't use back because it's here already
class JobEntryUpdateView( LoginRequiredMixin, CrispyCMVPlugin, UpdateView):
    model=Job
    form_class=JobEntryForm
    template_name='utils/generic_crispy_form.html'

    def get_form( self, form_class=None):
        self.customer = self.object.customer
        self.crispy_title = f"Update job {self.object.jobno} for {self.object.customer.account} ({self.object.customer.fullname})"        
        form = super().get_form( form_class)
        form.helper[-1] =  ButtonHolder( Submit('update', 'Update', ), Submit('done', 'Done', ),  )
        return form

    def get_success_url( self):
        print( self.request.POST )
        if self.request.POST.get('done',None):
            return reverse('jobs:ok')
        return reverse( 'jobs:entry_update', 
            kwargs={'pk':self.object.pk, } ) # loop until user clicks Done