2012-04-27 10 views
7

Dość podstawowy scenariusz użycia tutaj. Chcę zapisać użytkownika, który utworzył obiekt i użytkownika, który go zmodyfikował. Jest to jednak model inlined, więc oczywiście potrzebuję użyć save_formset. Django docs mają następujący przykładowy kod:Umożliwienie dalszego zastępowania save_formset na 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() 

Chodzi o to, jeśli zauważy, ponieważ super nigdy nie jest wywoływana, to jest ślepa. Jeśli podklasowana jest ModelAdmin i ta metoda jest nadpisywana w ten sam sposób, tracisz funkcjonalność właściwą rodzicowi. To ważne, ponieważ jest to taki wspólny scenariusz wykorzystanie że chcę czynnik poza funkcjonalność, więc stworzyłem następujące:

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) 

ja zostałam zaatakowana na wezwanie do super z przyzwyczajenia bardziej niż cokolwiek innego, nie myśląc, że to faktycznie spowoduje, że zestaw będzie dwa razy oszczędzał. Niemniej jednak nadal działa w każdym scenariuszu z wyjątkiem jednego: usuwanie. Natychmiast po próbie usunięcia wbudowanego w administratora pojawia się błąd. Błąd jest dość nieprecyzyjny i nie jest to odpowiedź na moje pytanie tutaj, ale uważam, że jest to związane z próbą zapisania formformy ponownie po tym, jak właśnie usunąłeś jedną z wystąpień. Kod działa poprawnie po usunięciu połączenia z super.

Długi i krótki, czy jest jakiś sposób, że brakuje mi zarówno dostosować zachowanie oszczędzania zestawu formularzy i umożliwić podklasom zrobić własne przesłonięcie?

+2

Wystarczy znaleźć [a nierozwiązane bilet] (https://code.djangoproject.com/ Bilet/17988) za to – okm

Odpowiedz

5

To jest doozie.

Miałem świetną zabawę i wygląda na to, że cała akcja dzieje się tutaj w django.forms.models.BaseModelFormSet.

Problem polega na tym, że ModelFormSet.save() usuwa wystąpienia bez względu na flagę commit i nie modyfikuje formularzy w celu odzwierciedlenia stanu usuniętego.

Jeśli ponownie zadzwonisz pod numer save(), będzie on iterować po formularzach, a w trakcie czyszczenia spróbuje podnieść identyfikator, do którego następuje odwołanie, i zgłosi błąd nieprawidłowego wyboru.

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) 

Jedynym sposobem udało mi się naprawić to załatać BaseModelFormset.save_existing_objects usunąć formę z self.forms jeśli obiekt zostanie usunięty.

Zrobiłem kilka testów i nie wydaje się, aby były jakiekolwiek złe efekty.

+0

Dzięki za dokładną analizę. Chciałem tylko sprawdzić poprawność, aby upewnić się, że czegoś nie brakuje, ale jeśli łata jest wymagana do źródła Django, to wydaje się być głównym kandydatem do zgłoszenia błędu. –

0

@Chris Pratt pomógł:

ja zostałam zaatakowana na wezwanie Super poza przyzwyczajenia bardziej niż cokolwiek innego, nie myśleć, że rzeczywiście spowoduje formset zapisać dwukrotnie.

Próbowałem dalej przesłonić save_formset w celu wysłania sygnału zapisu postu. Po prostu nie mogłem zrozumieć, że dzwonienie pod numer super() zapisało tylko formset po raz drugi.

W celu radzenia sobie z super() kwestii, stworzył metodę save_formset_now z mojego kodu niestandardowego, że zadzwonię, kiedy zastąpić save_formset przez admin.ModelAdmin dzieci.

Jest to kod, który wydaje się również zadbać o kwestii usuwania, używając Django 1.10 w 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(...) 
Powiązane problemy