防止在使用后退按钮后重新填写Django表单

3

问题

我们有以下设置。

  • 一个相当标准的Django类视图(继承自CreateView,从现在开始我将称其为表单)。
  • 成功提交并验证表单后,对象被创建,用户被重定向到创建记录的DetailView
  • 有些用户发现他们输入的数据不太满意。他们按下“后退”按钮。
  • CreateView 生成的HTML从浏览器缓存中获取,并重新填充了他们输入的数据。
  • 对于用户来说,这就像是一次编辑,所以他们更改了数据并再次提交。
  • 结果是2条记录存在微小差异。

我们尝试过什么?

  1. 起初,我认为Django使用的Post-Redirect-Get(PRG)模式应该可以防止这种情况。经过调查,似乎PRG仅旨在防止可怕的“是否要重新提交表单?”对话框。死路一条。

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

我们正在考虑什么?

我们正在考虑使用一些不太优雅的JavaScript技巧,在onLoad 时检查window.referrer,并在引用程序看起来像前面提到的DetailView 时手动清除表单和/或通知用户。当然,这感觉完全不对。不过,我们数据库中的半重复记录也是如此。

然而,我们似乎不可能是第一个受到此问题困扰的人,我想在StackOverflow上询问一下。

理想情况下,我们会告诉浏览器不要缓存表单,浏览器会听从我们的指示。再次强调,我们已经使用了@never_cache,但显然这还不够。这在Chrome、Safari和Firefox中都会发生。

期待任何见解!谢谢!

2个回答

2
也许当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)

    ...

赞同创意。然而,这可能会冒犯用户。因为用户使用缓存中的“CreateView”输入(大量)新数据,而像这样的修复将使数据消失,甚至没有解释。我们要做的是防止缓存表单出现。 - dyve

0

我知道我来晚了,但这可能会帮助任何其他寻找答案的人。

在为同样的问题苦恼时,我发现了这个解决方案,它使用人类因素而不是技术因素。如果用户从CreateView提交后进入一个UpdateView,该视图显示与标题和底部按钮除外完全相同的新创建对象,则用户将不会使用返回按钮。

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

以下是示例代码(稍微编辑以删除雇主可能不想公开的内容)。它使用django-crispy-forms使事情变得漂亮和简单。从客户表上的按钮进入Create视图,该按钮传递客户帐号而不是其记录的Django ID。

Urls

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接