Compare commits

...

2 Commits

2 changed files with 18 additions and 9 deletions

View File

@ -3,9 +3,10 @@ from decimal import Decimal
from django.db import models from django.db import models
from django.conf import settings from django.conf import settings
from django.db.models import Sum from django.db.models import Sum
from functools import cached_property
from django.utils.text import slugify
from django.db.models.fields import related from django.db.models.fields import related
from django.db.models.functions import ExtractMonth from django.db.models.functions import ExtractMonth
from django.utils.text import slugify
class Category(models.Model): class Category(models.Model):
@ -357,7 +358,15 @@ class Goal(models.Model):
return self.start_date return self.start_date
@cached_property
def progress(self): def progress(self):
"""Progreso del objetivo. Memoizado: se calcula una vez por instancia.
Ojo: es una cached_property, no un método. Se accede como `goal.progress`
(sin paréntesis), tanto en código como en plantillas. El valor se cachea
en la instancia la primera vez; si en la misma petición se modifican
gastos y se necesita recalcular, habría que crear una instancia nueva.
"""
if self.kind == self.KIND_SAVING: if self.kind == self.KIND_SAVING:
# Provisional: saldo de la cuenta asociada. Cuando exista el # Provisional: saldo de la cuenta asociada. Cuando exista el
# módulo de inversiones, esta rama es lo único que hay que tocar. # módulo de inversiones, esta rama es lo único que hay que tocar.
@ -384,7 +393,7 @@ class Goal(models.Model):
def percentage(self): def percentage(self):
if not self.target_amount: if not self.target_amount:
return 0 return 0
return (self.progress() / self.target_amount) * 100 return (self.progress / self.target_amount) * 100
def bar_width(self): def bar_width(self):
return min(float(self.percentage()), 100) return min(float(self.percentage()), 100)
@ -405,7 +414,7 @@ class Goal(models.Model):
"""True si es un presupuesto que ya se ha pasado del límite.""" """True si es un presupuesto que ya se ha pasado del límite."""
return ( return (
self.kind == self.KIND_BUDGET self.kind == self.KIND_BUDGET
and self.progress() > self.target_amount and self.progress > self.target_amount
) )
def __str__(self): def __str__(self):

View File

@ -24,7 +24,7 @@ def test_payment_goal_sums_expenses_since_start_date(user, account, category):
include_subcategories=False, start_date=start, target_amount=Decimal("100"), include_subcategories=False, start_date=start, target_amount=Decimal("100"),
) )
assert goal.progress() == Decimal("20") assert goal.progress == Decimal("20")
def test_payment_goal_include_subcategories_true_includes_child_expenses(user, account, category): def test_payment_goal_include_subcategories_true_includes_child_expenses(user, account, category):
@ -36,7 +36,7 @@ def test_payment_goal_include_subcategories_true_includes_child_expenses(user, a
include_subcategories=True, target_amount=Decimal("100"), include_subcategories=True, target_amount=Decimal("100"),
) )
assert goal.progress() == Decimal("15") assert goal.progress == Decimal("15")
def test_payment_goal_include_subcategories_false_excludes_child_expenses(user, account, category): def test_payment_goal_include_subcategories_false_excludes_child_expenses(user, account, category):
@ -48,7 +48,7 @@ def test_payment_goal_include_subcategories_false_excludes_child_expenses(user,
include_subcategories=False, target_amount=Decimal("100"), include_subcategories=False, target_amount=Decimal("100"),
) )
assert goal.progress() == Decimal("0") assert goal.progress == Decimal("0")
def test_budget_goal_period_month_counts_only_current_month(user, account, category): def test_budget_goal_period_month_counts_only_current_month(user, account, category):
@ -60,7 +60,7 @@ def test_budget_goal_period_month_counts_only_current_month(user, account, categ
period=Goal.PERIOD_MONTH, target_amount=Decimal("100"), period=Goal.PERIOD_MONTH, target_amount=Decimal("100"),
) )
assert goal.progress() == Decimal("30") assert goal.progress == Decimal("30")
def test_budget_goal_is_exceeded_true_over_target(user, account, category): def test_budget_goal_is_exceeded_true_over_target(user, account, category):
@ -92,7 +92,7 @@ def test_is_exceeded_always_false_for_non_budget_kind(user, account, category):
owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category, target_amount=Decimal("10"), owner=user, name="Pago", kind=Goal.KIND_PAYMENT, category=category, target_amount=Decimal("10"),
) )
assert goal.progress() > goal.target_amount assert goal.progress > goal.target_amount
assert goal.is_exceeded() is False assert goal.is_exceeded() is False
@ -103,7 +103,7 @@ def test_saving_goal_progress_equals_account_balance(user):
owner=user, name="Ahorro", kind=Goal.KIND_SAVING, account=acc, target_amount=Decimal("1000"), owner=user, name="Ahorro", kind=Goal.KIND_SAVING, account=acc, target_amount=Decimal("1000"),
) )
assert goal.progress() == acc.current_balance() assert goal.progress == acc.current_balance()
def test_bar_width_caps_at_100_above_target(user): def test_bar_width_caps_at_100_above_target(user):