2010-12-13 21 views
11

(Django 1.1) Ho un modello di progetto che tiene traccia dei suoi membri utilizzando un campo m2m. Ecco come si presenta:Django - Come salvare i dati m2m tramite segnale post_save?

class Project(models.Model): 
    members = models.ManyToManyField(User) 
    sales_rep = models.ForeignKey(User) 
    sales_mgr = models.ForeignKey(User) 
    project_mgr = models.ForeignKey(User) 
    ... (more FK user fields) ... 

Quando viene creato il progetto, selezionato sales_rep, sales_mgr, project_mgr, ecc User s vengono aggiunti ai membri per rendere più facile per tenere traccia dei permessi di progetto. Questo approccio ha funzionato molto bene finora.

Il problema che sto trattando ora è come aggiornare l'appartenenza al progetto quando uno dei campi FK User viene aggiornato tramite l'amministratore. Ho provato varie soluzioni a questo problema, ma l'approccio più pulito sembrava essere un segnale post_save come la seguente:

def update_members(instance, created, **kwargs): 
    """ 
    Signal to update project members 
    """ 
    if not created: #Created projects are handled differently 
     instance.members.clear() 

     members_list = [] 
     if instance.sales_rep: 
      members_list.append(instance.sales_rep) 
     if instance.sales_mgr: 
      members_list.append(instance.sales_mgr) 
     if instance.project_mgr: 
      members_list.append(instance.project_mgr) 

     for m in members_list: 
      instance.members.add(m) 
signals.post_save.connect(update_members, sender=Project) 

Tuttavia, il Project ha ancora gli stessi membri, anche se cambio uno dei campi attraverso il Admin! Ho avuto successo nell'aggiornare i campi m2m dei membri usando le mie opinioni in altri progetti, ma non ho mai dovuto farlo funzionare bene con l'amministratore.

C'è un altro approccio che dovrei prendere oltre a un segnale post_save per aggiornare l'abbonamento? Grazie in anticipo per il vostro aiuto!

UPDATE:

solo per chiarire, il segnale post_save funziona correttamente quando salvo la mia propria forma nel front-end (vecchi membri vengono rimossi, e quelli nuovi aggiunti). Tuttavia, il segnale post_save NON funziona correttamente quando salvi il progetto tramite l'admin (i membri rimangono uguali).

Penso che la diagnosi di Peter Rowell sia corretta in questa situazione. Se rimuovo il campo "membri" dal modulo di amministrazione, il segnale post_save funziona correttamente. Quando il campo è incluso, salva i vecchi membri in base ai valori presenti nel modulo al momento del salvataggio. Indipendentemente dalle modifiche apportate al campo m2m dei membri quando il progetto viene salvato (sia esso un segnale o un metodo di salvataggio personalizzato), verrà sempre sovrascritto dai membri presenti nel modulo prima del salvataggio. Grazie per la segnalazione!

+2

Non so se questo è il tuo problema, ma ho una sensazione viscerale che si può essere in esecuzione in un manufatto di come il codice di forme aggiorna informazioni m2m. Fondamentalmente prima salvano l'oggetto principale, quindi impostano i valori m2m prima cancellandoli tutti e quindi impostandoli in base ai valori presenti * nel modulo *. Questo accade * dopo * il save() sull'oggetto principale, quindi tutto ciò che fai in save() o basato sul segnale 'post_save' viene prima fatto, e quindi * annullato *. Questo è in 'django.forms.models.save_instance()'. Sarebbe bello se ci fosse un segnale 'after_form_save'. –

+0

Grazie, Peter! Credo che la tua diagnosi sia corretta. Ho aggiornato il mio post originale per includere queste informazioni. –

+0

Peter ha ragione. Ho avuto lo stesso problema e ho trovato una soluzione alternativa, ma non è un segnale "after_form_save": http://stackoverflow.com/questions/3652585/simple-django-form-model-save-question –

risposta

4

Non riesco a vedere nulla di sbagliato nel codice, ma sono confuso sul motivo per cui pensi che l'amministratore dovrebbe funzionare in modo diverso da qualsiasi altra app.

Tuttavia, devo dire che penso che la struttura del modello sia sbagliata. Penso che sia necessario sbarazzarsi di tutti quei campi di ForeignKey e avere un ManyToMany - ma utilizzare un tavolo di confronto per tenere traccia dei ruoli.

class Project(models.Model): 
    members = models.ManyToManyField(User, through='ProjectRole') 

class ProjectRole(models.Model): 
    ROLES = (
     ('SR', 'Sales Rep'), 
     ('SM', 'Sales Manager'), 
     ('PM', 'Project Manager'), 
    ) 
    project = models.ForeignKey(Project) 
    user = models.ForeignKey(User) 
    role = models.CharField(max_length=2, choices=ROLES) 
+0

Concordo sul fatto che la struttura del modello debba essere migliorata, ma sto lavorando con un'implementazione più vecchia e sto cercando di sfruttarla al meglio. Al momento, non sono pronto a migrare il sistema a questa nuova struttura, ma terrò presente il tuo suggerimento per il futuro. Grazie. –

6

Avendo avuto lo stesso problema, la mia soluzione è utilizzare il segnale m2m_changed. Puoi usarlo in due posti, come nell'esempio seguente.

L'amministratore sul risparmio procederà a:

  • salvare i campi modello
  • emettono il segnale post_save
  • per ogni m2m:
    • emettono pre_clear
    • chiara la relazione
    • emit post_clear
    • emettono pre_add
    • ripopolare
    • emettono post_add

Ecco un semplice esempio che cambia il contenuto dei dati salvati prima di poter realmente salvarlo.

class MyModel(models.Model): 

    m2mfield = ManyToManyField(OtherModel) 

    @staticmethod 
    def met(sender, instance, action, reverse, model, pk_set, **kwargs): 
     if action == 'pre_add': 
      # here you can modify things, for instance 
      pk_set.intersection_update([1,2,3]) 
      # only save relations to objects 1, 2 and 3, ignoring the others 
     elif action == 'post_add': 
      print pk_set 
      # should contain at most 1, 2 and 3 

m2m_changed.connect(receiver=MyModel.met, sender=MyModel.m2mfield.through) 

È possibile anche ascoltare pre_remove, post_remove, pre_clear e post_clear. Nel mio caso sto usando li per filtrare una lista ('cose attivi') entro il contenuto di un altro ('cose abilitati') indipendenti l'ordine in cui vengono salvate le liste:

def clean_services(sender, instance, action, reverse, model, pk_set, **kwargs): 
    """ Ensures that the active services are a subset of the enabled ones. 
    """ 
    if action == 'pre_add' and sender == Account.active_services.through: 
     # remove from the selection the disabled ones 
     pk_set.intersection_update(instance.enabled_services.values_list('id', flat=True)) 
    elif action == 'pre_clear' and sender == Account.enabled_services.through: 
     # clear everything 
     instance._cache_active_services = list(instance.active_services.values_list('id', flat=True)) 
     instance.active_services.clear() 
    elif action == 'post_add' and sender == Account.enabled_services.through: 
     _cache_active_services = getattr(instance, '_cache_active_services', None) 
     if _cache_active_services: 
      instance.active_services.add(*list(instance.enabled_services.filter(id__in=_cache_active_services))) 
      delattr(instance, '_cache_active_services') 
    elif action == 'pre_remove' and sender == Account.enabled_services.through: 
     # de-default any service we are disabling 
     instance.active_services.remove(*list(instance.active_services.filter(id__in=pk_set))) 

Se il "abilitato" quelli sono aggiornati (cancellati/rimossi + aggiunti di nuovo, come nell'admin), quelli "attivi" vengono memorizzati nella cache e cancellati nel primo passaggio ('pre_clear') e quindi aggiunti nuovamente dalla cache dopo il secondo passaggio ('post_add') .

Il trucco era aggiornare una lista sui segnali m2m_changed dell'altro.

+0

Hai salvato la mia giornata! Grazie :) –

0

Mi sono bloccato sulla situazione, quando avevo bisogno di trovare l'ultimo elemento dal set di elementi, quello collegato al modello tramite m2m_field.

seguito la risposta di Saverio, codice seguente risolto il mio problema:

def update_item(sender, instance, action, **kwargs): 
    if action == 'post_add': 
     instance.related_field = instance.m2m_field.all().order_by('-datetime')[0] 
     instance.save() 

m2m_changed.connect(update_item, sender=MyCoolModel.m2m_field.through) 
Problemi correlati