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 %} @@ -108,7 +105,7 @@

Relevant Stored Transactions

- {% if transaction_list %} + {% if transactions %}
{{ balance.date|date:"d.m.y" }} {{ balance.amount|euro }}
@@ -120,7 +117,7 @@ - {% for transaction in transaction_list %} + {% for transaction in transactions %} @@ -148,11 +145,11 @@ - {% for date, subject, amount in actual_transactions %} + {% for trans in actual_transactions %} - - - + + + {% 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)
{{ transaction.subject }} {{ transaction.amount|euro }}
{{ date|date:"d.m.y" }}{{ subject }}{{ amount|euro }}{{ trans.date|date:"d.m.y" }}{{ trans.subject }}{{ trans.amount|euro }}