Empresas
Empregos
  • Sobre nós
  • Soluções
    • Publicação de vagas
      Publique sua vaga e receba candidatos qualificados em 48h.
    • Avaliações de candidatos
      Mais de 500 testes técnicos e psicológicos, mais anti-fraude.
    • Headhunting
      Busca executiva personalizada do início ao fim.
    • Folha de Pagamento + EOR
      Dispersão de folha e EOR em mais de 15 países da LATAM.
  • Preços
  • Empregos

0

608
Visualizações
Evite la repoblación y/o reenvío del formulario Django después de usar el botón Atrás

El problema

Tenemos la siguiente configuración.

  • Vista bastante estándar basada en la clase Django (hereda de CreateView , que es como lo llamaré de ahora en adelante).
  • Después de una POST exitosa y una validación de formulario, se crea el objeto y se redirect_to al usuario a la DetailView de detalles del registro creado.
  • Algunos usuarios deciden que no están contentos con los datos que ingresaron. Presionan el botón Atrás.
  • El HTML generado por CreateView se obtiene de la memoria caché del navegador y se vuelve a llenar con los datos que ingresaron.
  • Para el usuario, esto se siente como una edición, por lo que cambia los datos y los vuelve a enviar.
  • El resultado son 2 registros, con pequeñas diferencias.

¿Qué hemos probado?

  1. Al principio pensé que se suponía que el patrón Post-Redirect-Get (PRG) que usa Django evitaría esto. Después de investigar, parece que PRG solo está destinado a evitar el temido "¿Desea volver a enviar el formulario?" diálogo. Callejón sin salida.

  2. Después de presionar el botón Atrás, todo se recupera del caché, por lo que no tenemos posibilidad de interactuar con el usuario desde nuestro código Django. Para intentar evitar el almacenamiento en caché local, hemos decorado CreateView con @never_cache . Esto no hace nada por nosotros, la página aún se recupera del caché.

¿Qué estamos considerando?

Estamos considerando trucos sucios de JavaScript que hacen una verificación onLoad de window.referrer, y una limpieza manual del formulario y/o aviso al usuario si el referente se parece al DetailView mencionado anteriormente. Por supuesto, esto se siente totalmente mal. Por otra parte, también lo hacen los registros semiduplicados en nuestra base de datos.

Sin embargo, parece tan poco probable que seamos los primeros en molestarnos por esto que quería preguntar por aquí en StackOverflow.

Idealmente, le diríamos al navegador que almacenar en caché el formulario es un gran NO, y el navegador escucharía. Nuevamente, ya usamos @never_cache , pero aparentemente esto no es suficiente. Ocurre en Chrome, Safari y Firefox.

Mirando hacia adelante a cualquier idea! ¡Gracias!

about 4 years ago · Santiago Trujillo
2 Respostas
Responde à pergunta

0

¿Tal vez no procese la solicitud POST cuando proviene de una referencia que no sea la misma página?

 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) ...
about 4 years ago · Santiago Trujillo Relatório

0

Sé que llego tarde a esta fiesta, pero esto puede ayudar a cualquiera que busque una respuesta.

Habiendo encontrado esto mientras me arrancaba los pelos por el mismo problema, aquí está mi solución utilizando factores humanos en lugar de técnicos. El usuario no usará el botón Atrás si después de enviar desde un CreateView, termina en un UpdateView del objeto recién creado que se ve exactamente igual, aparte del título y los botones en la parte inferior.

Una solución técnica podría ser crear un campo modelo para contener un UUID y crear un UUID pasado al formulario de creación como un campo oculto. Cuando se presiona enviar, form_valid podría verificar en la base de datos un objeto con ese UUID y negarse a crear lo que sería un duplicado ( unique=True lo aplicaría a nivel de base de datos).

Aquí hay un código de ejemplo (ligeramente redactado para eliminar cosas que mi empleador podría no querer en público). Utiliza django-crispy-forms para hacer las cosas bonitas y fáciles. La vista Crear se ingresa desde un botón en una tabla de clientes que pasa el número de cuenta del cliente, no la identificación de Django de su registro.

URL

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

Puntos de vista

 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
about 4 years ago · Santiago Trujillo Relatório
Responde à pergunta
Encontrar trabalhos remotos

Descubra a nova forma de encontrar um emprego!

melhores empregos
Principais categorias de trabalho
Empresas
Postar vaga Preços Comercial
Jurídico
Termos e Condições Política de privacidade
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomende algumas ofertas para mim
Preciso de ajuda