Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

615
Vistas
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 Respuestas
Responde la pregunta

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 Denunciar

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 Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda