2012-04-27 5 views
7

Escenario de uso bastante básico aquí. Quiero guardar el usuario que creó un objeto y el usuario que lo modificó por última vez. Sin embargo, es un modelo en línea así que, por supuesto, necesito usar save_formset. La documentación de Django tienen el siguiente código de ejemplo:Permitir anulación adicional de save_formset en 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() 

La cosa es, si se nota, ya que nunca se super se llama, esto es un callejón sin salida. Si se subclasifica el ModelAdmin y este método se reemplaza de la misma manera, se pierde la funcionalidad inherente al padre. Esto es importante porque se trata de un escenario de uso tan común que quiero factorizar la funcionalidad, por lo que crea el siguiente:

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) 

me insertan en la llamada a super por costumbre más que cualquier otra cosa, sin pensar que en realidad hará que el formset ahorre dos veces. Sin embargo, todavía funciona en todos los escenarios excepto en uno: eliminar. Tan pronto como intentes eliminar una línea en el administrador, obtienes un error. El error es bastante vago y no es realmente relevante para mi pregunta aquí, pero creo que está relacionado con intentar guardar el formset de nuevo después de haber eliminado una de las instancias en él. El código funciona bien cuando se elimina la llamada a super.

De largo y corto, ¿hay alguna forma de que me falte personalizar el comportamiento de guardado del conjunto de formularios y para permitir que las subclases hagan su propia anulación?

+2

Sólo encontrado [un boleto sin resolver] (https://code.djangoproject.com/ ticket/17988) para este – okm

Respuesta

5

Esto es un doozie.

Me divertí hurgando y parece que toda la acción ocurre aquí en django.forms.models.BaseModelFormSet.

El problema es que ModelFormSet.save() elimina las instancias independientemente del commit marca y no modifica los formularios para reflejar el estado eliminado.

Si llama de nuevo save(), itera sobre los formularios y en ModelChoiceField, la limpieza intenta levantar la ID referenciada y arroja un error de elección no válida.

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) 

La única forma que era capaz de solucionar este problema es para parchear BaseModelFormset.save_existing_objects para eliminar la forma de self.forms si se elimina un objeto.

Hice algunas pruebas y no parece haber ningún efecto perjudicial.

+0

Gracias por el análisis exhaustivo. Solo quería un control de cordura para asegurarme de que no me estaba perdiendo completamente de algo, pero si se necesita un parche para la fuente de Django, este parece ser un candidato principal para un informe de error. –

0

@ Chris Pratt ayudaron:

que anotaron la llamada a super por costumbre más que cualquier otra cosa, no pensar que en realidad hará que el juego de formularios para guardar dos veces.

Estaba tratando de anular más un save_formset para enviar una señal de guardar señal. Simplemente no podía entender que llamar al super() solo guardaba el formset por segunda vez.

Con el fin de tratar el tema super(), he creado un método save_formset_now con mi código personalizado, que yo llamo cuando anular el save_formset a través de los admin.ModelAdmin niños.

Este es el código, lo que parece también tener cuidado de la edición de borrado, utilizando Django 1,10 en 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(...)