1
1

rewrite frontend and logic in core app

This commit is contained in:
2021-01-10 21:25:43 +01:00
parent 37f698d82b
commit 652cea79ae
10 changed files with 318 additions and 30 deletions

View File

@@ -1,7 +1,13 @@
from django.contrib import admin from django.contrib import admin
from core.models import Subject, Transaction from core.models import Subject, Transaction
from financeplanner.admin import admin_site
class CustomAdminSite(admin.AdminSite):
pass
admin_site = CustomAdminSite()
@admin.register(Subject, site=admin_site) @admin.register(Subject, site=admin_site)

View File

@@ -1,14 +1,10 @@
from datetime import datetime
from decimal import Decimal from decimal import Decimal
from dateutil.relativedelta import relativedelta
from django.core.management import BaseCommand from django.core.management import BaseCommand
from django.utils import timezone
from core.models import Subject from core.models import Subject
from core.prediction import predict_transactions from core.prediction import predict_all
past_lookup_delta = relativedelta(weeks=2)
future_lookup_delta = relativedelta(months=2)
class Command(BaseCommand): class Command(BaseCommand):
@@ -21,29 +17,11 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
start_balance = Decimal(options["start"]) start_balance = Decimal(options["start"])
today = datetime.now().date() prediction_list = predict_all(Subject.objects.order_by("name"))
past_lookup_bound = today - past_lookup_delta today = timezone.now().date()
future_lookup_bound = today + future_lookup_delta
transaction_dict = {}
for subject in Subject.objects.all():
transactions = []
for tr in subject.transactions.order_by("booking_date"):
if past_lookup_bound <= tr.booking_date <= future_lookup_bound:
transactions.append(tr)
predicted_transaction, prediction_info = predict_transactions(subject)
if predicted_transaction:
first_predicted_date = predicted_transaction[0].booking_date
if first_predicted_date >= past_lookup_bound:
# if two weeks after the first predicted transaction have passed, the subject is considered done
for tr in predicted_transaction:
if past_lookup_bound <= tr.booking_date <= future_lookup_bound:
transactions.append(tr)
transaction_dict[subject] = (transactions, prediction_info)
future_transactions = [] future_transactions = []
for subject, prediction in transaction_dict.items(): for subject, transactions, info in prediction_list:
transactions, info = prediction[0], prediction[1]
print(f">>> {subject}") print(f">>> {subject}")
if info: if info:
rec_days, rec_months, day_of_month = \ rec_days, rec_months, day_of_month = \

View File

@@ -1,6 +1,9 @@
from datetime import timedelta
from decimal import Decimal from decimal import Decimal
from typing import Iterable
from dateutil.relativedelta import relativedelta from dateutil.relativedelta import relativedelta
from django.utils import timezone
from core.models import Subject, Transaction from core.models import Subject, Transaction
@@ -85,3 +88,28 @@ def predict_transactions(subject: Subject):
"day_of_month": day_of_month, "day_of_month": day_of_month,
} }
return transactions, prediction_info return transactions, prediction_info
def predict_all(subjects: Iterable[Subject], past_days=30, future_days=60):
today = timezone.now().date()
past_lookup_bound = today - timedelta(days=past_days)
future_lookup_bound = today + timedelta(days=future_days)
lookup_bounds = (past_lookup_bound, future_lookup_bound)
prediction_list = []
for subject in subjects:
transactions = list(
subject.transactions.filter(booking_date__range=lookup_bounds).order_by("booking_date"))
predicted_transactions, prediction_info = predict_transactions(subject)
if predicted_transactions:
first_predicted_date = predicted_transactions[0].booking_date
if first_predicted_date >= past_lookup_bound:
# if two weeks after the first predicted transaction have passed, the subject is considered done
for transaction in predicted_transactions:
if past_lookup_bound <= transaction.booking_date <= future_lookup_bound:
transactions.append(transaction)
prediction_list.append((subject, transactions, prediction_info))
return prediction_list

152
core/static/core/style.css Normal file
View File

@@ -0,0 +1,152 @@
html, body {
width: 100%;
height: 100%;
padding: 0;
margin: 0;
border: none;
font-family: monospace;
font-size: 14px;
}
@media only screen and (min-width: 600px) and (max-width: 899px) {
html, body {
font-size: 16px;
}
}
@media only screen and (min-width: 900px) {
html, body {
font-size: 18px;
}
}
body {
display: flex;
flex-direction: column;
align-items: center;
background-color: #111111;
color: #EEEEEE;
}
h1, p, a {
margin: 0;
border: none;
padding: 0;
text-decoration: none;
}
h1 {
font-size: 1.3rem;
font-weight: bold;
}
#title, #navi, #content {
flex: 0 1 0;
width: 95%;
max-width: 1200px;
margin: 12px;
}
#title {
font-size: 1.6rem;
text-align: center;
color: #6699EE;
}
#navi {
border-top: 1px solid #223366;
border-bottom: 1px solid #223366;
padding: 8px 0;
text-align: center;
}
a {
color: #6699EE;
transition: color 100ms;
}
a:hover, a:focus {
color: #EEEEEE;
}
form label {
display: none;
}
#login-form {
display: flex;
flex-direction: column;
align-items: center;
}
#id_username, #id_password, #login-warning, #login-button {
flex: 0 1 0;
margin: 4px;
}
table, thead, tbody, tfoot, tr, td {
margin: 0;
border: none;
padding: 0;
}
table {
border-collapse: collapse;
margin: 16px 0;
width: 100%;
max-width: 400px;
}
thead {
font-weight: bold;
}
tr {
border-bottom: 1px solid #444;
}
td {
padding: 3px 4px;
vertical-align: center;
}
.subject-cell {
max-width: 100px;
text-align: left;
}
.date-cell {
text-align: center;
}
.amount-cell {
text-align: right;
}
@media only screen and (min-width: 600px) and (max-width: 899px) {
table {
max-width: 500px;
}
td {
padding: 3px 8px;
}
.subject-cell {
max-width: 180px;
}
}
@media only screen and (min-width: 900px) {
table {
max-width: 600px;
}
td {
padding: 3px 12px;
}
.subject-cell {
max-width: 240px;
}
}

View File

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

View File

@@ -0,0 +1,45 @@
{% extends "core/base.html" %}
{% block navi %}
<a href="{% url 'admin:index' %}">admin</a>
|
<a href="{% url 'logout' %}">logout</a>
{% endblock %}
{% block content %}
<h1>current subjects</h1>
{% if prediction_list %}
<table>
<thead>
<tr>
<td class="subject-cell">subject</td>
<td class="date-cell">date</td>
<td class="amount-cell"></td>
</tr>
</thead>
<tbody>
{% for subject, transactions, info in prediction_list %}
{% for transaction in transactions %}
<tr>
{% if forloop.counter == 1 %}
<td rowspan="{{ transactions|length }}" class="subject-cell">{{ subject.name }}</td>
{% endif %}
<td class="date-cell">
{% if transaction.pk %}&#x1F5D2;{% else %}&#x1F52E;{% endif %}
{{ transaction.booking_date|date:"d.m.y" }}
</td>
<td class="amount-cell">{{ transaction.amount }}</td>
</tr>
{% endfor %}
{% endfor %}
</tbody>
</table>
{% else %}
<p>
no data to show :/
</p>
{% endif %}
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "core/base.html" %}
{% block navi %}
<a href="{% url 'admin:index' %}">admin</a>
|
<a href="{% url 'login' %}">login</a>
{% endblock %}
{% block content %}
You have been logged out.
{% endblock %}

View File

@@ -0,0 +1,22 @@
{% extends "core/base.html" %}
{% block navi %}
<a href="{% url 'admin:index' %}">admin</a>
{% endblock %}
{% block content %}
<form id="login-form" method="post" action="{% url 'login' %}">
{% csrf_token %}
<label for="id_username">username</label>
<input id="id_username" name="username" type="text" placeholder="username">
<label for="id_password">password</label>
<input id="id_password" name="password" type="password" placeholder="password">
{% if form.errors %}
<div id="login-warning">
nope.
</div>
{% endif %}
<input id="login-button" type="submit" value="login"/>
<input type="hidden" name="next" value="{{ next }}"/>
</form>
{% endblock %}

10
core/urls.py Normal file
View File

@@ -0,0 +1,10 @@
from django.contrib.auth.decorators import login_required
from django.urls import path
from django.views.generic import RedirectView
from core.views import SubjectsView
urlpatterns = [
path("", RedirectView.as_view(pattern_name="core:subjects", permanent=False), name="index"),
path("subjects/", login_required(SubjectsView.as_view()), name="subjects"),
]

View File

@@ -1,3 +1,13 @@
from django.shortcuts import render from django.views.generic import TemplateView
# Create your views here. from core.models import Subject
from core.prediction import predict_all
class SubjectsView(TemplateView):
template_name = "core/subjects.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["prediction_list"] = predict_all(Subject.objects.order_by("name"))
return context