2009-12-17 12 views
14

Ho problemi con ManytoMany Relationships che non aggiornano in un modello quando lo salvo (tramite l'amministratore) e provo a usare il nuovo valore all'interno di una funzione allegata a il segnale post_save o all'interno dello save_model di associato allo AdminModel. Ho provato a ricaricare l'oggetto all'interno di quelle funzioni utilizzando la funzione di acquisizione con l'id .. ma ha ancora i vecchi valori.Problema con ManyToMany Relazioni non aggiornate immediatamente dopo il salvataggio

Si tratta di un problema di transazione? C'è un segnale generato quando termina la transazione ?

Grazie,

+0

Quindi, stai cambiando il pk degli oggetti? –

+0

Ho un oggetto, e c'è una relazione molti con altri, ma posso ottenere una relazione aggiornata – diegueus9

risposta

20

Quando si salva un modello tramite forme di amministrazione, non è una transazione atomica. L'oggetto principale viene salvato per primo (per assicurarsi che abbia un PK), quindi M2M è azzerato e i nuovi valori impostati su qualsiasi cosa proveniente dal modulo. Quindi, se si è nel save() dell'oggetto principale ci si trova in una finestra di opportunità in cui M2M non è stato ancora aggiornato. Infatti, se si si prova a fare qualcosa per M2M, la modifica verrà cancellata dal clear(). Mi sono imbattuto in questo circa un anno fa.

Il codice è stato modificato in qualche modo dai giorni del refactor pre-ORM, ma si riduce al codice in django.db.models.fields.ManyRelatedObjectsDescriptor e ReverseManyRelatedObjectsDescriptor. Osserva i loro metodi __set __() e vedrai manager.clear(); manager.add(*value) Che clear() completa cancella tutti i riferimenti M2M per l'oggetto principale corrente in quella tabella. Add() quindi imposta i nuovi valori.

Quindi per rispondere alla tua domanda: sì, questo è un problema di transazione.

C'è un segnale generato quando termina la transazione? Niente di ufficiale, ma continua a leggere:

C'era un related thread a few months ago e MonkeyPatching era un metodo proposto. Grégoire posted a MonkeyPatch per questo. Non l'ho provato, ma sembra che dovrebbe funzionare.

+1

Ho incontrato anche questo problema, ho usato una soluzione trovata qui http://stackoverflow.com/questions/6200233/manytomany -field-not-saved-when-using-django-admin –

+1

Correggimi se ho torto, ma questa risposta di @peterrowell è ancora valida a partire dalla versione attuale di Django (1.10). Il campo M2M è ** cancellato ** per primo e poi viene riempito con i dati del modulo. –

+0

@nik_m: È passato un po 'di tempo dall'ultima volta che l'ho visto. Il problema fondamentale è che questa è intrinsecamente * non * una transazione atomica perché il modello primario * deve * essere creato/modificato prima che il m2m venga creato/aggiornato (perché il m2m fa riferimento ai PKID dei genitori). Potrebbe esserci un work-around menzionato [qui] (http://stackoverflow.com/a/23796604/17017) e [qui] (https://docs.djangoproject.com/en/1.10/ref/signals/# m2m-cambiato). In bocca al lupo! –

0

Un altro approccio senza una patch Scimmia è con celery, è possibile effettuare un'operazione che avrà accesso ai dati corretti per il rapporto M2M, questo è perché l'attività viene eseguita asincrono e se si mette un ritardo di 30 secondi, aproxim, è sarà sicuro che la transazione è stata conclusa.

È necessario chiamare Task.apply_async(args=[...], countdown=30) nei segnali post_save o pre_save.

0

È possibile trovare maggiori informazioni in questa discussione: Django manytomany signals?

+0

L'ho provato in django 1.4, è inutile, usando il segnale 'm2m_changed', l'assegnazione dei valori di m2m causerebbe un errore residente, se si disconnette e poi si assegna, quindi' instance.save() ', l'aggiornamento non ha funzionato. – est

5

ho una soluzione generale a questo che sembra un po 'più pulita di scimmia-patching del nucleo o anche utilizzando il sedano (anche se sono sicuro che qualcuno potrebbe trovare aree dove fallisce). Fondamentalmente aggiungo un metodo clean() nell'admin per il modulo che ha le relazioni m2m e imposta le relazioni di istanza sulla versione clean_data. Ciò rende disponibili i dati corretti al metodo di salvataggio dell'istanza, anche se non è ancora "sui libri".Provare e vedere come va:

def clean(self, *args, **kwargs): 
    # ... actual cleaning here 
    # then find the m2m fields and copy from cleaned_data to the instance 
    for f in self.instance._meta.get_all_field_names(): 
     if f in self.cleaned_data: 
      field = self.instance._meta.get_field_by_name(f)[0] 
      if isinstance(field, ManyToManyField): 
       setattr(self.instance,f,self.cleaned_data[f]) 
+0

ci sono aggiornamenti a questo problema per 1.4? – est

3

Vedi http://gterzian.github.io/Django-Cookbook/signals/2013/09/07/manipulating-m2m-with-signals.html

problema: Quando si manipolano l'M2M di un modello all'interno di un post o ricevitore di segnale pre_save, le modifiche vengono spazzati via nella successiva compensazione' 'del m2m di Django.

soluzione: In te posta o gestore di segnale pre_save, registrare un altro gestore per il segnale m2m_changed sul modello intermediario m2m del modello di cui si desidera aggiornare m2m.

Si noti che questo secondo gestore riceverà diversi segnali m2m_changed ed è fondamentale per verificare il valore degli argomenti di "azione" passati insieme a essi.

All'interno di questo secondo gestore, verificare l'azione "post_clear". Quando ricevi un segnale con l'azione post_clear, il m2m è stato cancellato da Django e hai la possibilità di manipolarlo con successo.

un esempio:

def save_handler(sender, instance, *args, **kwargs): 
    m2m_changed.connect(m2m_handler, sender=sender.m2mfield.through, weak=False) 


def m2m_handler(sender, instance, action, *args, **kwargs): 
    if action =='post_clear': 
     succesfully_manipulate_m2m(instance) 


pre_save.connect(save_handler, sender=YouModel, weak=False) 

vedere https://docs.djangoproject.com/en/1.5/ref/signals/#m2m-changed

4

Quando si tenta di accedere ai campi ManyToMany nel segnale post_save del modello degli oggetti correlate sono già stati rimossi e non verrà aggiunto di nuovo fino a dopo che il segnale è finito.

Per accedere a questi dati è necessario collegarsi al metodo save_related su ModelAdmin. Sfortunatamente dovrai includere il codice nel segnale post_save per le richieste non di amministrazione che richiedono la tua personalizzazione.

vedere: https://docs.djangoproject.com/en/1.7/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_related

Esempio:

# admin.py 
Class GroupAdmin(admin.ModelAdmin): 
    ... 
    def save_related(self, request, form, formsets, change): 
     super(GroupAdmin, self).save_related(request, form, formsets, change) 
     # do something with the manytomany data from the admin 
     form.instance.users.add(some_user) 

Poi i segnali si possono fare le stesse modifiche che si desidera eseguire su un salvataggio:

# signals.py 
@receiver(post_save, sender=Group) 
def group_post_save(sender, instance, created, **kwargs): 
    # do somethign with the manytomany data from non-admin 
    instance.users.add(some_user) 
    # note that instance.users.all() will be empty from the admin: [] 
+0

questa dovrebbe essere la risposta corretta – psychok7

Problemi correlati