djangodjango-modelsdjango-admindjango-modeladmindjango-admin-actions

How to get the inline objects in the save method in models.py


I have the class Invoice, which (simplified) has the following attributes:

class Invoice(models.Model)
    number = models.CharField(verbose_name="Number", max_length=16)
    issue_date = models.DateTimeField(verbose_name="Issue date", default=datetime.now)
    total = models.FloatField(verbose_name="Total", blank=True, null=True)

And then, I have the class InvoiceLine, which represents the line/lines that a invoice can have:

class InvoiceLine(models.Model):
    invoice = models.ForeignKey(Invoice, verbose_name="Invoice")
    description = models.CharField(verbose_name="Description", max_length=64)
    line_total = models.FloatField(verbose_name="Line total")

InvoiceLine is an inline of the Invoice, and what I want to achieve is that, when in the admin somebody save the invoice with its lines (one ore more) the total of the invoice is calculated. I've tried to do it by overriding the method save:

class Invoice(models.Model)
    number = models.CharField(verbose_name="Number", max_length=16)
    issue_date = models.DateTimeField(verbose_name="Issue date", default=datetime.now)
    total = models.FloatField(verbose_name="Total", blank=True, null=True)

    def save(self, *args, **kwargs):
         invoice_lines = InvoiceLine.objects.filter(invoice=self.id)
         self.total = 0
         for line in invoice_lines:
             self.total=self.total+line.line_total
         super(Invoice, self).save(*args, **kwargs)

The problem is that when I add elements in the InvoiceLine, the first time I save and the functionsave is called, the new elements in the inline (InvoiceLine) aren't stored yet, so when I do InvoiceLine.objects.filter(invoice=self.id) , they are not taken into account. So, the only way this works is saving twice. I've also tried:

def save(self, *args, **kwargs):
    super(Invoice, self).save(*args, **kwargs)
    invoice_lines = InvoiceLine.objects.filter(invoice=self.pk)
    self.total = 0
    for line in invoice_lines:
        self.total=self.total+line.line_total
    super(Invoice, self).save(*args, **kwargs)

But has the same result. Any idea? Thanks in advance!


Solution

  • Finally I've found it on a post Change object after saving all inlines in Django Admin that has helped me a lot. The key is in admin.py where I already had my class InvoiceHeaderAdmin(admin.ModelAdmin), but I have had to put three functions in order to modify the total attribute after all the inlines has been saved: this way, the query invoice_lines = InvoiceLine.objects.filter(invoice_header=obj.pk) that didn't work well before, now it works perfect. Tue function InvoiceHeaderAdmin has been as follows:

    class InvoiceHeaderAdmin(admin.ModelAdmin):
        inlines = [InvoiceLineInline]
        list_filter = ('format_line','issue_date',)
        list_display = ('number','organization','issue_date','total',)
        fields = ('format_line','organization','issue_date',)
    
        #the following functions are for calculating the total price of the invoice header based on the lines
        def response_add(self, request, new_object):
            obj = self.after_saving_model_and_related_inlines(new_object)
            return super(InvoiceHeaderAdmin, self).response_add(request, obj)
    
        def response_change(self, request, obj):
            obj = self.after_saving_model_and_related_inlines(obj)
            return super(InvoiceHeaderAdmin, self).response_change(request, obj)
    
        def after_saving_model_and_related_inlines(self, obj):
    
            invoice_lines = InvoiceLine.objects.filter(invoice_header=obj.pk)
    
            obj.total = 0
            for line in invoice_lines:
                obj.total=obj.total+line.line_total
            obj.save()
            return obj
    

    The clue is the last function, where all the inlines have been saved and now I can calculate the attribute from object (invoice) and modify it.