Business
Jobs
  • About Us
  • Solutions
    • Job Postings
      Post your job and receive qualified candidates in 48h.
    • Candidate Assessments
      500+ technical and psychological tests, plus anti-fraud.
    • Headhunting
      Tailor-made executive search from start to finish.
    • Payroll + EOR
      Payroll dispersal and EOR across 15+ LATAM countries.
  • Pricing
  • Jobs

0

614
Views
Prevent repopulation and/or resubmit of Django form after using the back button

The Problem

We have the following setup.

  • Pretty standard Django class based view (inherits from CreateView, which is what I'll call it form now on).
  • After a successful POST and form validation, the object is created, and the user is redirect_to'd the DetailView of the created record.
  • Some users decide that they are not happy with the data they entered. They press the back button.
  • The HTML generated by the CreateView is fetched form browser cache, and repopulated with the data they entered.
  • To the user, this feels like an edit, so they change the data and submit again.
  • The result is 2 records, with minor differences.

What have we tried?

  1. At first I thought the Post-Redirect-Get (PRG) pattern that Django uses was supposed to prevent this. After investigating, it seems that PRG is only meant to prevent the dreaded "Do you want to resubmit the form?" dialog. Dead end.

  2. After hitting the back button, everything is fetched from cache, so we have no chance of interacting with the user from our Django code. To try and prevent local caching, we have decorated the CreateView with @never_cache. This does nothing for us, the page is still retrieved form cache.

What are we considering?

We are considering dirty JavaScript tricks that do an onLoad check of window.referrer, and a manual clean of the form and/or notice to user if the referrer looks like the DetailView mentioned earlier. Of course this feel totally wrong. Then again, so do semi-duplicate records in our DB.

However, it seems so unlikely that we are the first to be bothered by this that I wanted to ask around here on StackOverflow.

Ideally, we would tell the browser that caching the form is a big NO, and the browser would listen. Again, we already use @never_cache, but apparently this is not enough. Happens in Chrome, Safari and Firefox.

Looking forward to any insights! Thanks!

over 4 years ago · Santiago Trujillo
2 answers
Answer question

0

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)

    ...
over 4 years ago · Santiago Trujillo Report

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
over 4 years ago · Santiago Trujillo Report
Answer question
Find remote jobs

Discover the new way to find a job!

Top jobs
Top job categories
Business
Post vacancy Pricing Sales
Legal
Terms and conditions Privacy policy
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Show me some job opportunities
There's an error!