Cybrkyd's Git Repositories

python-budget - commit: 09cb2d4

commit 09cb2d45953ff09b8df9a9c2314705a2a533e0962b999390113c983e57f9dea8
author Cybrkyd <git@cybrkyd.com> 2026-06-02 10:45:52 +0100
committer Cybrkyd <git@cybrkyd.com> 2026-06-02 10:45:52 +0100

Commit Message

Simplify and make dashboard-only

- Database now is managed directly in SQLite3 - removed init_db()
- Database tables are 'entries' and VIEW: 'v_current_period_total'
- Cleaned up redundant SQL operations and associated function logic
- Removed entry form, autofill, tables, etc

📊 Diffstat

budget.py 275
1 files changed, 12 insertions(+), 263 deletions(-)

Diff

diff --git a/budget.py b/budget.py
index 9630fdc..e2ed2d2 100644
--- a/budget.py
+++ b/budget.py
@@ -1,82 +1,20 @@
from http.server import BaseHTTPRequestHandler, HTTPServer
- from urllib.parse import parse_qs
import sqlite3
- from datetime import datetime
import webbrowser
import threading
DB = "budget.db"
- def init_db():
- with sqlite3.connect(DB) as conn:
- conn.execute("""
- CREATE TABLE IF NOT EXISTS entries (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- credit REAL DEFAULT 0,
- debit REAL DEFAULT 0,
- amount REAL NOT NULL,
- description TEXT,
- details TEXT,
- created_at TEXT NOT NULL
- )
- """)
- conn.execute("""
- CREATE TABLE IF NOT EXISTS budget (
- id INTEGER PRIMARY KEY AUTOINCREMENT,
- category TEXT NOT NULL UNIQUE,
- planned_amount REAL NOT NULL
- )
- """)
-
def get_balance():
with sqlite3.connect(DB) as conn:
cur = conn.execute("SELECT COALESCE(SUM(amount), 0) FROM entries")
return cur.fetchone()[0]
def get_month_total():
- now = datetime.now()
- start = datetime(now.year, now.month, 1)
- end = datetime(now.year + (now.month // 12), ((now.month % 12) + 1), 1)
-
with sqlite3.connect(DB) as conn:
- cur = conn.execute("""
- SELECT COALESCE(SUM(amount), 0)
- FROM entries
- WHERE created_at >= ? AND created_at < ?
- """, (start.isoformat(), end.isoformat()))
+ cur = conn.execute("SELECT total_amount FROM v_current_period_total")
return cur.fetchone()[0]
- def get_total_budget_amount():
- with sqlite3.connect(DB) as conn:
- cur = conn.execute("SELECT COALESCE(SUM(planned_amount), 0) FROM budget")
- return cur.fetchone()[0]
-
- def get_budget_items():
- with sqlite3.connect(DB) as conn:
- conn.row_factory = sqlite3.Row
- cur = conn.execute("SELECT id, category, planned_amount FROM budget ORDER BY category")
- return cur.fetchall()
-
- def upsert_budget(category, planned_amount):
- with sqlite3.connect(DB) as conn:
- conn.execute("""
- INSERT INTO budget (category, planned_amount)
- VALUES (?, ?)
- ON CONFLICT(category) DO UPDATE SET planned_amount = excluded.planned_amount
- """, (category, planned_amount))
-
- def delete_budget(category):
- with sqlite3.connect(DB) as conn:
- conn.execute("DELETE FROM budget WHERE category = ?", (category,))
-
- def update_budget(category, planned_amount, original_category):
- with sqlite3.connect(DB) as conn:
- if category != original_category:
- conn.execute("DELETE FROM budget WHERE category = ?", (original_category,))
- conn.execute("INSERT INTO budget (category, planned_amount) VALUES (?, ?)", (category, planned_amount))
- else:
- conn.execute("UPDATE budget SET planned_amount = ? WHERE category = ?", (planned_amount, category))
-
HTML = """
<!doctype html>
<html>
@@ -90,86 +28,16 @@ body {{
margin: 40px auto;
}}
- input, select, button {{
- padding: 8px;
- font-size: 16px;
- }}
-
.card {{
border: 1px solid #ccc;
padding: 10px;
margin-top: 20px;
}}
-
- table {{
- width: 100%;
- border-collapse: collapse;
- margin-top: 15px;
- }}
-
- th, td {{
- text-align: left;
- padding: 8px;
- border-bottom: 1px solid #eee;
- }}
-
- th {{
- border-bottom: 2px solid #ccc;
- }}
-
- .actions {{
- display: inline;
- }}
-
- .budget-input {{
- width: 100px;
- padding: 4px;
- margin: -4px 0;
- }}
</style>
- <script>
- function autoFillAmount() {{
- var description = document.getElementById("description").value;
- var amountField = document.getElementById("amount");
-
- if (description === "Salary") {{
- amountField.value = "1000";
- }} else if (description === "Amazon Prime") {{
- amountField.value = "-8.99";
- }} else if (description === "Energy bill") {{
- amountField.value = "-150";
- }} else if (description === "TV Licence") {{
- amountField.value = "-12.99";
- }}
- }}
- </script>
</head>
<body>
- <h1>Budget Tracker</h1>
-
- <form method="POST" action="/add">
- <input id="amount" name="amount" type="text" placeholder="Amount (+ or -)" required>
- <select id="description" name="description" required onchange="autoFillAmount()">
- <option value="Amazon Prime">Amazon Prime</option>
- <option value="Cash" selected>Cash</option>
- <option value="Council Tax">Council Tax</option>
- <option value="Credit card">Credit card</option>
- <option value="Energy bill">Energy bill</option>
- <option value="Fuel">Fuel</option>
- <option value="Other">Other</option>
- <option value="Rent">Rent</option>
- <option value="Salary">Salary</option>
- <option value="Savings">Savings</option>
- <option value="Shopping">Shopping</option>
- <option value="Sky TV">Sky TV</option>
- <option value="TV Licence">TV Licence</option>
- <option value="Virgin Media">Virgin Media</option>
- <option value="Water">Water</option>
- </select>
- <input name="details" type="text" placeholder="Details (Optional)">
- <button type="submit">Add</button>
- </form>
+ <h1>Budget Dashboard</h1>
<div class="card">
<h2>Current Balance</h2>
@@ -178,29 +46,7 @@ function autoFillAmount() {{
<div class="card">
<h2>This Month</h2>
- <p>Total: {month:.2f}</p>
- <p>Budget total: {total_budget_amount:.2f}</p>
- <p>Projected remaining bal: {projected_remaining:.2f}</p>
- </div>
-
- <div class="card">
- <h2>Monthly Budget Targets</h2>
- <form method="POST" action="/budget/add">
- <input name="category" type="text" placeholder="Item" required>
- <input type="text" name="planned_amount" placeholder="Planned amount (+)" required>
- <button type="submit">Save Budget</button>
- </form>
-
- <form method="POST" action="/budget/update">
- <table>
- <tr>
- <th>Category</th>
- <th>Planned</th>
- <th>Actions</th>
- </tr>
- {budget_rows}
- </table>
- </form>
+ <p>Total In & Out: {month:.2f}</p>
</div>
</body>
@@ -208,29 +54,19 @@ function autoFillAmount() {{
"""
class Handler(BaseHTTPRequestHandler):
+
+ def log_message(self, format, *args):
+ pass
+
def do_GET(self):
if self.path == "/":
balance = get_balance()
month = get_month_total()
- total_budget_amount = get_total_budget_amount()
- projected_remaining = month - total_budget_amount
- budget_items = get_budget_items()
-
- budget_rows = ""
- for idx, item in enumerate(budget_items):
- budget_rows += f"""
- <tr>
- <td><input type="text" name="category_{idx}" value="{item['category']}" class="budget-input" style="width:150px"></td>
- <td><input type="text" name="planned_amount_{idx}" value="{item['planned_amount']:.2f}" class="budget-input"></td>
- <td>
- <input type="hidden" name="original_category_{idx}" value="{item['category']}">
- <button type="submit" name="update_{idx}" value="1">Update</button>
- <button type="submit" name="delete_{idx}" value="1" formaction="/budget/delete" onclick="return confirm('Delete?')">Delete</button>
- </td>
- </tr>
- """
- page = HTML.format(balance=balance, month=month, total_budget_amount=total_budget_amount, projected_remaining=projected_remaining, budget_rows=budget_rows)
+ page = HTML.format(
+ balance=balance,
+ month=month,
+ )
self.send_response(200)
self.send_header("Content-type", "text/html")
@@ -239,101 +75,14 @@ class Handler(BaseHTTPRequestHandler):
else:
self.send_error(404)
- def do_POST(self):
- if self.path == "/add":
- length = int(self.headers["Content-Length"])
- body = self.rfile.read(length).decode()
- data = parse_qs(body)
-
- amount = float(data["amount"][0])
- description = data.get("description", [""])[0]
- details = data.get("details", [""])[0]
-
- if amount > 0:
- credit = amount
- debit = 0
- else:
- credit = 0
- debit = abs(amount)
-
- with sqlite3.connect(DB) as conn:
- conn.execute(
- "INSERT INTO entries(credit, debit, amount, description, details, created_at) VALUES (?, ?, ?, ?, ?, ?)",
- (credit, debit, amount, description, details, datetime.now().date().isoformat())
- )
-
- self.send_response(303)
- self.send_header("Location", "/")
- self.end_headers()
-
- elif self.path == "/budget/add":
- length = int(self.headers["Content-Length"])
- body = self.rfile.read(length).decode()
- data = parse_qs(body)
-
- category = data.get("category", [""])[0]
- planned_amount = float(data.get("planned_amount", [0])[0])
-
- if category and planned_amount:
- upsert_budget(category, planned_amount)
-
- self.send_response(303)
- self.send_header("Location", "/")
- self.end_headers()
-
- elif self.path == "/budget/update":
- length = int(self.headers["Content-Length"])
- body = self.rfile.read(length).decode()
- data = parse_qs(body)
-
- # Find which row was updated
- for key in data:
- if key.startswith("update_"):
- idx = key.split("_")[1]
- category = data.get(f"category_{idx}", [""])[0]
- planned_amount = float(data.get(f"planned_amount_{idx}", [0])[0])
- original_category = data.get(f"original_category_{idx}", [""])[0]
-
- if category and planned_amount:
- update_budget(category, planned_amount, original_category)
- break
-
- self.send_response(303)
- self.send_header("Location", "/")
- self.end_headers()
-
- elif self.path == "/budget/delete":
- length = int(self.headers["Content-Length"])
- body = self.rfile.read(length).decode()
- data = parse_qs(body)
-
- # Find which row was deleted
- for key in data:
- if key.startswith("delete_"):
- idx = key.split("_")[1]
- original_category = data.get(f"original_category_{idx}", [""])[0]
-
- if original_category:
- delete_budget(original_category)
- break
-
- self.send_response(303)
- self.send_header("Location", "/")
- self.end_headers()
-
- else:
- self.send_error(404)
-
def open_browser():
webbrowser.open("http://localhost:8080")
def run():
- init_db()
server = HTTPServer(("localhost", 8080), Handler)
-
print("Running on http://localhost:8080")
- threading.Timer(0.2, open_browser).start()
+ threading.Thread(target=open_browser, daemon=True).start()
try:
server.serve_forever()