2012-04-27 12 views
7

Scenario di utilizzo piuttosto semplice qui. Voglio salvare l'utente che ha creato un oggetto e l'utente che lo ha modificato per l'ultima volta. Tuttavia, è un modello in linea, quindi, ovviamente, devo usare save_formset. La documentazione Django hanno il seguente codice di esempio:Consentire l'override di save_formset su un ModelAdmin

class ArticleAdmin(admin.ModelAdmin): 
    def save_formset(self, request, form, formset, change): 
     instances = formset.save(commit=False) 
     for instance in instances: 
      instance.user = request.user 
      instance.save() 
     formset.save_m2m() 

Il fatto è che, se si nota, in quanto super non viene mai chiamato, questo è un vicolo cieco. Se lo ModelAdmin è sottoclasse e questo metodo è sovrascritto nello stesso modo, si perde la funzionalità inerente al genitore. Questo è importante perché si tratta di un tale scenario di utilizzo comune che voglio fattore la funzionalità, così ho creato il seguente:

class TrackableInlineAdminMixin(admin.ModelAdmin): 
    def save_formset(self, request, form, formset, change): 
     instances = formset.save(commit=False) 
     for instance in instances: 
      if hasattr(instance, 'created_by') and hasattr(instance, 'modified_by'): 
       if not instance.pk: 
        instance.created_by = request.user 
       instance.modified_by = request.user 
      instance.save() 
     formset.save_m2m() 
     super(TrackableInlineAdminMixin, self).save_formset(request, form, formset, change) 

ho appiccicato la chiamata a super per abitudine più che altro, non pensando che in realtà farà sì che il formset salvi due volte. Tuttavia, funziona ancora in tutti gli scenari tranne uno: eliminazione. Non appena si tenta di eliminare un inline nell'amministratore, si ottiene un errore. L'errore è piuttosto vago e non mi piace molto alla mia domanda qui, ma credo che sia collegato al tentativo di salvare di nuovo il formset dopo che hai appena eliminato una delle istanze in esso. Il codice funziona perfettamente quando viene rimossa la chiamata a super.

Lungo e breve, c'è un modo che mi manca per personalizzare il comportamento di salvataggio del formset e consentire alle sottoclassi di eseguire la propria sovrascrittura?

+2

Appena trovato [un biglietto irrisolto] (https://code.djangoproject.com/ ticket/17988) per questo – okm

risposta

5

Questo è un doozie.

Mi sono divertito un po 'a curiosare e sembra che tutta l'azione avvenga qui in django.forms.models.BaseModelFormSet.

Il problema è che ModelFormSet.save() elimina istanze indipendentemente dal flag commit e non modifica i moduli per riflettere lo stato eliminato.

Se si chiama save() nuovamente itera sui moduli e in ModelChoiceField si tenta di recuperare l'ID di riferimento e viene generato un errore di scelta non valido.

def save_existing_objects(self, commit=True): 
    self.changed_objects = [] 
    self.deleted_objects = [] 
    if not self.initial_forms: 
     return [] 

    saved_instances = [] 
    for form in self.initial_forms: 
     pk_name = self._pk_field.name 
     raw_pk_value = form._raw_value(pk_name) 

     # clean() for different types of PK fields can sometimes return 
     # the model instance, and sometimes the PK. Handle either. 
     pk_value = form.fields[pk_name].clean(raw_pk_value) 
     pk_value = getattr(pk_value, 'pk', pk_value) 

     obj = self._existing_object(pk_value) 
     if self.can_delete and self._should_delete_form(form): 
      self.deleted_objects.append(obj) 
      obj.delete() 
      # problem here causes `clean` 6 lines up to fail next round 

      # patched line here for future save() 
      # to not attempt a second delete 
      self.forms.remove(form) 

L'unico modo sono stato in grado di risolvere questo problema è quello di patchare BaseModelFormset.save_existing_objects per rimuovere il modulo dal self.forms se un oggetto viene eliminato.

Ha effettuato alcuni test e non sembrano esserci effetti negativi.

+0

Grazie per l'analisi approfondita. Volevo solo un controllo di integrità per assicurarmi che non mi mancasse del tutto qualcosa, ma se è necessaria una patch per il sorgente Django, questo sembra un ottimo candidato per un bug report. –

0

@ Chris Pratt ha aiutato:

ho appiccicato la chiamata a super per abitudine più che altro, non pensando che in realtà farà sì che il formset per salvare due volte.

Stavo cercando di escludere ulteriormente un save_formset per inviare un segnale di salvataggio post. Non riuscivo a capire che chiamare super() stia salvando il formset solo per la seconda volta.

Al fine di affrontare la questione super(), ho creato un metodo save_formset_now con il mio codice personalizzato, che io chiamo quando sovrascrivo il save_formset attraverso le admin.ModelAdmin bambini.

Questo è il codice, che sembra curare anche la questione cancellazione, utilizzando Django 1.10 nel 2016.

class BaseMixinAdmin(object): 
    def save_formset_now(self, request, form, formset, change): 
     instances = formset.save(commit=False) 
     for obj in formset.deleted_objects: 
      obj.delete() 
     for instance in instances: 
      # *** Start Coding for Custom Needs *** 
       .... 
      # *** End Coding for Custom Needs *** 
      instance.save() 
     formset.save_m2m() 

class BaseAdmin(BaseMixinAdmin, admin.ModelAdmin): 
    def save_formset(self, request, form, formset, change): 
     self.save_formset_now(request, form, formset, change) 


class ChildAdmin(BaseAdmin): 
    def save_formset(self, request, form, formset, change): 
     self.save_formset_now(request, form, formset, change) 
     my_signal.send(...) 
Problemi correlati