2012-04-27 6 views
7

Ziemlich grundlegende Nutzung Szenario hier. Ich möchte den Benutzer, der ein Objekt erstellt hat, und den Benutzer, der es zuletzt geändert hat, speichern. Allerdings ist es ein Inline-Modell, so dass ich natürlich save_formset verwenden muss. Die Django docs hat folgenden Beispielcode:Erlaube weiteres Überschreiben von save_formset auf einem 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() 

Die Sache ist, wenn Sie feststellen, da super nie aufgerufen wird, ist dies eine Sackgasse. Wenn die ModelAdmin Unterklasse ist und diese Methode auf die gleiche Weise überschrieben wird, verlieren Sie die Funktionalität des übergeordneten Elements. Dies ist wichtig, denn dies ist eine solche gemeinsame Nutzung Szenario, das ich die Funktionalität ausklammern wollen, so habe ich die folgenden:

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) 

ich auf den Anruf zu super aus Gewohnheit mehr als alles andere geheftet, nicht zu denken, dass Tatsächlich wird das Formset zweimal gespeichert. Trotzdem funktioniert es in jedem Szenario außer einem: Löschen. Sobald Sie versuchen, eine Inline im Admin zu löschen, erhalten Sie eine Fehlermeldung. Der Fehler ist ziemlich vage und bezieht sich nicht unbedingt auf meine Frage hier, aber ich glaube, dass es damit zusammenhängt, das Formset erneut zu speichern, nachdem Sie gerade eine der Instanzen gelöscht haben. Der Code funktioniert einwandfrei, wenn der Anruf zu super entfernt wird.

Lange und kurze, gibt es eine Möglichkeit, die ich vermisse sowohl das Formset speichern Verhalten und erlauben Unterklassen, ihre eigenen überschreiben tun?

+2

Gerade gefunden [a ungelöst Ticket] (https://code.djangoproject.com/ Ticket/17988) hierfür – okm

Antwort

5

Dies ist ein Doozie.

Ich hatte etwas Spaß herum stochern und es scheint, die ganze Aktion passiert hier in django.forms.models.BaseModelFormSet.

Das Problem ist, dass ModelFormSet.save() Instanzen unabhängig von der commit Flag löscht und die Formulare nicht ändert, um den gelöschten Zustand widerzuspiegeln.

Wenn Sie erneut save() aufrufen, iteriert es über die Formulare und versucht in ModelChoiceField, die referenzierte ID hochzuladen und löst einen ungültigen Auswahlfehler aus.

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) 

Die einzige Art, wie ich dieses Problem beheben konnte ist BaseModelFormset.save_existing_objects patchen das Formular aus self.forms zu entfernen, wenn ein Objekt gelöscht wird.

Haben einige Tests durchgeführt und es scheint keine negativen Auswirkungen zu haben.

+0

Danke für die gründliche Analyse. Ich wollte nur eine Plausibilitätsprüfung machen, um sicherzugehen, dass ich nicht nur etwas komplett vermisste, aber wenn ein Patch für die Django-Quelle benötigt wird, dann scheint dies der beste Kandidat für einen Fehlerbericht zu sein. –

0

@ Chris Pratt half:

ich auf dem Aufruf von super gehefteten sonst aus Gewohnheit mehr als alles andere, denken nicht, dass es tatsächlich die formset verursachen zweimal zu speichern.

Ich habe versucht, eine save_formset weiter zu überschreiben, um ein Post-Speichern-Signal zu senden. Ich konnte einfach nicht verstehen, dass das Aufrufen von super() nur das Formset zum zweiten Mal speicherte.

Um mit dem super() Thema zu befassen, habe ich eine save_formset_now Methode mit meinem eigenen Code, dass ich anrufen, wenn ich die save_formset durch die admin.ModelAdmin Kinder außer Kraft setzen.

Dies ist der Code, der auch um das Lösch Problem zu nehmen scheint, mit Django 1.10 in 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(...) 
Verwandte Themen