From 652cea79aea0c5411d34770da602af072b4084cc Mon Sep 17 00:00:00 2001 From: Florian Hartmann Date: Sun, 10 Jan 2021 21:25:43 +0100 Subject: [PATCH] rewrite frontend and logic in core app --- core/admin.py | 8 +- core/management/commands/predict_balance.py | 32 +---- core/prediction.py | 28 ++++ core/static/core/style.css | 152 ++++++++++++++++++++ core/templates/core/base.html | 26 ++++ core/templates/core/subjects.html | 45 ++++++ core/templates/registration/logged_out.html | 11 ++ core/templates/registration/login.html | 22 +++ core/urls.py | 10 ++ core/views.py | 14 +- 10 files changed, 318 insertions(+), 30 deletions(-) create mode 100644 core/static/core/style.css create mode 100644 core/templates/core/base.html create mode 100644 core/templates/core/subjects.html create mode 100644 core/templates/registration/logged_out.html create mode 100644 core/templates/registration/login.html create mode 100644 core/urls.py diff --git a/core/admin.py b/core/admin.py index 4257857..8f8a336 100644 --- a/core/admin.py +++ b/core/admin.py @@ -1,7 +1,13 @@ from django.contrib import admin 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) diff --git a/core/management/commands/predict_balance.py b/core/management/commands/predict_balance.py index 9727fcc..4687a13 100644 --- a/core/management/commands/predict_balance.py +++ b/core/management/commands/predict_balance.py @@ -1,14 +1,10 @@ -from datetime import datetime from decimal import Decimal -from dateutil.relativedelta import relativedelta from django.core.management import BaseCommand +from django.utils import timezone from core.models import Subject -from core.prediction import predict_transactions - -past_lookup_delta = relativedelta(weeks=2) -future_lookup_delta = relativedelta(months=2) +from core.prediction import predict_all class Command(BaseCommand): @@ -21,29 +17,11 @@ class Command(BaseCommand): def handle(self, *args, **options): start_balance = Decimal(options["start"]) - today = datetime.now().date() - past_lookup_bound = today - past_lookup_delta - 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) + prediction_list = predict_all(Subject.objects.order_by("name")) + today = timezone.now().date() future_transactions = [] - for subject, prediction in transaction_dict.items(): - transactions, info = prediction[0], prediction[1] + for subject, transactions, info in prediction_list: print(f">>> {subject}") if info: rec_days, rec_months, day_of_month = \ diff --git a/core/prediction.py b/core/prediction.py index db709d0..167e212 100644 --- a/core/prediction.py +++ b/core/prediction.py @@ -1,6 +1,9 @@ +from datetime import timedelta from decimal import Decimal +from typing import Iterable from dateutil.relativedelta import relativedelta +from django.utils import timezone from core.models import Subject, Transaction @@ -85,3 +88,28 @@ def predict_transactions(subject: Subject): "day_of_month": day_of_month, } 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 diff --git a/core/static/core/style.css b/core/static/core/style.css new file mode 100644 index 0000000..0bd06b5 --- /dev/null +++ b/core/static/core/style.css @@ -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; + } +} diff --git a/core/templates/core/base.html b/core/templates/core/base.html new file mode 100644 index 0000000..dc8d229 --- /dev/null +++ b/core/templates/core/base.html @@ -0,0 +1,26 @@ +{% load static %} + + + + + + + finance planner + + + + +
+ 📊 finance planner 💰 +
+ + + +
+ {% block content %}{% endblock %} +
+ + + diff --git a/core/templates/core/subjects.html b/core/templates/core/subjects.html new file mode 100644 index 0000000..f93f48e --- /dev/null +++ b/core/templates/core/subjects.html @@ -0,0 +1,45 @@ +{% extends "core/base.html" %} + +{% block navi %} + admin + | + logout +{% endblock %} + +{% block content %} + +

current subjects

+ + {% if prediction_list %} + + + + + + + + + + {% for subject, transactions, info in prediction_list %} + {% for transaction in transactions %} + + {% if forloop.counter == 1 %} + + {% endif %} + + + + {% endfor %} + {% endfor %} + +
subjectdate
{{ subject.name }} + {% if transaction.pk %}🗒{% else %}🔮{% endif %} + {{ transaction.booking_date|date:"d.m.y" }} + {{ transaction.amount }}
+ {% else %} +

+ no data to show :/ +

+ {% endif %} + +{% endblock %} diff --git a/core/templates/registration/logged_out.html b/core/templates/registration/logged_out.html new file mode 100644 index 0000000..7448f19 --- /dev/null +++ b/core/templates/registration/logged_out.html @@ -0,0 +1,11 @@ +{% extends "core/base.html" %} + +{% block navi %} + admin + | + login +{% endblock %} + +{% block content %} + You have been logged out. +{% endblock %} diff --git a/core/templates/registration/login.html b/core/templates/registration/login.html new file mode 100644 index 0000000..fc6a740 --- /dev/null +++ b/core/templates/registration/login.html @@ -0,0 +1,22 @@ +{% extends "core/base.html" %} + +{% block navi %} + admin +{% endblock %} + +{% block content %} +
+ {% csrf_token %} + + + + + {% if form.errors %} +
+ nope. +
+ {% endif %} + + +
+{% endblock %} diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 0000000..d5aa229 --- /dev/null +++ b/core/urls.py @@ -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"), +] diff --git a/core/views.py b/core/views.py index 91ea44a..fdb27e6 100644 --- a/core/views.py +++ b/core/views.py @@ -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