1
1

completely remove old financeplanner app

This commit is contained in:
2021-01-10 21:29:25 +01:00
parent 4aebc3109f
commit 491dd93f01
21 changed files with 0 additions and 1053 deletions

View File

@@ -1,33 +0,0 @@
from django.contrib import admin
from django.urls import reverse_lazy
from financeplanner.models import Transaction, Balance
from financeplanner.utils import format_price
class AdminSite(admin.AdminSite):
index_title = "FinancePlanner"
site_title = "Admin Panel"
site_header = "Admin Panel"
site_url = reverse_lazy("finance:index")
admin_site = AdminSite()
def amount(obj):
return format_price(obj.amount)
amount.short_description = "amount"
@admin.register(Transaction, site=admin_site)
class TransactionAdmin(admin.ModelAdmin):
list_display = ("subject", amount, "booking_date", "recurring_months", "not_recurring_after")
@admin.register(Balance, site=admin_site)
class BalanceAdmin(admin.ModelAdmin):
list_display = ("date", amount)
ordering = ("-date",)

View File

@@ -1,6 +0,0 @@
from django.apps import AppConfig
class FinancePlannerConfig(AppConfig):
name = "financeplanner"
verbose_name = "FinancePlanner"

View File

@@ -1,245 +0,0 @@
from decimal import Decimal
from math import floor
from dateutil.relativedelta import relativedelta
from django.db.models import Q
from financeplanner.utils import current_date
days_per_year = Decimal("365.25")
days_per_month = days_per_year / Decimal("12")
days_per_week = Decimal("7")
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)
class ActualTransaction:
def __init__(self, date, subject, amount):
self.date = date
self.subject = subject
self.amount = amount
def __str__(self):
return f"ActualTransaction({self.date}, {self.subject}, {self.amount})"
class DailyStat:
def __init__(self, stat_id, date, balance_amount, actual_transactions, resulting_amount):
self.id = stat_id
self.date = date
self.balance_amount = balance_amount
self.actual_transactions = actual_transactions
self.resulting_amount = resulting_amount
self.highlighted = False
self.percentage = 40
self.resulting_amount_irregular = self.resulting_amount
self.percentage_irregular = self.percentage
def __str__(self):
return f"DailyStat({self.date}, {self.balance_amount}, " \
f"{len(self.actual_transactions)} a. transactions, {self.resulting_amount})"
def generate_graph_bar_attributes(self, amount_scale, today):
self.highlighted = self.date == today
if self.resulting_amount is not None:
self.percentage = int((self.resulting_amount / amount_scale) * 100)
self.percentage_irregular = self.percentage
def generate_irregular_graph_bar_attributes(self, amount_scale):
if self.resulting_amount_irregular is not None:
self.percentage_irregular = int((self.resulting_amount_irregular / amount_scale) * 100)
class Statistics:
_min_daily_amount_scale = 500
def __init__(self, user):
self.user = user
self.today = current_date()
self.calc_start = self.today - relativedelta(months=6)
self.calc_end = self.today + relativedelta(months=6)
self.real_calc_start = self.calc_start
self.display_start = self.today - relativedelta(months=1)
self.display_end = self.today + relativedelta(months=2)
self.balances = []
self.transactions = []
self.actual_transactions = []
self.daily_stats = []
self.daily_amount_scale = self._min_daily_amount_scale
self.avg_monthly_income = 0
self.avg_monthly_expenses = 0
self.avg_monthly_result = 0
self.avg_daily_irregular_expenses = 0
self.avg_weekly_irregular_expenses = 0
self.avg_monthly_irregular_expenses = 0
self._fetch_relevant_balances()
self._fetch_relevant_transactions()
self._calculate_actual_transactions()
self._calculate_daily_stats()
self._calculate_daily_amount_scale()
self._generate_graph_bar_attributes()
self._calculate_analysis()
self._add_statistics_considering_analysis()
def _fetch_relevant_balances(self):
self.balances = []
balances_until_start = self.user.balances.filter(date__lte=self.calc_start)
if balances_until_start.exists():
self.balances.append(balances_until_start.latest("date"))
other_balances = self.user.balances.filter(date__gt=self.calc_start, date__lte=self.calc_end).order_by("date")
self.balances += list(other_balances)
if self.balances:
self.real_calc_start = self.balances[0].date
def _fetch_relevant_transactions(self):
one_time_in_range = Q(
booking_date__gte=self.real_calc_start,
booking_date__lte=self.calc_end,
recurring_months__isnull=True,
)
endlessly_recurring = Q(
booking_date__lte=self.calc_end,
recurring_months__isnull=False,
not_recurring_after__isnull=True,
)
recurring_and_ending_in_range = Q(
booking_date__lte=self.calc_end,
recurring_months__isnull=False,
not_recurring_after__gte=self.real_calc_start,
)
transactions = self.user.transactions.filter(
one_time_in_range | endlessly_recurring | recurring_and_ending_in_range
).order_by("booking_date")
self.transactions = list(transactions)
def _calculate_actual_transactions(self):
actual_transactions = []
for trans in self.transactions:
if trans.recurring_months:
iter_date = trans.booking_date
iter_step = 1
limit = min(trans.not_recurring_after, self.calc_end) if trans.not_recurring_after else self.calc_end
while iter_date < limit:
if iter_date >= self.real_calc_start:
actual_transactions.append(ActualTransaction(
date=iter_date,
subject=f"{trans.subject} #{iter_step}",
amount=trans.amount,
))
iter_date += relativedelta(months=trans.recurring_months)
iter_step += 1
else:
actual_transactions.append(ActualTransaction(
date=trans.booking_date,
subject=trans.subject,
amount=trans.amount,
))
self.actual_transactions = sorted(actual_transactions, key=lambda t: t.date)
def _calculate_daily_stats(self):
self.daily_stats = []
iter_id = 0
iter_date = self.real_calc_start
iter_amount = None
while iter_date < self.calc_end:
balance_amount, actual_transactions = None, []
relevant_balances = [bal for bal in self.balances if bal.date == iter_date]
if relevant_balances:
assert len(relevant_balances) == 1, \
f"balances should be unique for user and date, but here are {relevant_balances}"
balance_amount = relevant_balances[0].amount
iter_amount = balance_amount
for transaction in self.actual_transactions:
if transaction.date == iter_date:
actual_transactions.append(transaction)
if iter_amount is not None:
iter_amount += transaction.amount
if iter_amount is not None:
self.daily_stats.append(DailyStat(
stat_id=iter_id,
date=iter_date,
balance_amount=balance_amount,
actual_transactions=actual_transactions,
resulting_amount=iter_amount,
))
iter_id += 1
iter_date += relativedelta(days=1)
def _calculate_daily_amount_scale(self):
amounts = [s.resulting_amount for s in self.daily_stats
if s.resulting_amount is not None
and self.display_start <= s.date <= self.display_end]
if amounts:
max_amount = max(amounts)
scale = _floor_to_first_two_places(max_amount * Decimal("1.25"))
self.daily_amount_scale = max(scale, self._min_daily_amount_scale)
def _generate_graph_bar_attributes(self):
for stat in self.daily_stats:
stat.generate_graph_bar_attributes(self.daily_amount_scale, self.today)
def _calculate_analysis(self):
booked = Q(booking_date__lte=self.today)
incoming = Q(amount__gt=0)
outgoing = Q(amount__lt=0)
recurring = Q(recurring_months__isnull=False) & (
Q(not_recurring_after__isnull=True) | Q(not_recurring_after__gte=self.today))
in_trans = self.user.transactions.filter(booked & incoming & recurring)
in_trans_per_month = [t.amount / t.recurring_months for t in in_trans]
self.avg_monthly_income = sum(in_trans_per_month)
out_trans = self.user.transactions.filter(booked & outgoing & recurring)
out_trans_per_month = [t.amount / t.recurring_months for t in out_trans]
self.avg_monthly_expenses = sum(out_trans_per_month)
self.avg_monthly_result = self.avg_monthly_income + self.avg_monthly_expenses
daily_irregular_expenses = []
if len(self.balances) > 1:
for i in range(1, len(self.balances)):
previous_balance = self.balances[i - 1]
current_balance = self.balances[i]
day_before = current_balance.date - relativedelta(days=1)
relevant_stats = [s for s in self.daily_stats if s.date == day_before]
if relevant_stats and day_before > previous_balance.date:
assert len(relevant_stats) == 1, \
f"daily stats should be unique per date, but here are {relevant_stats}"
amount_diff = current_balance.amount - relevant_stats[0].resulting_amount
time_diff = current_balance.date - previous_balance.date
daily_irregular_expenses.append(amount_diff / time_diff.days)
if daily_irregular_expenses:
self.avg_daily_irregular_expenses = sum(daily_irregular_expenses) / len(daily_irregular_expenses)
self.avg_weekly_irregular_expenses = self.avg_daily_irregular_expenses * days_per_week
self.avg_monthly_irregular_expenses = self.avg_daily_irregular_expenses * days_per_month
self.avg_monthly_result_complete = self.avg_monthly_result + self.avg_monthly_irregular_expenses
def _add_statistics_considering_analysis(self):
previous_original_amount = None
iter_amount = None
for stat in self.daily_stats:
if stat.resulting_amount is not None:
relevant_balances = [bal for bal in self.balances if bal.date == stat.date]
if relevant_balances or previous_original_amount is None:
iter_amount = stat.resulting_amount + self.avg_daily_irregular_expenses
else:
orig_diff = stat.resulting_amount - previous_original_amount
new_diff = orig_diff + self.avg_daily_irregular_expenses
iter_amount += new_diff
stat.resulting_amount_irregular = iter_amount
stat.generate_irregular_graph_bar_attributes(self.daily_amount_scale)
previous_original_amount = stat.resulting_amount
def get_daily_stats_in_range(self):
return [s for s in self.daily_stats if self.display_start <= s.date <= self.display_end]

View File

@@ -1,20 +0,0 @@
from django.contrib.auth.management.commands import createsuperuser
from django.db import transaction
USERNAME = "admin"
class Command(createsuperuser.Command):
help = "Used to create the superuser 'admin' with password '123'"
@transaction.atomic()
def handle(self, *args, **options):
options.update({
self.UserModel.USERNAME_FIELD: USERNAME,
"email": "admin@test.com",
"interactive": False,
})
super().handle(*args, **options)
user = self.UserModel.objects.latest("pk")
user.set_password("123")
user.save()

View File

@@ -1,68 +0,0 @@
from decimal import Decimal
from dateutil.relativedelta import relativedelta
from django.contrib.auth import get_user_model
from django.core.management import BaseCommand
from financeplanner.management.commands.createadmin import USERNAME
from financeplanner.models import Transaction, Balance
from financeplanner.utils import current_date
User = get_user_model()
class Command(BaseCommand):
def handle(self, *args, **options):
user = User.objects.get(**{User.USERNAME_FIELD: USERNAME})
today = current_date()
Transaction.objects.create(
user=user,
subject="simple transaction some days ago",
amount=Decimal("34.95"),
booking_date=today - relativedelta(days=5),
)
Transaction.objects.create(
user=user,
subject="simple transaction a week ahead",
amount=Decimal("66.66"),
booking_date=today + relativedelta(weeks=1),
)
Transaction.objects.create(
user=user,
subject="recurring stuff ending soon",
amount=Decimal("49"),
booking_date=today - relativedelta(days=180),
recurring_months=1,
not_recurring_after=today + relativedelta(weeks=6)
)
Transaction.objects.create(
user=user,
subject="recurring stuff beginning soon",
amount=Decimal("30"),
recurring_months=2,
booking_date=today + relativedelta(weeks=2),
)
Balance.objects.create(
user=user,
date=today - relativedelta(weeks=7),
amount=Decimal("600"),
)
Balance.objects.create(
user=user,
date=today - relativedelta(weeks=3),
amount=Decimal("380"),
)
Balance.objects.create(
user=user,
date=today - relativedelta(days=2),
amount=Decimal("450"),
)

View File

@@ -1,42 +0,0 @@
# Generated by Django 3.0.3 on 2020-02-18 08:56
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
import financeplanner.models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='Transaction',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('subject', models.CharField(max_length=100, verbose_name='subject')),
('amount', financeplanner.models.PriceField(decimal_places=2, max_digits=8, verbose_name='amount')),
('booking_date', models.DateField(verbose_name='booking date')),
('recurring_months', models.PositiveSmallIntegerField(blank=True, null=True, verbose_name='recurring months')),
('not_recurring_after', models.DateField(blank=True, null=True, verbose_name='not recurring after')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='transactions', to=settings.AUTH_USER_MODEL)),
],
),
migrations.CreateModel(
name='Balance',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('date', models.DateField(verbose_name='date')),
('amount', financeplanner.models.PriceField(decimal_places=2, max_digits=8, verbose_name='amount')),
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='balances', to=settings.AUTH_USER_MODEL)),
],
options={
'unique_together': {('user', 'date')},
},
),
]

View File

@@ -1,60 +0,0 @@
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import models
from financeplanner.utils import round_with_dec_places
User = get_user_model()
class PriceField(models.DecimalField):
def to_python(self, value):
value = super(PriceField, self).to_python(value)
return round_with_dec_places(value, self.decimal_places)
def __init__(self, *args, **kwargs):
# allowing numbers until 999,999.99
kwargs['max_digits'] = 8
kwargs['decimal_places'] = 2
super().__init__(*args, **kwargs)
class BaseModel(models.Model):
class Meta:
abstract = True
def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.full_clean()
super().save(force_insert, force_update, using, update_fields)
class Transaction(models.Model):
user = models.ForeignKey(to=User, related_name="transactions", on_delete=models.CASCADE)
subject = models.CharField("subject", max_length=100)
amount = PriceField("amount")
booking_date = models.DateField("booking date")
recurring_months = models.PositiveSmallIntegerField("recurring months", null=True, blank=True)
not_recurring_after = models.DateField("not recurring after", null=True, blank=True)
def __str__(self):
return f"{self.subject}: {self.amount:.2f}"
def clean(self):
if self.recurring_months is not None and self.recurring_months < 1:
raise ValidationError("recurring_months must be at least 1, if present")
if self.not_recurring_after and self.not_recurring_after <= self.booking_date:
raise ValidationError("not_recurring_after must be later than booking_date")
class Balance(models.Model):
class Meta:
unique_together = ("user", "date")
user = models.ForeignKey(to=User, related_name="balances", on_delete=models.CASCADE)
date = models.DateField("date")
amount = PriceField("amount")
def __str__(self):
return f"{self.date}: {self.amount:.2f}"

View File

@@ -1,203 +0,0 @@
* {
color: #FFFCF9;
}
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
border: none;
}
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: #131211;
padding-bottom: 24px;
}
h1 {
margin-top: 18px;
margin-bottom: 6px;
font-size: 1.4em;
font-weight: normal;
color: #FFCC99;
}
h2 {
margin-top: 6px;
margin-bottom: 12px;
font-size: 1.1em;
font-weight: normal;
color: #FFCC99;
}
a {
color: #FFCC99;
text-decoration: none;
transition: color 150ms;
}
a:hover, a:focus {
color: #CC6622;
}
table {
margin: 6px;
border-collapse: collapse;
}
td {
padding: 3px 12px 3px 6px;
border: 1px solid #3F3D3B;
}
thead {
font-variant: small-caps;
}
tr {
background-color: rgba(255, 255, 255, 0);
transition: background-color 100ms;
}
tr:hover {
background-color: rgba(255, 255, 255, 0.1);
}
.mini-link {
font-size: 0.9em;
}
.flex-col-centering {
display: flex;
flex-direction: column;
align-items: center;
}
.text-centering {
text-align: center;
}
.main-container {
flex-shrink: 1;
width: 90%;
max-width: 1200px;
padding: 12px;
margin-top: 12px;
background-color: #282624;
border-radius: 4px;
font-size: 16px;
}
.login-form {
flex: 1;
width: 95%;
max-width: 400px;
}
.login-line {
flex: 1;
width: 95%;
margin: 6px;
padding: 3px;
display: flex;
flex-direction: row;
justify-content: center;
align-items: center;
}
.login-label {
flex-shrink: 1;
margin-right: 6px;
padding: 2px;
text-align: right;
}
.login-textbox {
flex-grow: 1;
padding: 2px;
min-width: 60px;
background-color: #DDDDDD;
color: #131211;
border: none;
border-radius: 4px;
}
.login-warning {
flex: 1;
padding: 2px;
text-align: center;
color: #CC6622;
}
.login-button {
flex: 0 0 120px;
padding: 4px;
background-color: #202020;
border: 1px solid #666666;
border-radius: 4px;
color: #FFCC99;
transition: color 150ms, background-color 150ms;
}
.login-button:hover, .login-button:focus {
background-color: #131211;
color: #CC6622;
}
#big-stat-panel {
width: 100%;
opacity: 50%;
font-size: 1.4em;
height: 1.4em;
text-align: center;
}
#small-stat-panel {
width: 100%;
opacity: 50%;
font-size: 0.8em;
height: 0.8em;
text-align: center;
}
#graph {
flex: 0 0 300px;
height: 300px;
width: 100%;
display: flex;
flex-direction: row;
align-items: end;
padding-bottom: 4px;
overflow-x: auto;
transition: opacity 100ms;
}
#graph.scrolling {
opacity: 75%;
cursor: grabbing;
}
.graph-bar {
flex: 1 1 0;
min-width: 10px;
min-height: 10px;
margin-right: 1px;
border-radius: 2px;
background-color: #FFCC99;
z-index: 10;
transition: height 250ms;
}
.graph-bar.highlighted {
background-color: #CC6622;
}
.graph-bar:hover {
background-color: #FFFCF9;
box-shadow: #FFFCF9 0 0 12px;
z-index: 11;
}

View File

@@ -1,22 +0,0 @@
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>{% block title %}FinancePlanner{% endblock %}</title>
<link rel="stylesheet" type="text/css" href="{% static 'financeplanner/style.css' %}">
</head>
<body>
<h1>&#x1F4CA; FinancePlanner &#x1F4B0;</h1>
<p>
{% block navi %}{% endblock %}
</p>
{% block content %}{% endblock %}
</body>
</html>

View File

@@ -1,223 +0,0 @@
{% extends "financeplanner/base.html" %}
{% load custom_tags %}
{% block navi %}
<a href="{% url 'admin:index' %}" class="mini-link">Admin Panel</a>
-
<a href="{% url 'logout' %}" class="mini-link">Logout</a>
{% endblock %}
{% block content %}
<div class="main-container flex-col-centering">
{% if daily_stats %}
<div id="big-stat-panel"></div>
<div id="small-stat-panel"></div>
<div id="graph">
{% for stat in daily_stats %}
<div class="graph-bar{% if stat.highlighted %} highlighted{% endif %}"
id="graph-bar-{{ stat.id }}">
</div>
{% endfor %}
</div>
{% else %}
<p>No enough data from {{ start|date:"d.m.y" }} til {{ end|date:"d.m.y" }} to show a graph.</p>
{% endif %}
</div>
<div class="main-container">
<h2>Analysis</h2>
<p>current average amounts per month:</p>
<table>
<tbody>
<tr>
<td>income</td>
<td>{{ avg_monthly_income|euro }}</td>
</tr>
<tr>
<td>expenses</td>
<td>{{ avg_monthly_expenses|euro }}</td>
</tr>
<tr>
<td>result</td>
<td>{{ avg_monthly_result|euro }}</td>
</tr>
</tbody>
</table>
<p>average irregular expenses based on the last half-a-year:</p>
<table>
<tbody>
<tr>
<td>monthly</td>
<td>{{ avg_monthly_irregular_expenses|euro }}</td>
</tr>
<tr>
<td>weekly</td>
<td>{{ avg_weekly_irregular_expenses|euro }}</td>
</tr>
<tr>
<td>daily</td>
<td>{{ avg_daily_irregular_expenses|euro }}</td>
</tr>
</tbody>
</table>
<p>
<input type="checkbox" id="irregular-expenses-checkbox">
<label for="irregular-expenses-checkbox">take irregular expenses into account</label>
</p>
<p>monthly result after irregular expenses: {{ avg_monthly_result_complete|euro }}</p>
</div>
<div class="main-container">
<h2>Relevant Balances</h2>
{% if balances %}
<table>
<thead>
<tr>
<td>date</td>
<td>amount</td>
</tr>
</thead>
<tbody>
{% for balance in balances %}
<tr>
<td>{{ balance.date|date:"d.m.y" }}</td>
<td>{{ balance.amount|euro }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>-</p>
{% endif %}
</div>
<div class="main-container">
<h2>Relevant Stored Transactions</h2>
{% if transactions %}
<table>
<thead>
<tr>
<td>subject</td>
<td>amount</td>
<td>booking date</td>
<td>recurring months</td>
<td>not recurring after</td>
</tr>
</thead>
<tbody>
{% for transaction in transactions %}
<tr>
<td>{{ transaction.subject }}</td>
<td>{{ transaction.amount|euro }}</td>
<td>{{ transaction.booking_date|date:"d.m.y" }}</td>
<td>{{ transaction.recurring_months }}</td>
<td>{{ transaction.not_recurring_after|date:"d.m.y" }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>-</p>
{% endif %}
</div>
<div class="main-container">
<h2>Calculated Actual Transactions</h2>
{% if actual_transactions %}
<table>
<thead>
<tr>
<td>date</td>
<td>subject</td>
<td>amount</td>
</tr>
</thead>
<tbody>
{% for trans in actual_transactions %}
<tr>
<td>{{ trans.date|date:"d.m.y" }}</td>
<td>{{ trans.subject }}</td>
<td>{{ trans.amount|euro }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p>-</p>
{% endif %}
</div>
<script type="text/javascript">
const graph = document.getElementById('graph');
const bigStatPanel = document.getElementById('big-stat-panel');
const smallStatPanel = document.getElementById('small-stat-panel');
const irrExpCheckbox = document.getElementById('irregular-expenses-checkbox');
{% for stat in daily_stats %}
const graphBar{{ stat.id }} = document.getElementById('graph-bar-{{ stat.id }}');
{% endfor %}
let scrolling = false;
let initX;
let initScrollLeft;
function startDragScroll(event) {
scrolling = true;
graph.classList.add('scrolling');
initX = event.pageX - graph.offsetLeft;
initScrollLeft = graph.scrollLeft;
}
function stopDragScroll() {
scrolling = false;
graph.classList.remove('scrolling');
}
function doDragScroll(event) {
if (scrolling) {
event.preventDefault();
const newX = event.pageX - graph.offsetLeft;
const diff = (newX - initX);
graph.scrollLeft = initScrollLeft - diff;
}
}
function applyGraphBarHeights() {
if (irrExpCheckbox.checked) {
{% for stat in daily_stats %}
graphBar{{ stat.id }}.style.height = '{{ stat.percentage_irregular }}%';
{% endfor %}
} else {
{% for stat in daily_stats %}
graphBar{{ stat.id }}.style.height = '{{ stat.percentage }}%';
{% endfor %}
}
}
graph.addEventListener('mousedown', (event) => startDragScroll(event));
graph.addEventListener('mouseleave', () => stopDragScroll());
graph.addEventListener('mouseup', () => stopDragScroll());
graph.addEventListener('mousemove', (event) => doDragScroll(event));
irrExpCheckbox.addEventListener('change', () => applyGraphBarHeights());
window.onload = applyGraphBarHeights;
{% for stat in daily_stats %}
graphBar{{ stat.id }}.addEventListener('mouseover', () => function () {
if (irrExpCheckbox.checked) {
bigStatPanel.innerText = '{{ stat|stat_header:True }}';
} else {
bigStatPanel.innerText = '{{ stat|stat_header:False }}';
}
smallStatPanel.innerText = '{{ stat|stat_details }}';
}());
graphBar{{ stat.id }}.addEventListener('mouseleave', () => function () {
bigStatPanel.innerText = '';
smallStatPanel.innerText = '';
}());
{% endfor %}
</script>
{% endblock %}

View File

@@ -1,15 +0,0 @@
{% extends "financeplanner/base.html" %}
{% block title %}FinancePlanner - Logout{% endblock %}
{% block navi %}
<a href="{% url 'admin:index' %}" class="mini-link">Admin Panel</a>
-
<a href="{% url 'login' %}" class="mini-link">Login</a>
{% endblock %}
{% block content %}
<div class="main-container text-centering">
<p>You have been logged out.</p>
</div>
{% endblock %}

View File

@@ -1,30 +0,0 @@
{% extends "financeplanner/base.html" %}
{% block title %}FinancePlanner - Login{% endblock %}
{% block navi %}
<a href="{% url 'admin:index' %}" class="mini-link">Admin Panel</a>
{% endblock %}
{% block content %}
<div class="main-container flex-col-centering">
<form id="login-form" method="post" class="login-form" action="{% url 'login' %}">
{% csrf_token %}
<p class="login-line">
<label for="id_username" class="login-label">Username</label>
<input id="id_username" name="username" type="text" class="login-textbox">
</p>
<p class="login-line">
<label for="id_password" class="login-label">Password</label>
<input id="id_password" name="password" type="password" class="login-textbox">
</p>
{% if form.errors %}
<p class="login-line login-warning">Wrong credentials, please try again.</p>
{% endif %}
<p class="login-line">
<input type="submit" value="Login" class="login-button"/>
</p>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
</div>
{% endblock %}

View File

@@ -1,33 +0,0 @@
from django import template
from django.template.defaultfilters import date
from financeplanner.calc import DailyStat
from financeplanner.utils import format_price
register = template.Library()
@register.filter(name="euro")
def euro(value):
return format_price(value) or "-"
@register.filter(name="stat_header")
def stat_header(value: DailyStat, irregular):
return " | ".join([
date(value.date, "d.m.y"),
euro(value.resulting_amount_irregular if irregular else value.resulting_amount)
])
@register.filter(name="stat_details")
def stat_details(value: DailyStat):
parts = []
if value.balance_amount is not None:
parts.append(f"Balance: {euro(value.balance_amount)}")
for trans in value.actual_transactions:
parts.append(f"{trans.subject}: {euro(trans.amount)}")
result = " | ".join(parts) or "-"
if len(result) > 80:
return result[:47] + "..."
return result

View File

@@ -1,7 +0,0 @@
from django.urls import path
from financeplanner import views
urlpatterns = [
path("", views.index, name="index"),
]

View File

@@ -1,20 +0,0 @@
from decimal import Decimal
from django.utils import timezone
def current_date():
return timezone.now().date()
def round_with_dec_places(number, places):
if number is None:
return None
dec_num = number if isinstance(number, Decimal) else Decimal(number)
return dec_num.quantize(Decimal(10) ** -places)
def format_price(number):
if number is None:
return None
return f"{round_with_dec_places(number, 2)}"

View File

@@ -1,26 +0,0 @@
from django.contrib.auth.decorators import login_required
from django.shortcuts import render
from financeplanner.calc import Statistics
@login_required
def index(request):
user = request.user
statistics = Statistics(user)
context = {
"start": statistics.display_start,
"end": statistics.display_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,
"avg_daily_irregular_expenses": statistics.avg_daily_irregular_expenses,
"avg_weekly_irregular_expenses": statistics.avg_weekly_irregular_expenses,
"avg_monthly_irregular_expenses": statistics.avg_monthly_irregular_expenses,
"avg_monthly_result_complete": statistics.avg_monthly_result_complete,
}
return render(request, "financeplanner/index.html", context)