Fixed bugs and added webinterface
This commit is contained in:
parent
9c59b349c2
commit
afbe8a96b1
11 changed files with 171 additions and 4 deletions
1
Pipfile
1
Pipfile
|
@ -8,6 +8,7 @@ verify_ssl = true
|
|||
[packages]
|
||||
sqlalchemy = "*"
|
||||
prompt-toolkit = "*"
|
||||
bottle = "*"
|
||||
|
||||
[requires]
|
||||
python_version = "3.8"
|
||||
|
|
10
Pipfile.lock
generated
10
Pipfile.lock
generated
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "3abcef8be3462b1da61e99b34940773733b8cf76fc3ef77890b2d05098144f49"
|
||||
"sha256": "e8dc56266a4018b0bdb774d18144085e717b5a9792bedfc7cb1325c78a641c19"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {
|
||||
|
@ -16,6 +16,14 @@
|
|||
]
|
||||
},
|
||||
"default": {
|
||||
"bottle": {
|
||||
"hashes": [
|
||||
"sha256:0819b74b145a7def225c0e83b16a4d5711fde751cd92bae467a69efce720f69e",
|
||||
"sha256:43157254e88f32c6be16f8d9eb1f1d1472396a4e174ebd2bf62544854ecf37e7"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==0.12.18"
|
||||
},
|
||||
"prompt-toolkit": {
|
||||
"hashes": [
|
||||
"sha256:a402e9bf468b63314e37460b68ba68243d55b2f8c4d0192f85a019af3945050e",
|
||||
|
|
|
@ -36,7 +36,7 @@ with open("transactions.csv") as fh:
|
|||
print("Found transaction older than then oldest transction in the DB. Aborting")
|
||||
sys.exit(1)
|
||||
|
||||
t = Transaction(date=date, name=name, iban=iban, amount=convert_amount(amount) * 100)
|
||||
t = Transaction(date=date, name=name, iban=iban, amount=convert_amount(amount), description=description)
|
||||
session.add(t)
|
||||
session.commit()
|
||||
count += 1
|
||||
|
|
17
models.py
17
models.py
|
@ -16,12 +16,27 @@ class Transaction(Base):
|
|||
description = Column(String)
|
||||
category_id = Column(Integer, ForeignKey("Category.id"))
|
||||
|
||||
def is_positive(self):
|
||||
return self.amount > 0
|
||||
|
||||
def pretty_amount(self):
|
||||
return "{0:.2f}".format(self.amount/100)
|
||||
|
||||
def get_date(self, format):
|
||||
if format == "iso":
|
||||
return self.date.strftime("%Y-%m-%d")
|
||||
elif format == "de":
|
||||
return self.date.strftime("%d.%m.%Y")
|
||||
else:
|
||||
return "UNKNOWN FORMAT"
|
||||
|
||||
|
||||
|
||||
class Category(Base):
|
||||
__tablename__ = "Category"
|
||||
id = Column(Integer, primary_key=True)
|
||||
name = Column(String)
|
||||
transactions = relationship("Category")
|
||||
transactions = relationship("Transaction", backref="category")
|
||||
parent_id = Column(Integer, ForeignKey("Category.id"))
|
||||
children = relationship("Category", backref=backref("parent", remote_side=[id]))
|
||||
|
||||
|
|
40
serve.py
Normal file
40
serve.py
Normal file
|
@ -0,0 +1,40 @@
|
|||
#! /usr/bin/env python3
|
||||
from bottle import route, run, template
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from models import *
|
||||
|
||||
engine = create_engine("sqlite:///app.db")
|
||||
Base = declarative_base()
|
||||
|
||||
Session = sessionmaker(bind=engine)
|
||||
session = Session()
|
||||
|
||||
|
||||
@route('/')
|
||||
def index():
|
||||
return template('index')
|
||||
|
||||
@route('/categories')
|
||||
def categories():
|
||||
categories = session.query(Category).all()
|
||||
categories = [c for c in categories if c.is_child()]
|
||||
return template('categories', categories=categories)
|
||||
|
||||
@route('/category/<name>')
|
||||
def category(name):
|
||||
c = session.query(Category).filter(Category.name==name).first()
|
||||
if c:
|
||||
transactions = session.query(Transaction).filter(Transaction.category_id==c.id).all()
|
||||
else:
|
||||
transactions = []
|
||||
|
||||
return template('category', category=c,transactions=transactions)
|
||||
|
||||
@route('/transactions')
|
||||
def transactions():
|
||||
transactions = session.query(Transaction).all()
|
||||
return template('transactions', transactions=transactions)
|
||||
|
||||
run(host='localhost', port=8080, reloader=True)
|
6
sort.py
6
sort.py
|
@ -3,6 +3,7 @@ from sqlalchemy import create_engine
|
|||
from sqlalchemy.orm import sessionmaker
|
||||
from sqlalchemy.ext.declarative import declarative_base
|
||||
from models import *
|
||||
from categories import add_category
|
||||
import sys
|
||||
|
||||
from prompt_toolkit.completion import FuzzyWordCompleter
|
||||
|
@ -15,7 +16,7 @@ Session = sessionmaker(bind=engine)
|
|||
session = Session()
|
||||
|
||||
categories = session.query(Category).all()
|
||||
category_lookup = [{c.full_name: c.id} for c in categories]
|
||||
category_lookup = {c.full_name(): c.id for c in categories}
|
||||
category_names = FuzzyWordCompleter([c.full_name() for c in categories])
|
||||
|
||||
unsorted = session.query(Transaction).filter(Transaction.category_id == None).all()
|
||||
|
@ -36,5 +37,8 @@ for t in unsorted:
|
|||
t.category_id = cat_id
|
||||
session.add(t)
|
||||
session.commit()
|
||||
else:
|
||||
print(f"Creating new category '{select}'")
|
||||
add_category(select)
|
||||
|
||||
print("-" * 20)
|
||||
|
|
34
views/base.tpl
Normal file
34
views/base.tpl
Normal file
|
@ -0,0 +1,34 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="d">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<title>Schmeckels</title>
|
||||
<link href="https://unpkg.com/tailwindcss/dist/tailwind.min.css" rel="stylesheet">
|
||||
<link href="https://unpkg.com/@tailwindcss/custom-forms/dist/custom-forms.min.css" rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body class="bg-grey-100 font-sans leading-normal tracking-normal">
|
||||
<div class="bg-gray-300">
|
||||
<ul class="flex pb-3 pt-3">
|
||||
<li class="ml-12 mr-12">
|
||||
<a class="text-blue-500 hover:text-blue-800" href="/">SCHMECKELS</a>
|
||||
</li>
|
||||
<li class="mr-2">
|
||||
<a class="text-blue-500 hover:text-blue-800" href="/transactions">Transactions</a>
|
||||
</li>
|
||||
<li class="mr-2">
|
||||
<a class="text-blue-500 hover:text-blue-800" href="/categories">Categories</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="container mx-auto">
|
||||
|
||||
{{!base}}
|
||||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
8
views/categories.tpl
Normal file
8
views/categories.tpl
Normal file
|
@ -0,0 +1,8 @@
|
|||
% rebase("base.tpl")
|
||||
|
||||
List of categories:
|
||||
<ul>
|
||||
% for c in categories:
|
||||
<li><a href="/category/{{ c.name }}">{{ c.full_name() }}</a></li>
|
||||
% end
|
||||
</ul>
|
26
views/category.tpl
Normal file
26
views/category.tpl
Normal file
|
@ -0,0 +1,26 @@
|
|||
% rebase("base.tpl")
|
||||
|
||||
<h1>Transaktionen aus {{ category.full_name() }}</h1>
|
||||
|
||||
<table class="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Date</th>
|
||||
<th class="px-4 py-2">Sender/Empfänger</th>
|
||||
<th class="px-4 py-2">Verwendungszweck</th>
|
||||
<th class="px-4 py-2">Betrag</th>
|
||||
<th class="px-4 py-2">Kategorie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
% for t in transactions:
|
||||
<tr>
|
||||
<td class="border px-4 py-2">{{ t.get_date("de") }}</td>
|
||||
<td class="border px-4 py-2">{{ t.name }}</td>
|
||||
<td class="border px-4 py-2">{{ t.description }}</td>
|
||||
<td class="border px-4 py-2 {{ 'text-green-500' if t.is_positive() else 'text-red-500' }}">{{ t.pretty_amount() }}</td>
|
||||
<td class="border px-4 py-2">{{ t.category.full_name() if t.category else "-" }}</td>
|
||||
</tr>
|
||||
% end
|
||||
</tbody>
|
||||
</table>
|
3
views/index.tpl
Normal file
3
views/index.tpl
Normal file
|
@ -0,0 +1,3 @@
|
|||
% rebase("base.tpl")
|
||||
|
||||
<h1 class="text-2xl font-bold text-indigo-500">Hello World</h1>
|
28
views/transactions.tpl
Normal file
28
views/transactions.tpl
Normal file
|
@ -0,0 +1,28 @@
|
|||
% rebase("base.tpl")
|
||||
|
||||
<table class="table-auto">
|
||||
<thead>
|
||||
<tr>
|
||||
<th class="px-4 py-2">Date</th>
|
||||
<th class="px-4 py-2">Sender/Empfänger</th>
|
||||
<th class="px-4 py-2">Verwendungszweck</th>
|
||||
<th class="px-4 py-2">Betrag</th>
|
||||
<th class="px-4 py-2">Kategorie</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
% for t in transactions:
|
||||
<tr>
|
||||
<td class="border px-4 py-2">{{ t.get_date("de") }}</td>
|
||||
<td class="border px-4 py-2">{{ t.name }}</td>
|
||||
<td class="border px-4 py-2">{{ t.description }}</td>
|
||||
<td class="border px-4 py-2 {{ 'text-green-500' if t.is_positive() else 'text-red-500' }}">{{ t.pretty_amount() }}</td>
|
||||
% if t.category:
|
||||
<td class="border px-4 py-2"> <a href="/category/{{ t.category.name }}"> {{ t.category.full_name()}}</a></td>
|
||||
% else:
|
||||
<td class="border px-4 py-2"> - </td>
|
||||
% end
|
||||
</tr>
|
||||
% end
|
||||
</tbody>
|
||||
</table>
|
Loading…
Add table
Reference in a new issue