2011-12-15 3 views
13

Tutti, ho un problema con i segnali django.Perché posso accedere a un oggetto durante il segnale post_save, ma non quando faccio scattare il codice all'interno di quel segnale che lo chiama su un altro processo

Ho un modello Nel tentativo di accelerare la reattività dei carichi di pagina, sto scaricando alcune elaborazioni intensive che devono essere eseguite, tramite una chiamata a un secondo server web localhost in esecuzione, entrambi che utilizzano lo stesso database . Sto vedendo un comportamento in cui il processo chiamante può recuperare l'oggetto, ma il processo chiamato non può. Entrambe le porte 80 e port [port] stanno puntando ai processi django che scorrono dallo stesso database.

In models.py

class A(models.Model): 
    stuff... 

def trigger_on_post_save(sender, instance, create, raw, **keywords): 
    #This line works 
    A.objects.get(pk=instance.pk) 
    #then we call this 
    urlopen(r'http://127.0.0.1:[port]' + 
     reverse(some_view_url, args(instance_pk)).read() 

post_save.connect(trigger_on_post_save, A) 

In views.py

def some_view_function(request, a_pk): 
    #This line raises an object_not_found exception 
    A.objects.get(pk=a_pk) 

Inoltre, dopo che la chiamata urlopen solleva un'eccezione, l'oggetto non esiste nel database. Ho capito che post_save è stato chiamato dopo che l'oggetto è stato salvato e scritto nel database. È sbagliato?

+0

Non dovrebbe essere: reverse (some_view_url, args (instance.pk)) .read()? –

risposta

12

Credo che post_save si attivi dopo il salvataggio, ma prima che la transazione venga eseguita nel database. Per impostazione predefinita, Django esegue solo modifiche al database dopo che la richiesta è stata completata.

Due le possibili soluzioni al problema:

  1. Manage your transactions manually, e il fuoco di un segnale personalizzato dopo si commettono.
  2. Fai in modo che il tuo secondo processo attenda un po 'di tempo per la richiesta.

Per essere onesti, tuttavia, l'intera configurazione sembra un po 'sgradevole. Probabilmente dovresti esaminare Celery per l'accodamento di attività asincrone.

+0

+1 per il cenno verso Celery. Questo è un caso d'uso ideale per una coda asincrona. –

+0

tbh, mi piacerebbe entrare in un setup di Celery/RabbitMQ, ma lo sviluppatore precedente aveva già implementato il suo sistema TaskQueue, quindi stavo solo cercando di portarlo avanti. Grazie per il chiarimento sulle transazioni, è esattamente quello che stavo cercando. ** EDIT **: Continuerò a spingere il nostro team di validazione del software glaciale per approvare qualcosa del genere. – mklauber

+7

Voglio solo aggiungere che l'uso di sedano non cambia il problema - Ho avuto un compito di sedani che recuperava vecchi dati perché la transazione del save() richiesta non era ancora stata commessa. È necessario aggiungere un ritardo nel Task.run() o - meglio - creare un'istanza dell'attività "post_commit" piuttosto che "post_save". Django non fornisce questo segnale (ancora), ma dai un'occhiata a https://github.com/davehughes/django-transaction-signals –

1

Aveva lo stesso problema durante la creazione di un nuovo modello da admin django. Sovrascrivere il metodo ModelAdmin.save_model per gestire la transazione manualmente.

def save_model(self, request, obj, form, change): 
    from django.db import transaction 
    with transaction.commit_on_success(): 
     super(ModelAdmin, self).save_model(request, obj, form, change) 

    # write your code here 
16

ci siamo imbattuti in un problema simile e abbiamo finito per usare on_commit callback (NOTA: questo è possibile solo con Django> = 1.9). Quindi, si potrebbe possibile fare qualcosa di simile:

from django.db import transaction 

class A(models.Model): 
    stuff... 

def trigger_on_post_save(sender, instance, create, raw, **keywords): 
    def on_commit(): 
     urlopen(r'http://127.0.0.1:[port]' + 
       reverse(some_view_url, args(instance_pk)).read() 
    transaction.on_commit(on_commit) 

post_save.connect(trigger_on_post_save, A) 

L'idea è che si wil essere chiamata la tua endpoint dopo la transazione è stata impegnata, in modo da l'istanza oggetto dell'operazione sarà già salvato;).

+1

+1: questa è la soluzione "batterie incluse" per Django 1.9. Tieni presente che puoi persino usarlo dal segnale "pre_save", poiché si attiva solo quando la transazione viene eseguita, quindi ad es. è possibile modificare alcuni attributi del modello come richiesto prima che colpisca il database. – TimStaley

+0

Se è necessario supportare Django 1.6 - 1.8, consultare https://django-transaction-hooks.readthedocs.io/en/latest/ che è il fonte della funzionalità che è stata aggiunta nella versione 1.9 –

0

È un bel posto per usare i decoratori. C'è un po 'esteso versione di risposta di @ yoanis-gil:

def on_transaction_commit(func): 
    def inner(*args, **kwargs): 
     transaction.on_commit(lambda: func(*args, **kwargs)) 

return inner 

@receiver(post_save, sender=A) 
@on_transaction_commit 
def trigger_on_post_save(sender, **kwargs): 
    # Do things here