Code

Opened 4 years ago

Closed 3 years ago

Last modified 2 years ago

#13950 closed Uncategorized (wontfix)

Add "post save" hook to ModelAdmin class

Reported by: 3point2 Owned by: pandres
Component: contrib.admin Version: 1.2
Severity: Normal Keywords:
Cc: Triage Stage: Unreviewed
Has patch: yes Needs documentation: yes
Needs tests: yes Patch needs improvement: yes
Easy pickings: no UI/UX: no

Description

If a model with a set of related objects is edited in the admin, there is currently no way to run any code once the admin site has saved the related objects.

For example, consider an invoice:

models.py:

class Invoice(models.Model):
    # fields like customer_name, etc
    total = models.DecimalField(max_digits=11, decimal_places=2)

    def update_total(self):
        self.total = sum([x.cost for x in self.invoiceentry_set.all()]

class InvoiceEntry(models.Model):
    invoice = models.ForeignKey(Invoice)
    item = models.CharField(max_length=100)
    cost = models.DecimalField(max_digits=11, decimal_places=2)
    # etc

When clicking "save" in the admin after editing or adding an invoice in the admin interface, there is no way to have the invoice update its total using the update_total method. Overriding the save() method on the Invoice model doesn't help, because it is called before the related InvoiceEntries are created and saved.

I supposes the functionality I want could be implemented by overriding the save() method for the InvoiceEntry model and have it update the total of the parent instance, although this seems a little inelegant as the Invoice instance would then be updated and saved once for every invoice entry created or edited.

I suggest adding a "post_save" option to the ModelAdmin class, which will be called once all related objects have been saved. Please consider the attached patch. It allows the following:

admin.py:

class InvoiceAdmin(admin.ModelAdmin):
    def post_save(self, instance):
        instance.update_total()

admin.site.register(Invoice, InvoiceAdmin)

With the patch, the post_save method above is called after the invoice and all its related entries have been created and saved.

If this is considered for inclusion into Django, I'd be happy to modify the patch to include documentation.

Vasili

Attachments (1)

post-save.diff (966 bytes) - added by 3point2 4 years ago.

Download all attachments as: .zip

Change History (6)

Changed 4 years ago by 3point2

comment:1 Changed 3 years ago by pandres

  • Needs documentation unset
  • Needs tests unset
  • Owner changed from nobody to pandres
  • Patch needs improvement unset

comment:2 Changed 3 years ago by pandres

  • Needs documentation set
  • Needs tests set
  • Patch needs improvement set
  • Resolution set to wontfix
  • Status changed from new to closed

I'm not sure if I understand what do you mean. How will the model know if all of its related objects have been saved?

There are many ways you can do this, including showing the related objects in the same admin view of the main object (http://docs.djangoproject.com/en/dev/ref/contrib/admin/#inlinemodeladmin-objects).

In your example you can also compute the total when getting the attribute.

comment:3 follow-up: Changed 3 years ago by 3point2

  • Resolution wontfix deleted
  • Status changed from closed to reopened

I'll try explain the problem better.

Let's start with the example I described above: the Invoice and InvoiceEntry models. I have registered the Invoice model with the admin and also registered the InvoiceEntry model as an inline (as you suggest).

Let's say a user creates a new invoice, and adds several invoice entries using the inline forms. When the user clicks on "Save", there is no way to calculate the total of the invoice. This is why:

  • First, the invoice is saved. This calls the save_model method on the modeladmin, which calls the save() method on the Invoice model. At this point, none of the invoice entries have been saved yet, so it's not possible to calculate a total in save_model() or save()
  • The post_save signal is sent when save() completes. The invoice's invoiceentry_set manager still returns an empty list as the inlines have not been saved yet.
  • The inline invoice entries are now saved. At this point, it's no longer possible to hook into the save process to calculate the invoice's total. The only workaround would be to have each invoice entry update its parent invoice's total when it is saved, which will cause a lot of unnecessary save() calls on the invoice (one for each entry).

My post_save hook allows the user to call a function *after* the inlines have been saved. This allows the total to be calculated using invoice.invoiceentry_set.all()

comment:4 in reply to: ↑ 3 Changed 3 years ago by kmtracey

  • Resolution set to wontfix
  • Status changed from reopened to closed

Replying to 3point2:

  • The inline invoice entries are now saved. At this point, it's no longer possible to hook into the save process to calculate the invoice's total. The only workaround would be to have each invoice entry update its parent invoice's total when it is saved, which will cause a lot of unnecessary save() calls on the invoice (one for each entry).

No, you can override the ModelAdmin's save_formset (see: http://docs.djangoproject.com/en/dev/ref/contrib/admin/#django.contrib.admin.ModelAdmin.save_formset) and add whatever code you like after all the inline models have been saved.

My post_save hook allows the user to call a function *after* the inlines have been saved. This allows the total to be calculated using invoice.invoiceentry_set.all()

So does the existing capability of overriding save_formset. I don't see why another way is needed.

comment:5 Changed 2 years ago by anonymous

  • Easy pickings unset
  • Severity set to Normal
  • Type set to Uncategorized
  • UI/UX unset

this was actually added in r16498 (see #16115) in the form of a similar hook called save_related (for anyone coming across this old ticket)

also, using save_formset wasn't ideal as it gets called once for every inline.

Add Comment

Modify Ticket

Change Properties
<Author field>
Action
as closed
as The resolution will be set. Next status will be 'closed'
The resolution will be deleted. Next status will be 'new'
Author


E-mail address and user name can be saved in the Preferences.

 
Note: See TracTickets for help on using tickets.