From eee65c63fc7466cd4db8c9425e233b5e3cba0b3d Mon Sep 17 00:00:00 2001
From: Florian Hartmann
Date: Wed, 26 Feb 2020 23:29:25 +0100
Subject: [PATCH] adjust index view, template and tags to work with the new
Statistics class
---
.../templates/financeplanner/index.html | 41 ++--
financeplanner/templatetags/custom_tags.py | 3 -
financeplanner/views.py | 228 +-----------------
3 files changed, 30 insertions(+), 242 deletions(-)
diff --git a/financeplanner/templates/financeplanner/index.html b/financeplanner/templates/financeplanner/index.html
index fe2744d..474d68e 100644
--- a/financeplanner/templates/financeplanner/index.html
+++ b/financeplanner/templates/financeplanner/index.html
@@ -19,29 +19,26 @@
- {% if graph_data %}
+ {% if daily_stats %}
-
- {% for date, stat in graph_data.items %}
- {% if stat.div_percentage is None %}
+ {% for stat in daily_stats %}
+ {% if stat.percentage is None %}
{% else %}
-
{% endif %}
{% endfor %}
{% else %}
-
- No enough data from {{ range_start|date:"d.m.y" }} til
- {{ range_end|date:"d.m.y" }} to show a graph.
-
+
No enough data from {{ start|date:"d.m.y" }} til {{ end|date:"d.m.y" }} to show a graph.
{% endif %}
@@ -51,15 +48,15 @@
| current average monthly income |
- {{ analysis.avg_monthly_in|euro }} |
+ {{ avg_monthly_income|euro }} |
| current average monthly expenses |
- {{ analysis.avg_monthly_out|euro }} |
+ {{ avg_monthly_expenses|euro }} |
| current average monthly result |
- {{ analysis.avg_monthly_res|euro }} |
+ {{ avg_monthly_result|euro }} |
@@ -84,7 +81,7 @@
Relevant Balances
- {% if balance_list %}
+ {% if balances %}
@@ -93,7 +90,7 @@
- {% for balance in balance_list %}
+ {% for balance in balances %}
| {{ balance.date|date:"d.m.y" }} |
{{ balance.amount|euro }} |
@@ -108,7 +105,7 @@
Relevant Stored Transactions
- {% if transaction_list %}
+ {% if transactions %}
@@ -120,7 +117,7 @@
- {% for transaction in transaction_list %}
+ {% for transaction in transactions %}
| {{ transaction.subject }} |
{{ transaction.amount|euro }} |
@@ -148,11 +145,11 @@
- {% for date, subject, amount in actual_transactions %}
+ {% for trans in actual_transactions %}
- | {{ date|date:"d.m.y" }} |
- {{ subject }} |
- {{ amount|euro }} |
+ {{ trans.date|date:"d.m.y" }} |
+ {{ trans.subject }} |
+ {{ trans.amount|euro }} |
{% endfor %}
diff --git a/financeplanner/templatetags/custom_tags.py b/financeplanner/templatetags/custom_tags.py
index 9200e44..c30edd9 100644
--- a/financeplanner/templatetags/custom_tags.py
+++ b/financeplanner/templatetags/custom_tags.py
@@ -1,5 +1,3 @@
-from decimal import Decimal
-
from django import template
from financeplanner.utils import format_price
@@ -9,5 +7,4 @@ register = template.Library()
@register.filter(name="euro")
def euro(value):
- assert isinstance(value, Decimal)
return format_price(value) or "-"
diff --git a/financeplanner/views.py b/financeplanner/views.py
index 82a709a..17a31c7 100644
--- a/financeplanner/views.py
+++ b/financeplanner/views.py
@@ -1,228 +1,22 @@
-from decimal import Decimal
-from math import floor
-
-from dateutil.relativedelta import relativedelta
from django.contrib.auth.decorators import login_required
-from django.db.models import Q
from django.shortcuts import render
-from financeplanner.utils import current_date
-
-
-def _get_relevant_balances(user, range_start, range_end):
- balance_list = []
- balances_until_start = user.balances.filter(date__lte=range_start)
- if balances_until_start.exists():
- balance_list = [balances_until_start.latest("date")]
- other_balances = user.balances.filter(date__gt=range_start, date__lte=range_end).order_by("date")
- return balance_list + list(other_balances)
-
-
-def _get_relevant_transactions(user, calculation_start, range_end):
- one_time_in_range = Q(
- booking_date__gte=calculation_start,
- booking_date__lte=range_end,
- recurring_months__isnull=True,
- )
- endlessly_recurring = Q(
- booking_date__lte=range_end,
- recurring_months__isnull=False,
- not_recurring_after__isnull=True,
- )
- recurring_and_ending_in_range = Q(
- booking_date__lte=range_end,
- recurring_months__isnull=False,
- not_recurring_after__gte=calculation_start,
- )
- transactions = user.transactions.filter(
- one_time_in_range | endlessly_recurring | recurring_and_ending_in_range
- ).order_by("booking_date")
- return list(transactions)
-
-
-def _calculate_actual_transactions(calculation_start, range_end, transaction_list):
- result = []
- for trans in transaction_list:
- current_calc_trans = {}
- if trans.recurring_months:
- current_date = trans.booking_date
- step = 1
- limit = min(trans.not_recurring_after, range_end) if trans.not_recurring_after else range_end
- while current_date < limit:
- if current_date >= calculation_start:
- result.append((
- current_date,
- f"{trans.subject} #{step}",
- trans.amount,
- ))
- current_date += relativedelta(months=trans.recurring_months)
- step += 1
- else:
- current_calc_trans[trans.booking_date] = trans.amount
- result.append((
- trans.booking_date,
- trans.subject,
- trans.amount,
- ))
- return result
-
-
-def _calculate_daily_stats(calculation_start, range_end, balance_list, actual_transactions):
- stats = {}
-
- current_date = calculation_start
- current_amount = None
- while current_date < range_end:
- current_stats = {
- "balance": None,
- "transactions": [],
- "amount": None,
- }
-
- relevant_balances = [bal for bal in balance_list if bal.date == current_date]
- if relevant_balances:
- assert len(relevant_balances) == 1, f"balances should be unique for user and date, " \
- f"but here are {relevant_balances}"
- amount = relevant_balances[0].amount
- current_stats["balance"] = relevant_balances[0].amount
- current_amount = amount
-
- relevant_transactions = [tra for tra in actual_transactions if tra[0] == current_date]
- for tra in relevant_transactions:
- subject, amount = tra[1], tra[2]
- current_stats["transactions"].append({
- "subject": subject,
- "amount": amount,
- })
- if current_amount is not None:
- current_amount -= amount
-
- current_stats["amount"] = current_amount
- stats[current_date] = current_stats
- current_date += relativedelta(days=1)
-
- return stats
-
-
-def _trim_stats(stats, range_start):
- return {date: stat for date, stat in stats.items() if date >= range_start}
-
-
-def _floor_to_first_two_places(dec):
- if dec < 100:
- return 100
- exp = floor(dec.log10()) - 1
- factor = 10 ** exp
- return factor * int(dec / factor)
-
-
-def _calculate_scale(stats):
- amounts = [stat["amount"] for stat in stats.values() if stat["amount"] is not None]
- if not amounts:
- return 100
- max_amount = max(amounts)
- return _floor_to_first_two_places(max_amount * Decimal("1.3"))
-
-
-def _build_graph_data(range_start, range_end, calculation_start, balance_list, actual_transactions):
- """
- result has the format:
- {
- : {
- "balance": ,
- "transactions": [
- {
- "subject": ,
- "amount": ,
- },
- ...
- ],
- "amount": ,
- "div_percentage": ,
- "div_opacity_class": ,
- "div_color_class": ,
- },
- ...
- }
- """
- today = current_date()
- stats = _calculate_daily_stats(calculation_start, range_end, balance_list, actual_transactions)
- stats = _trim_stats(stats, range_start)
- amount_limit = _calculate_scale(stats)
- for date in stats.keys():
- amount = stats[date]["amount"]
- if amount is None:
- stats[date]["div_percentage"] = None
- else:
- percentage = int((amount / amount_limit) * 100)
- stats[date]["div_percentage"] = percentage
- if percentage >= 60:
- stats[date]["div_color_class"] = "green"
- elif percentage >= 45:
- stats[date]["div_color_class"] = "olive"
- elif percentage >= 30:
- stats[date]["div_color_class"] = "yellow"
- elif percentage >= 15:
- stats[date]["div_color_class"] = "orange"
- else:
- stats[date]["div_color_class"] = "red"
-
- if date == today:
- stats[date]["div_opacity_class"] = "highlighted"
- elif stats[date]["balance"] or stats[date]["transactions"]:
- stats[date]["div_opacity_class"] = "strong"
- else:
- stats[date]["div_opacity_class"] = ""
-
- return stats
-
-
-def _calculate_analysis(user):
- result = {}
-
- today = current_date()
- booked = Q(booking_date__lte=today)
- incoming = Q(amount__lt=0)
- outgoing = Q(amount__gt=0)
- recurring = Q(recurring_months__isnull=False) & (
- Q(not_recurring_after__isnull=True) | Q(not_recurring_after__gte=today))
-
- in_trans = user.transactions.filter(booked & incoming & recurring)
- in_trans_per_month = [t.amount / t.recurring_months for t in in_trans]
- result["avg_monthly_in"] = -sum(in_trans_per_month)
-
- out_trans = user.transactions.filter(booked & outgoing & recurring)
- out_trans_per_month = [t.amount / t.recurring_months for t in out_trans]
- result["avg_monthly_out"] = sum(out_trans_per_month)
-
- result["avg_monthly_res"] = result["avg_monthly_in"] - result["avg_monthly_out"]
-
- return result
+from financeplanner.calc import Statistics
@login_required
def index(request):
user = request.user
- today = current_date()
- range_start = today - relativedelta(months=6)
- range_end = today + relativedelta(months=6)
- balance_list = _get_relevant_balances(user, range_start, range_end)
- calculation_start = balance_list[0].date if balance_list else range_start
- transaction_list = _get_relevant_transactions(user, calculation_start, range_end)
- actual_transactions = _calculate_actual_transactions(calculation_start, range_end, transaction_list)
- if balance_list:
- graph_data = _build_graph_data(range_start, range_end, calculation_start, balance_list, actual_transactions)
- else:
- graph_data = None
- analysis = _calculate_analysis(user)
+ statistics = Statistics(user)
context = {
- "today": today,
- "range_start": range_start,
- "range_end": range_end,
- "balance_list": balance_list,
- "transaction_list": transaction_list,
- "actual_transactions": sorted(actual_transactions, key=lambda t: t[0]),
- "graph_data": graph_data,
- "analysis": analysis,
+ "start": statistics.start,
+ "end": statistics.end,
+ "balances": statistics.balances,
+ "transactions": statistics.transactions,
+ "actual_transactions": statistics.actual_transactions,
+ "daily_stats": statistics.get_daily_stats_in_range(),
+ "avg_monthly_income": statistics.avg_monthly_income,
+ "avg_monthly_expenses": statistics.avg_monthly_expenses,
+ "avg_monthly_result": statistics.avg_monthly_result,
}
return render(request, "financeplanner/index.html", context)