tonde
tonde

Reputation: 11

How do I make a Django model instance to relate to another model instance

I want to have the amount under Rent to increment on the amount under Accounts. When a user types in the amount in Rent it should deduct or add in the amount in Account and should reset if paid or stay the same when not paid.

class Account(models.Model):
    id = models.AutoField(primary_key=True)
    name = models.CharField(max_length=250)
    number = models.PositiveIntegerField(help_text="Account number", unique=True, blank=True, null=False)
    currency = models.CharField(max_length=3, choices=ALLOWED_CURRENCIES)
    creation_date = models.DateTimeField(auto_now_add=True)
    amount = models.DecimalField(max_digits=MONEY_MAX_DIGITS, decimal_places=MONEY_DECIMAL_PLACES, help_text="Latest"                                                                                                  "balance", default=0, blank=False, null=True)

class Rent(models.Model):
    source = models.ForeignKey(Account, on_delete=models.CASCADE, verbose_name="Account holder.")
    amount = models.DecimalField(max_digits=MONEY_MAX_DIGITS, decimal_places=MONEY_DECIMAL_PLACES)
    date = models.DateTimeField(auto_now_add=True)

Upvotes: 1

Views: 38

Answers (1)

Andrea Corbellini
Andrea Corbellini

Reputation: 17751

You can use one of these three strategies:

  1. Compute the Account's amount dynamically. Instead of using the amount field, you can use a method (or maybe a cached property) like this:

    from django.db.models import Sum
    
    class Account(models.Model):
    
        # ...
    
        def amount(self):
            result = self.rent_set.all().aggregate(
                total_amount=Sum('amount'))
            return result['total_amount']
    

    This approach has the advantage that it is always accurate: no matter how you create and manipulate Account and Rent records, this method will always return the correct amount.

    It has the disadvantage of issuing a query every time you call it. This will have a performance impact.

  2. Update the Account amount field when Rent is saved or deleted:

    from django.db.models import Sum
    
    class Account(models.Model):
    
        # ...
    
        def update_amount(self):
            result = self.rent_set.all().aggregate(
                total_amount=Sum('amount'))
            self.amount = result['total_amount']
    
    class Rent(models.Model):
    
        # ...
    
        def save(self, *args, **kwargs):
            super().save(*args, **kwargs)
            self.account.update_amount()
            self.account.save()
    
        def delete(self, *args, **kwargs):
            account = self.account
            super().delete(*args, **kwargs)
            account.update_amount()
            account.save()
    

    This approach has the advantage that the amount is stored into Account records, and therefore does not need to be recalculated every time you need it, only when a Rent object is saved.

    The disadvantage is that you must call save() and delete() on Rent objects. You cannot use, for example, queryset methods like Rent.objects.all().delete(...) or Rent.objects.all().update(...) because your custom save() and delete() won't be called.

    Another issue is that the amount won't be updated if you edit/delete Rent records at the database level, without going through your custom code.

  3. Update the Account amount upon receiving post_save/post_delete signals (thanks user3375448 for the suggestion):

    from django.db.models.signals import post_save, post_delete
    from django.dispatch import receiver
    
    @receiver(post_save, sender=Rent)
    @receiver(post_delete, sender=Rent)
    def my_handler(sender, instance, **kwargs):
        instance.account.update_amount()
    

    This is essentially like the previous solution and it has essentially the same advantages and disadvantages.

    One improvement from the previous solution is that post_delete is sent when a queryset is deleted as well.

Upvotes: 1

Related Questions