Extrae helpers del dashboard y elimina el N+1 de saldos
La vista pasaba de 226 lineas y mezclaba tres cosas distintas: traducir los parametros de la URL a un periodo, calcular los KPIs y montar las series de las graficas por cuenta. Saca las dos que son autonomas a _resolve_period y _build_account_charts y deja dashboard en 170. De paso quita el N+1: la vista llamaba a current_balance() una vez por cuenta para el KPI y otra vez por cuenta dentro del bucle de graficas, y cada llamada son dos queries. Ahora los saldos salen de _account_balances, que los calcula con dos agregados, y se reparten por un dict indexado por id. Medido con CaptureQueriesContext: con 1/3/6 cuentas se pasa de 25/53/95 queries a 21/37/61. accounts deja de ser un QuerySet y pasa a lista, porque ahora se recorre mas de una vez, y la cuenta seleccionada se busca en memoria en vez de con otra query. Lo que sigue costando una query por cuenta es monthly_balance(), que no se toca aqui. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
b4c9f8507b
commit
f074005a7f
@ -112,6 +112,67 @@ def _account_balances(accounts):
|
|||||||
return account_balances, negative_accounts, total_balance
|
return account_balances, negative_accounts, total_balance
|
||||||
|
|
||||||
|
|
||||||
|
def _resolve_period(request, today):
|
||||||
|
"""Traduce los parámetros de la URL en el periodo mirado por el dashboard.
|
||||||
|
|
||||||
|
Devuelve (period, selected_year, selected_month), donde selected_month es
|
||||||
|
None cuando se está mirando el año completo."""
|
||||||
|
period = request.GET.get("period", "")
|
||||||
|
|
||||||
|
if period == "this_month":
|
||||||
|
return period, today.year, today.month
|
||||||
|
|
||||||
|
if period == "last_month":
|
||||||
|
selected_year, selected_month = sub_months(today.year, today.month, 1)
|
||||||
|
return period, selected_year, selected_month
|
||||||
|
|
||||||
|
if period == "this_year":
|
||||||
|
return period, today.year, None
|
||||||
|
|
||||||
|
selected_year = _get_int(request.GET.get("year")) or today.year
|
||||||
|
selected_month = _get_int(request.GET.get("month")) or None
|
||||||
|
return period, selected_year, selected_month
|
||||||
|
|
||||||
|
|
||||||
|
def _build_account_charts(accounts, balance_by_account, selected_year, today):
|
||||||
|
"""Serie mensual de saldo de cada cuenta para las gráficas del dashboard.
|
||||||
|
|
||||||
|
En el año en curso la serie se corta en el mes actual y ese último punto
|
||||||
|
se sustituye por el saldo real de la cuenta, que llega ya calculado en
|
||||||
|
balance_by_account para no repetir current_balance() por cuenta."""
|
||||||
|
accounts_charts = []
|
||||||
|
|
||||||
|
for acc in accounts:
|
||||||
|
try:
|
||||||
|
monthly_data = acc.monthly_balance(selected_year)
|
||||||
|
m_balance = [float(row["balance"]) for row in monthly_data]
|
||||||
|
except Exception:
|
||||||
|
logger.exception(
|
||||||
|
"Error calculando monthly_balance para la cuenta %s (año %s)",
|
||||||
|
acc.id, selected_year
|
||||||
|
)
|
||||||
|
m_balance = [0] * 12
|
||||||
|
|
||||||
|
current_balance = balance_by_account[acc.id]
|
||||||
|
|
||||||
|
if selected_year == today.year:
|
||||||
|
current_month_index = today.month - 1
|
||||||
|
|
||||||
|
if current_month_index < len(m_balance):
|
||||||
|
m_balance[current_month_index] = float(current_balance)
|
||||||
|
|
||||||
|
m_balance = m_balance[:today.month]
|
||||||
|
|
||||||
|
accounts_charts.append({
|
||||||
|
"id": acc.id,
|
||||||
|
"name": acc.name,
|
||||||
|
"data": m_balance,
|
||||||
|
"current_balance": current_balance,
|
||||||
|
})
|
||||||
|
|
||||||
|
return accounts_charts
|
||||||
|
|
||||||
|
|
||||||
@login_required
|
@login_required
|
||||||
def home(request):
|
def home(request):
|
||||||
today = date.today()
|
today = date.today()
|
||||||
@ -407,33 +468,26 @@ def expense_delete(request, pk):
|
|||||||
def dashboard(request):
|
def dashboard(request):
|
||||||
today = date.today()
|
today = date.today()
|
||||||
|
|
||||||
period = request.GET.get("period", "")
|
|
||||||
account_id = _get_int(request.GET.get("account"))
|
account_id = _get_int(request.GET.get("account"))
|
||||||
compare_enabled = request.GET.get("compare") == "1"
|
compare_enabled = request.GET.get("compare") == "1"
|
||||||
|
|
||||||
# Time presets
|
# Time presets
|
||||||
if period == "this_month":
|
period, selected_year, selected_month = _resolve_period(request, today)
|
||||||
selected_year, selected_month = today.year, today.month
|
|
||||||
elif period == "last_month":
|
|
||||||
selected_year, selected_month = sub_months(today.year, today.month, 1)
|
|
||||||
elif period == "this_year":
|
|
||||||
selected_year, selected_month = today.year, None
|
|
||||||
else:
|
|
||||||
selected_year = _get_int(request.GET.get("year")) or _get_int(today.year)
|
|
||||||
selected_month = _get_int(request.GET.get("month"))
|
|
||||||
selected_month = _get_int(selected_month) if selected_month else None
|
|
||||||
|
|
||||||
# Accounts
|
# Accounts
|
||||||
accounts = Account.objects.filter(owner=request.user, active=True)
|
accounts = list(Account.objects.filter(owner=request.user, active=True))
|
||||||
selected_account_obj = None
|
selected_account_obj = None
|
||||||
if account_id:
|
if account_id:
|
||||||
selected_account_obj = accounts.filter(id=account_id).first()
|
selected_account_obj = next((a for a in accounts if a.id == account_id), None)
|
||||||
|
|
||||||
# Calculate the KPI
|
# Calculate the KPI
|
||||||
|
account_balances, _, total_balance = _account_balances(accounts)
|
||||||
|
balance_by_account = {row["account"].id: row["balance"] for row in account_balances}
|
||||||
|
|
||||||
if selected_account_obj:
|
if selected_account_obj:
|
||||||
kpi_balance = selected_account_obj.current_balance()
|
kpi_balance = balance_by_account[selected_account_obj.id]
|
||||||
else:
|
else:
|
||||||
kpi_balance = sum(account.current_balance() for account in accounts)
|
kpi_balance = total_balance
|
||||||
|
|
||||||
# Filter by base expenses
|
# Filter by base expenses
|
||||||
expenses = Expense.objects.filter(owner=request.user)
|
expenses = Expense.objects.filter(owner=request.user)
|
||||||
@ -561,32 +615,9 @@ def dashboard(request):
|
|||||||
})
|
})
|
||||||
|
|
||||||
# Anual evolution by accounts
|
# Anual evolution by accounts
|
||||||
accounts_charts = []
|
accounts_charts = _build_account_charts(
|
||||||
for acc in accounts:
|
accounts, balance_by_account, selected_year, today
|
||||||
try:
|
)
|
||||||
monthly_data = acc.monthly_balance(selected_year)
|
|
||||||
m_balance = [float(row["balance"]) for row in monthly_data]
|
|
||||||
except Exception:
|
|
||||||
logger.exception(
|
|
||||||
"Error calculando monthly_balance para la cuenta %s (año %s)",
|
|
||||||
acc.id, selected_year
|
|
||||||
)
|
|
||||||
m_balance = [0] * 12
|
|
||||||
|
|
||||||
if selected_year == today.year:
|
|
||||||
current_month_index = today.month - 1
|
|
||||||
|
|
||||||
if current_month_index < len(m_balance):
|
|
||||||
m_balance[current_month_index] = float(acc.current_balance())
|
|
||||||
|
|
||||||
m_balance = m_balance[:today.month]
|
|
||||||
|
|
||||||
accounts_charts.append({
|
|
||||||
"id": acc.id,
|
|
||||||
"name": acc.name,
|
|
||||||
"data": m_balance,
|
|
||||||
"current_balance": acc.current_balance(),
|
|
||||||
})
|
|
||||||
|
|
||||||
# Goals
|
# Goals
|
||||||
goals = Goal.objects.filter(owner=request.user)
|
goals = Goal.objects.filter(owner=request.user)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user