Tenemos la siguiente configuración.
CreateView , que es como lo llamaré de ahora en adelante).redirect_to al usuario a la DetailView de detalles del registro creado.CreateView se obtiene de la memoria caché del navegador y se vuelve a llenar con los datos que ingresaron.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.
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é.
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!
¿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) ...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