2010-01-26 5 views
17

Sto cercando di trovare un modo per impedire agli utenti di inviare i miei moduli in duplice copia. Ho javascript che disabilita il pulsante di invio, ma c'è ancora un utente occasionale che trova un modo per inviare due volte.Esiste una libreria per impedire l'invio di moduli duplicati per django?

Ho una visione di una libreria riutilizzabile che potrei creare per proteggere da questo.

Nella mia biblioteca ideale, il blocco di codice sarebbe simile a questa:

try: 
    with acquire_lock({'field1':'abc', 'field2':'def'}) as lock: 
     response = #do some credit card processing 
     lock.response = response 
except SubmissionWasDuplicate, e: 
    response = e.response 

La tabella di blocco sarebbe simile a questa:

duplicate_submission_locks

  • submission_hash # un MD5 gli argomenti presentati
  • risposta # dati decapati
  • created_at # utilizzato per spazzare questa tabella
  • lock_expired # booleano che indica se il blocco è scaduto

Qualcuno sa se questo esiste già? Non sembra difficile da scrivere, quindi se non esiste potrei scriverlo da solo.

+0

Date un'occhiata a questa domanda http: // StackOverflow. it/questions/320096/django-how-can-i-protect-against-concurrent-modification-data-base-entries ha alcune buone idee sul blocco ottimistico –

+0

Il blocco ottimistico è vicino a quello che mi serve, tranne 2 cose. 1) Questo è su un modulo di registrazione quindi non c'è ancora un campo modello da aggiornare. 2) Se si tratta di una doppia presentazione, voglio che entrambe le presentazioni mostrino la pagina di successo o la pagina di prova come sarebbe appropriato. – Gattster

risposta

6

Una soluzione facile a questo problema è aggiungere un hash univoco a ogni modulo. Quindi puoi avere un tavolo mobile di moduli attuali. Quando viene inviato un modulo o l'hash diventa troppo vecchio, puoi espellerlo dalla tabella e rifiutare qualsiasi modulo che non ha un hash corrispondente nella tabella.

HTTPRedirect è il modo corretto di farlo, come accennato in precedenza.

Sfortunatamente, anche l'amministratore incorporato di Django è incline a problemi legati a questo problema. In alcuni casi, il framework di cross-site scripting può aiutare a prevenire alcuni di questi problemi, ma temo che le attuali versioni di produzione non abbiano questo integrato.

+0

Mi chiedo se questo potrebbe colpire glitch se l'utente usa il suo pulsante indietro e reinvia la stessa forma con campi diversi? Avremmo bisogno di aggiornare l'hash unico. – Gattster

+0

La tua richiesta era di prevenire il nuovo invio. Rimuovere l'hash dal DB dovrebbe farlo facilmente. Un modulo presentato con valori leggermente diversi è ancora una ri-presentazione nella maggior parte dei casi, anche se è possibile utilizzare l'hash per memorizzare un riferimento temporaneo a inoltro all'invio, consentendo la modifica di una presentazione precedente per un determinato periodo di tempo. –

4

Per essere onesti, la soluzione migliore (pratica facile e buona) consiste nell'emettere un HTTPRedirect() alla pagina di ringraziamento, e se la pagina di ringraziamento è la stessa del modulo, va bene. Puoi ancora farlo.

+0

Grazie per aver risposto. Fare un HTTPRedirect per visualizzare la pagina di ringraziamento è ciò che avevo in mente. Questa domanda è più su come rilevare che il post è duplicato. – Gattster

2

È sempre consigliabile utilizzare il reindirizzamento dopo -post metodo. Ciò impedisce all'utente di reinviare accidentalmente il modulo utilizzando la funzione di aggiornamento dal browser. È anche utile anche quando usi il metodo hash. È perché senza reindirizzare dopo un POST, in caso di pulsante Indietro/Aggiorna, l'utente vedrà un messaggio di domanda su come inviare nuovamente il modulo, che può confonderla.

Se si esegue un reindirizzamento GET dopo ogni POST, premendo Back/Refresh non verrà visualizzato questo messaggio wierd (per il solito utente). Quindi per la protezione completa usa Hash + redirect-after-post.

10

È possibile utilizzare una sessione per memorizzare l'hash

import hashlib 

def contact(request): 
    if request.method == 'POST': 
     form = MyForm(request.POST) 
     #join all the fields in one string 
     hashstring=hashlib.sha1(fieldsstring) 
     if request.session.get('sesionform')!=hashstring: 
      if form.is_valid() :           
       request.session['sesionform'] = hashstring 
       #do some stuff... 
       return HttpResponseRedirect('/thanks/') # Redirect after POST 
     else 
      raise SubmissionWasDuplicate("duplicate") 
    else: 
     form = MyForm() 

Con questo approccio (non cancellando il cookie di sessione) l'utente non può ri-memorizzare i dati util la sessione scade, tra l'altro, i Supponendo che esista qualcosa che identifica l'utente che invia i dati

+0

Cosa succede se l'utente raggiunge/thanks/page e la transazione non è ancora finita? Inoltre, cosa succede se la transazione fallisce? – Gattster

+1

se la transazione fallisce, è necessario terminare la sessione e reindirizzare a una pagina di errore. Se vuoi essere sicuro di arrivare alla pagina "/ thanks" solo quando la transazione è completa e valida, devi aggiungere un token quando le transazioni sono completate e inviarlo alla pagina "/ thanks" e convalidato lì (proprio come paypal fa) –

3

La risposta di Kristian Damian è davvero un grande suggerimento. Ho solo pensato ad una leggera variazione su quel tema, ma potrebbe avere un sovraccarico in più.

Si potrebbe provare l'attuazione di qualcosa che viene utilizzato in django-piston per BaseHandler oggetti, che è un metodo chiamato exists() che controlla per vedere se ciò che si sta presentando è già presente nel database.

Da handler.py (BaseHandler):

def exists(self, **kwargs): 
    if not self.has_model(): 
     raise NotImplementedError 

    try: 
     self.model.objects.get(**kwargs) 
     return True 
    except self.model.DoesNotExist: 
     return False 

Quindi diciamo fanno che una funzione chiamata request_exists(), invece di un metodo:

if form.is_valid() 
    if request_exists(request): 
     # gracefully reject dupe submission 
    else: 
     # do stuff to save the request 
     ... 
     # and ALWAYS redirect after a POST!! 
     return HttpResponseRedirect('/thanks/') 
+0

Questo funzionerà. La domanda principale che questo solleva per me è come gestire con garbo l'invio dupe. Dal momento che il primo invio probabilmente non è ancora terminato, ho bisogno di posticipare fino al termine della prima submission. Quindi ho bisogno di inviare l'utente alla pagina dei ringraziamenti e di trovare il risultato dell'invio, che avrebbe potuto riuscire o fallire con un messaggio di errore. – Gattster

+0

Bene, non è necessario inviarli a una pagina "/ thanks /" nuda. È possibile inviarlo insieme ai dati della richiesta in modo che il modello sia in grado di elaborare i risultati dell'invio e visualizzarli di conseguenza. Forse una pagina "/ add/confirm /" interstiziale sarebbe buona prima di arrivare a "/ thanks /"? – jathanism