Files
links/invest/template_views.py
T
2026-04-25 11:58:42 +10:00

98 lines
4.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Template views for the invest app."""
import logging
from django.shortcuts import render, get_object_or_404
from django.utils import timezone
from .models import Portfolio, Transaction
from .services import get_portfolio_value, get_weekly_overview, get_all_holdings, get_performance_chart_data
logger = logging.getLogger(__name__)
def dashboard(request):
"""Landing page: weekly snapshot overview + per-portfolio table."""
overview = get_weekly_overview()
# Determine current Australian financial year (JulJun)
now = timezone.now()
fy_start = now.year if now.month >= 7 else now.year - 1
fy_label = f"FY {str(fy_start)[2:]}-{str(fy_start + 1)[2:]}"
last_week_date = overview.get('last_week_date')
all_holdings = get_all_holdings(reference_date=last_week_date)
# Merge snapshot data into each holdings group.
# Change is computed as (live total last snapshot), so the card header and
# the change line are always consistent with the live holdings table.
rows_by_id = {row['portfolio'].id: row for row in overview.get('portfolio_rows', [])}
for group in all_holdings:
row = rows_by_id.get(group['portfolio'].id, {})
group['last_snapshot_value'] = row.get('last_week_value')
group['position_count'] = row.get('position_count', len(group['holdings']))
# Derive portfolio-level change by summing per-stock value changes,
# so the header is always consistent with the individual rows.
stock_changes = [s['value_change'] for s in group['holdings'] if s['value_change'] is not None]
if stock_changes:
total_change = sum(stock_changes)
ref_total = group['total_value'] - total_change
group['change'] = total_change
group['change_pct'] = round((total_change / ref_total) * 100, 2) if ref_total else None
else:
group['change'] = None
group['change_pct'] = None
# Recalculate overview week_gain/week_change_pct from per-portfolio stock-level
# changes so the headline is consistent with the portfolio cards. The snapshot
# comparison inflates the figure whenever the portfolio composition changes
# (e.g. stocks sold/bought during the week), while price-movement only reflects
# actual market performance.
holdings_with_change = [g for g in all_holdings if g['change'] is not None]
if holdings_with_change:
total_change = sum(g['change'] for g in holdings_with_change)
total_ref = sum(g['total_value'] - g['change'] for g in holdings_with_change)
overview['week_gain'] = round(total_change, 2)
overview['week_change_pct'] = round((total_change / total_ref) * 100, 2) if total_ref else None
recent_transactions = (
Transaction.objects
.select_related('portfolio')
.order_by('-date', '-created_at')[:100]
)
return render(request, 'invest/dashboard.html', {
'overview': overview,
'fy_label': fy_label,
'all_holdings': all_holdings,
'chart_data_json': get_performance_chart_data() or 'null',
'recent_transactions': recent_transactions,
})
def portfolio_detail(request, pk):
"""Portfolio detail: live holdings, no cost/P&L."""
portfolio = get_object_or_404(Portfolio, pk=pk)
try:
summary = get_portfolio_value(portfolio)
except Exception as exc:
logger.error("get_portfolio_value failed for %s: %s", pk, exc)
summary = {
'portfolio_id': portfolio.id,
'portfolio_name': portfolio.name,
'holdings': [],
'total_value': 0,
}
return render(request, 'invest/portfolio_detail.html', {
'portfolio': portfolio,
'summary': summary,
})
def portfolio_transactions(request, pk):
"""Transaction history for a portfolio."""
portfolio = get_object_or_404(Portfolio, pk=pk)
transactions = portfolio.transactions.all().order_by('-date', '-created_at')
return render(request, 'invest/transactions.html', {
'portfolio': portfolio,
'transactions': transactions,
})