Model classes for an expense tracking app in Django I want to build a simple expense tracking app in Django. Each user can create categories, and each category can have multiple expenses:



Additionally, I wanted to have a value field that shows the total value of the category per month. For this I am using the def save() function in the Expenses class and a def calc_category_value() function called from the signals. I was wondering if this approach is correct:

def calc_category_value(instance):
    today = date.today()
    days = calendar.monthrange(today.year,today.month)[1]
    range_start = "%s-%s-%s" % (today.year,today.month,1)
    range_end = "%s-%s-%s" % (today.year,today.month,days)
    category = Category.objects.get(id=instance.category.id)
    expenses = Expense.objects.filter(date__range=[range_start,range_end],category=category)
    total = 0
    for e in expenses:
        total += e.amount   
    category.value = total
    category.save()

class Profile(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.OneToOneField(User, on_delete=models.CASCADE)
    create_date = models.DateTimeField('date added', auto_now_add=True)
    modify_date = models.DateTimeField('date modified', default=timezone.now)

class Category(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    user = models.ForeignKey(Profile, on_delete=models.CASCADE)
    name = models.CharField(max_length=200, unique=True)
    value = models.DecimalField(max_digits=11,decimal_places=2, default=0)
    create_date = models.DateTimeField('date added', auto_now_add=True)
    modify_date = models.DateTimeField('date modified', default=timezone.now)

    class Meta:
        verbose_name_plural = "Categories"

    def __unicode__(self):
        return self.name +' (' + str(self.value) + ')'

class Expense(models.Model):
    id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
    title = models.CharField(max_length=200, blank=True)
    amount = models.DecimalField(max_digits=11, decimal_places=2,default=0)
    category = models.ForeignKey(Category, on_delete=models.CASCADE)
    date = models.DateField(default=timezone.now)
    create_date = models.DateTimeField('date added', auto_now_add=True)
    modify_date = models.DateTimeField('date modified', default=timezone.now)


    def save(self, *args, **kwargs):
        super(Expense, self).save(*args, **kwargs)
        today = date.today()
        days = calendar.monthrange(today.year,today.month)[1]
        range_start = "%s-%s-%s" % (today.year,today.month,1)
        range_end = "%s-%s-%s" % (today.year,today.month,days)
        expenses = Expense.objects.filter(date__range=[range_start,range_end],category=self.category)
        total = 0
        for e in expenses:
            total += e.amount
        self.category.value = total
        self.category.save()

    class Meta:
        verbose_name_plural = "Expenses"
        order_with_respect_to = 'category'

    def __unicode__(self):
        return self.title +', ' + str(self.amount) +' (' + self.category.name + ')'


Signals.py:

@receiver(post_delete, sender=Expense)
def post_delete(sender, instance, **kwargs):
    calc_category_value(instance)
You would probably want to show total expenses in the category for each month, not just the last. So Category doesn't have a value. It has a value (which is a bad naming by the way) for each month. This can be modelled simply as a query like (pseudo sql) select sum(amount) from expenses where group = 'some group' group by date.month

And by the way you can use this declarative style here.

total = 0
for e in expenses:
    total += e.amount   
category.value = total

category.value = sum(e.amount for e in expenses)