Search code examples
pythondjangoormentity-relationship

Post save process using ManyToMany with intermediary model in admin


I have a model relationship as defined by the examples in the Django docs on ManyToMany relationship with an intermediary model.

I know how this normally works, but this is a quick little app that only uses the Django Admin and this is causing a slight bump in the road.

Here is what I have:

class Item(models.Model):
    name = models.CharField(blank=True, max_length=100)
    vat_deductable = models.BooleanField(default=True)
    price = models.DecimalField(max_digits=12, decimal_places=2)

    def __unicode__(self):
        return self.name


class Invoice(models.Model):
    customer = models.ForeignKey(Relation)
    date = models.DateField(default=datetime.datetime.today)
    amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    vat_amount = models.DecimalField(max_digits=12, decimal_places=2, blank=True, null=True)
    is_paid = models.BooleanField(default=False)
    paid_on = models.DateField(blank=True, null=True)
    items = models.ManyToManyField(Item, through='SoldItem')

    def __unicode__(self):
        return unicode(self.customer) + u'_' + unicode(self.date)

class SoldItem(models.Model):
    item = models.ForeignKey(Item)
    invoice = models.ForeignKey(Invoice)
    qty = models.IntegerField(default=1, null=True)

Here is what I would like to do:

After all models have been saved (the new instance of the parent model, any new instances of related models and any new instances of intermediary models) I would like to loop over all instances of Item that is associated with the newly saved Invoice so I can add their price and maybe VAT to the two fields Invoice.amount and Invoice.vat_amount

How would I do that? I have tried with custom save methods on both the Invoice model and its ModelAdmin form, but neither place gives the complete picture when new relations are being formed.

Maybe a signal? But which?

EDIT: I have tried this solution: https://stackoverflow.com/a/2109177/150033

It would make sense that save_m2m would make sure everything is saved, but it seems that the newest relationship is always missing when trying this.


Solution

  • Ok, after much research I found someone else that solved this http://igorsobreira.com/blog/2011/2/12/change-object-after-saving-all-inlines-in-django-admin/

    In the invoice admin:

    class InvoiceAdmin(admin.ModelAdmin):
        inlines = [
            SoldItemAdmin,
        ]
    
        def response_add(self, request, new_object):
            obj = self.after_saving_model_and_related_inlines(new_object)
            return super(InvoiceAdmin, self).response_add(request, obj)
    
        def response_change(self, request, obj):
            obj = self.after_saving_model_and_related_inlines(obj)
            return super(InvoiceAdmin, self).response_change(request, obj)
    
        def after_saving_model_and_related_inlines(self, obj):
            solditem_changed.send(obj)
            return obj
    

    And our signal:

    solditem_changed = Signal()
    @receiver(solditem_changed)
    def update_invoice(sender, **kwargs):
        if hasattr(sender, 'solditem_set'):
            total = 0
            for item in sender.solditem_set.all():
                total += item.item.price * item.qty
            sender.amount = total
            sender.save()
        else:
            sender.amount = 0
            sender.save()