[Django]-Prevent repopulation and/or resubmit of Django form after using the back button

2👍

Maybe don’t process the POST request when it’s coming from a referrer other than the same page?

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)

    ...

0👍

I know I’m late to this party but this may help anybody else looking for an answer.

Having found this while tearing my hair out over the same problem, here is my solution using human factors rather than technical ones. The user won’t use the back button if after submitting from a CreateView, he ends up in an UpdateView of the newly created object that looks exactly the same apart from the title and the buttons at the bottom.

A technical solution might be to create a model field to hold a UUID and create a UUID passed into the create form as a hidden field. When submit is pressed, form_valid could check in the DB for an object with that UUID and refuse to create what would be a duplicate (unique=True would enforce that at DB level).

Here’s example code (slightly redacted to remove stuff my employer might not want in public). It uses django-crispy-forms to make things pretty and easy. The Create view is entered from a button on a table of customers which passes the customer account number, not the Django id of its record.

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

Views

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

Leave a comment