From: Christian Heller <c.heller@plomlompom.de> Date: Sun, 19 Nov 2023 05:12:13 +0000 (+0100) Subject: Refactor all accounting scripts. X-Git-Url: https://plomlompom.com/repos/%7B%7Bdb.prefix%7D%7D/static/%7B%7Bprefix%7D%7D/calendar?a=commitdiff_plain;h=436e0f40b45319ef4452ce6ced1a3c3df813119b;p=misc Refactor all accounting scripts. --- diff --git a/calories.py b/calories.py index 5d2fc98..88165b8 100644 --- a/calories.py +++ b/calories.py @@ -1,43 +1,76 @@ -from http.server import BaseHTTPRequestHandler, HTTPServer import os import json import datetime +import jinja2 +from plomlib import PlomDB, PlomException, run_server, run_server, PlomServer -hostName = "localhost" -serverPort = 8081 +db_path = '/home/plom/org/calories_db.json' -def build_page(eatable_rows, consumption_rows, eatables_selection, day_rows): - return """<html> -<meta charset="UTF-8"> +server_port = 8081 + +tmpl = """ <style> table { margin-bottom: 2em; } th, td { text-align: left; } td.number { text-align: right; } input[type="number"] { text-align: right; } -</style>""" + f""" +</style> <body> <form action="/" method="POST"> <td><input name="update" type="submit" value="update" /></td> <table> <tr><th>eatable</th><th>unit count</th><th>unit weight (g)</th><th>calories</th><th>sugar (g)</th></tr> -{consumption_rows} +{% for c in consumptions %} +<tr> +<input type="hidden" name="keep_visible" value="1"><input name="eatable_key" type="hidden" value="{{c.key|e}}"> +<td class="number"><input class="unit_count number" name="unit_count" type="number" min="0" step="0.1" value="{{c.count}}" /></td> +<td>{{c.title}}</td> +<td></td> +<td class="number">{{c.cals}}</td> +<td class="number">{{c.sugar}}</td> +</tr> +{% endfor %} <tr> <th>add from DB:</th> </tr> <tr> <input type="hidden" name="keep_visible" value="0"> <td class="number"><input class="unit_count" name="unit_count" type="number" step="0.1" min="0" value="0" /></td> -<td><select name="eatable_key">{eatables_selection}</select></td> +<td><select name="eatable_key">{% for sel in eatables_selection %} +<option value="{{sel.0|e}}">{{sel.1|e}}</option> +{% endfor %}</select></td> <td></td> </tr> </table> <table> +<tr><th>today:</th><th></th><th></th><th>archive?</th></tr> +<td><input name="new_date" size=8 value="{{db.today_date}}" /><td> +<td class="number"><input name="new_day_cals" type="hidden" value="{{db.today.calories}}" readonly />{{db.today.calories}}</td> +<td class="number"><input name="new_day_sugar" type="hidden" value="{{db.today.sugar_g}}" readonly />{{db.today.sugar_g}}</td> +<td><input name="archive_day" type="checkbox" /></td> +</tr> <tr><th>day</th><th>calories</th><th>sugar (g)</th></tr> -{day_rows} +{% for d in days %} +<tr> +<td><input name="day_date" type="hidden" value="{{d.date|e}}" />{{d.date_short|e}}</td> +<td class="number"><input name="day_cals" type="hidden" step="0.1" min="0" value="{{d.cals}}" />{{d.cals}}</td> +<td class="number"><input name="day_sugar" type="hidden" step="0.1" min="0" value="{{d.sugar}}" />{{d.sugar}}</td> +</tr> +{% endfor %} </table> <table> <tr><th>title</th><th>calories</th><th>sugar (g)</th><th>standard weight (g)</th><th>comments</th><th>delete</th></tr> -{eatable_rows} +{% for e in eatables %} +<tr> +<input name="eatable_uuid" type="hidden" value="{{e.uuid}}" /> +<td><input name="title" value="{{e.title|e}}" /></td> +<td class="number"><input name="cals" type="number" step="0.1" min="0" value="{{e.cals}}" /></td> +<td class="number"><input name="sugar_g" type="number" step="0.1" min="0" value="{{e.sugar_g}}" /></td> +<td class="number"><input name="standard_g" type="number" step="0.1" min="0" value="{{e.sugar_g}}" /></td> +<td><input name="comments" value="{{e.comments|e}}" /</td> +<td><input name="delete" type="checkbox" value="{{e.uuid}}" /> +</tr> +{% endfor %} <tr> <th>add:</th> </tr> @@ -52,7 +85,6 @@ input[type="number"] { text-align: right; } </form> </body> <script> -""" + """ var unit_count_inputs = document.getElementsByClassName("unit_count"); for (let i = 0; i < unit_count_inputs.length; i++) { let input = unit_count_inputs[i]; @@ -66,11 +98,8 @@ for (let i = 0; i < unit_count_inputs.length; i++) { } </script> -</html> """ -class LockFileDetected(Exception): - pass class Eatable: @@ -92,6 +121,7 @@ class Eatable: "popularity": self.popularity } + class Consumption: def __init__(self, eatable_key, unit_count=None, keep_visible=0): @@ -106,6 +136,7 @@ class Consumption: "keep_visible": self.keep_visible } + class Day: def __init__(self, calories, sugar_g): @@ -118,20 +149,22 @@ class Day: "sugar_g": self.sugar_g, } -class Database: + +class CaloriesDB(PlomDB): def __init__(self, load_from_file=True): - db_name = "calories_db" - self.db_file = db_name + ".json" - self.lock_file = db_name+ ".lock" + self.load_from_file = load_from_file self.eatables = {} self.consumptions = [] self.days = {} self.today = Day(0, 0) self.today_date = "" - if load_from_file and os.path.exists(self.db_file): - with open(self.db_file, "r") as f: - self.from_dict(json.load(f)) + super().__init__(db_path) + + def read_db_file(self, f): + if not self.load_from_file: + return + self.from_dict(json.load(f)) def from_dict(self, d): self.set_today_date(d["today_date"]) @@ -161,14 +194,14 @@ class Database: return {"cals": calories, "sugar": sugar_g } def eatables_selection(self): - html = '' + options = [] already_selected = [c.eatable_key for c in self.consumptions] for k, v in sorted(self.eatables.items(), key=lambda item: item[1].title): if k in already_selected: continue v = self.eatables[k] - html += '<option value="%s">%s</option>' % (k, v.title) - return html + options += [(k, v.title)] + return options def add_eatable(self, id_, eatable): self.eatables[id_] = eatable @@ -188,26 +221,17 @@ class Database: del self.eatables[id_] def write(self): - import shutil - if os.path.exists(self.lock_file): - raise LockFileDetected - if os.path.exists(self.db_file): - shutil.copy(self.db_file, self.db_file + ".bak") - f = open(self.lock_file, "w+") - f.close() - with open(self.db_file, "w") as f: - json.dump(self.to_dict(), f) - os.remove(self.lock_file) + self.write_text_to_db(json.dumps(self.to_dict())) -class MyServer(BaseHTTPRequestHandler): +class CaloriesServer(PlomServer): def do_POST(self): from uuid import uuid4 from urllib.parse import parse_qs length = int(self.headers['content-length']) postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1) - db = Database(False) + db = CaloriesDB(False) def decode(key, i, is_num=True): if is_num: return float(postvars[key][i]) @@ -259,70 +283,51 @@ class MyServer(BaseHTTPRequestHandler): break try: db.write() - self.send_response(302) - self.send_header('Location', '/') - self.end_headers() - except LockFileDetected: - self.send_response(400) - self.end_headers() - self.wfile.write(bytes("Sorry, lock file!", "utf-8")) + self.redirect() + except PlomException as e: + self.fail_400(e) def do_GET(self): - self.send_response(200) - self.send_header("Content-type", "text/html") - self.end_headers() - db = Database() - - eatables = "" + db = CaloriesDB() + # eatables = "" + eatable_rows = [] for k,v in db.eatables.items(): - eatables += "<tr>"\ - "<input name=\"eatable_uuid\" type=\"hidden\" value=\"%s\" />"\ - "<td><input name=\"title\" value=\"%s\" /></td>"\ - "<td class\"number\"><input name=\"cals\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\ - "<td class\"number\"><input name=\"sugar_g\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\ - "<td class\"number\"><input name=\"standard_g\" type=\"number\" step=\"0.1\" min=\"1\" value=\"%1.f\" /></td>"\ - "<td><input name=\"comments\" value=\"%s\" /></td>"\ - "<td><input name=\"delete\" type=\"checkbox\" value=\"%s\" /></td>"\ - "</tr>" % (k, v.title, v.cals, v.sugar_g, v.standard_g, v.comments, k) - consumptions = "" + eatable_rows += [{ + 'uuid': k, + 'title': v.title, + 'cals': f'{v.cals:.1f}', + 'sugar_g': f'{v.sugar_g:.1f}', + 'standard_g': f'{v.standard_g:.1f}', + 'comments': v.comments + }] db.consumptions = sorted(db.consumptions, key=lambda x: db.eatables[x.eatable_key].title) + consumption_rows = [] for c in db.consumptions: r = db.calc_consumption(c) - consumptions += "<tr />"\ - "<input type=\"hidden\" name=\"keep_visible\" value=\"1\"><input name=\"eatable_key\" type=\"hidden\" value=\"%s\">"\ - "<td class\"number\"><input class=\"unit_count number\" name=\"unit_count\" type=\"number\" min=\"0\" step=\"0.1\" value=\"%.1f\" /></td>"\ - "<td>%s</td>"\ - "<td></td>"\ - "<td class=\"number\">%.1f</td>"\ - "<td class=\"number\">%.1f</td>"\ - "</tr>" % (c.eatable_key, c.unit_count, db.eatables[c.eatable_key].title, r["cals"], r["sugar"]) - day_rows = "" - for date in sorted(db.days.keys()): + consumption_rows += [{ + 'key': c.eatable_key, + 'count': c.unit_count, + 'title': db.eatables[c.eatable_key].title, + 'cals': r['cals'], + 'sugar': r['sugar'] + }] + day_rows = [] + for date in reversed(sorted(db.days.keys())): day = db.days[date] - day_rows = "<tr>"\ - "<td><input name=\"day_date\" type=\"hidden\" value=\"%s\" />%s</td>"\ - "<td class=\"number\"><input name=\"day_cals\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\ - "<td class=\"number\"><input name=\"day_sugar\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\ - "</tr>" % (date, date[:10], day.calories, day.calories, day.sugar_g, day.sugar_g) + day_rows - day_rows = "<tr>"\ - "<th>today:</th><th></th><th></th><th>archive?</th>"\ - "</tr>"\ - "<tr>"\ - "<td><input name=\"new_date\" size=8 value=\"%s\" /></td>"\ - "<td class=\"number\"><input name=\"new_day_cals\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\ - "<td class=\"number\"><input name=\"new_day_sugar\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\ - "<td><input name=\"archive_day\" type=\"checkbox\" /></td>"\ - "</tr>" % (db.today_date, db.today.calories, db.today.calories, db.today.sugar_g, db.today.sugar_g) + day_rows - page = build_page(eatables, consumptions, db.eatables_selection(), day_rows) - self.wfile.write(bytes(page, "utf-8")) - - -if __name__ == "__main__": - webServer = HTTPServer((hostName, serverPort), MyServer) - print(f"Server started http://{hostName}:{serverPort}") - try: - webServer.serve_forever() - except KeyboardInterrupt: - pass - webServer.server_close() - print("Server stopped.") + day_rows += [{ + 'date': date, + 'date_short': date[:10], + 'cals': f'{day.calories:.1f}', + 'sugar': f'{day.sugar_g:.1f}', + }] + page = jinja2.Template(tmpl).render( + db=db, + days=day_rows, + consumptions=consumption_rows, + eatables=eatable_rows, + eatables_selection=db.eatables_selection()) + self.send_HTML(page) + + +if __name__ == "__main__": + run_server(server_port, CaloriesServer) diff --git a/income_progress_bars.py b/income_progress_bars.py index 94a3456..553627d 100644 --- a/income_progress_bars.py +++ b/income_progress_bars.py @@ -1,13 +1,12 @@ -# from http.server import BaseHTTPRequestHandler, HTTPServer import os import json import jinja2 from plomlib import PlomDB, PlomException, run_server, run_server, PlomServer -server_port = 8081 +server_port = 8083 +db_path = '/home/plom/org/income.json' -tmpl = jinja2.Template("""<html> -<meta charset="UTF-8"> +tmpl = """ <style> body { font-family: monospace; @@ -78,20 +77,20 @@ table { </style> <body> <table> -<tr><th /><th>earned</th><th>progress</th><th>surplus</th></tr > -{% for p in progress_bars %} -<tr><th>{{p.title}}</th> +<tr><th></th><th>earned</th><th>progress</th><th>surplus</th></tr > +{% for p in progress_bars %}<tr><th>{{p.title}}</th> <td class="countable">{{p.earned|round(2)}}</td> -<td class="progressbar">{% if p.time_progress >= 0 %}<div class="time_progress" style="margin-left: {{p.time_progress}}px"></div>{% endif %}<div class="progress" style="background-color: {% if p.success < 0.5 %}red{% elif p.success < 1 %}yellow{% else %}green{% endif %}; width: {{p.success_income_cut}}"></div></td> -<td class="progressbar surplusbar"><div class="diff_goal">{{p.diff_goal}}</div><div class="progressbar surplus" style="width: {{p.success_income_bonus}}" /></div></td></tr> -{% endfor %} -</table> +<td class="progressbar">{% if p.time_progress >= 0 %}<div class="time_progress" style="margin-left: {{p.time_progress}}px"></div>{% endif %}<div class="progress" style="background-color: {% if p.success < 0.5 %}red{% elif p.success < 1 %}yellow{% else %}green{% endif %}; width: {{p.success_income_cut}}px"></div></td> +<td class="progressbar surplusbar"><div class="diff_goal">{{p.diff_goal}}</div><div class="progressbar surplus" style="width: {{p.success_income_bonus}}px" ></div></td></tr> +{% endfor %}</table> + <form action="/" method="POST"> <table> <tr><th>hourly rate</th><th>worked today</th></tr> <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_1" value="{{workday_hourly_rate_1}}"/>â¬</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_1" value="{{workday_minutes_worked_1}}" step="5" /> minutes</td> <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_2" value="{{workday_hourly_rate_2}}"/>â¬</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_2" value="{{workday_minutes_worked_2}}" step="5" /> minutes</td> <tr><td class="input_container"><input type="number" min="1" class="rate" name="workday_hourly_rate_3" value="{{workday_hourly_rate_3}}"/>â¬</td><td><input type="number" min="0" class="minutes" name="workday_minutes_worked_3" value="{{workday_minutes_worked_3}}" step="5" /> minutes</td> +</table> <table> <tr><th>yearly income goal</th><td><input type="number" class="year_goal" min="1" name="year_goal" value="{{year_goal}}" />â¬</td></tr> <tr><th>monthly income goal</th><td class="countable">{{month_goal|round(2)}}â¬</td></tr> @@ -103,8 +102,8 @@ table { <input type="submit" name="update" value="update inputs" /> <input type="submit" name="finish" value="finish day" /> </form> -</body -</html>""") +""" + class IncomeDB(PlomDB): @@ -124,7 +123,7 @@ class IncomeDB(PlomDB): self.workday_minutes_worked_3 = 0, self.year_goal = 20000, self.workdays_per_month = 16 - super().__init__('_income') + super().__init__(db_path) def read_db_file(self, f): d = json.load(f) @@ -143,6 +142,7 @@ class IncomeDB(PlomDB): def write_db(self): self.write_text_to_db(json.dumps(self.to_dict())) + class ProgressBar: def __init__(self, title, earned, goal, time_progress=-1): self.title = title @@ -160,7 +160,7 @@ class ProgressBar: if time_progress > 0: self.success = success_income / time_progress -# class MyServer(BaseHTTPRequestHandler): + class IncomeServer(PlomServer): def do_POST(self): @@ -188,9 +188,7 @@ class IncomeServer(PlomServer): db.workday_minutes_worked_2 = 0 db.workday_minutes_worked_3 = 0 db.write_db() - self.send_response(302) - self.send_header('Location', '/') - self.end_headers() + self.redirect() except PlomException as e: self.fail_400(e) @@ -237,7 +235,7 @@ class IncomeServer(PlomServer): ProgressBar("month", month_plus, month_goal, progress_time_month), ProgressBar("week", week_plus, week_goal, progress_time_week), ProgressBar("workday", day_income, workday_goal)] - page = tmpl.render( + page = jinja2.Template(tmpl).render( progress_bars = progress_bars, workday_hourly_rate_1 = db.workday_hourly_rate_1, workday_minutes_worked_1 = db.workday_minutes_worked_1, @@ -256,5 +254,6 @@ class IncomeServer(PlomServer): except PlomException as e: self.fail_400(e) + if __name__ == "__main__": run_server(server_port, IncomeServer) diff --git a/ledger.py b/ledger.py index b4d9fd9..244a684 100755 --- a/ledger.py +++ b/ledger.py @@ -1,6 +1,4 @@ -from http.server import BaseHTTPRequestHandler, HTTPServer import os -import html import jinja2 import decimal from datetime import datetime, timedelta @@ -8,6 +6,89 @@ from urllib.parse import parse_qs, urlparse from plomlib import PlomDB, PlomException, run_server, run_server, PlomServer server_port = 8082 +db_path = '/home/plom/org/ledger2023.dat' + +html_head = """ +<style> +body { color: #000000; } +table { margin-bottom: 2em; } +th, td { text-align: left } +input[type=number] { text-align: right; font-family: monospace; } +.money { font-family: monospace; text-align: right; } +.comment { font-style: italic; color: #777777; } +.meta { font-size: 0.75em; color: #777777; } +.full_line_comment { display: block; white-space: nowrap; width: 0; } +</style> +<body> +<a href="/">ledger</a> +<a href="/balance">balance</a> +<a href="/add_free">add free</a> +<a href="/add_structured">add structured</a> +<hr /> +""" +booking_html = """ +<p id="{{nth}}"><a href="#{{nth}}">{{date}}</a> {{desc}} <span class="comment">{{head_comment|e}}</span><br /> +<span class="meta">[edit: <a href="/add_structured?start={{start}}&end={{end}}">structured</a> +/ <a href="/add_free?start={{start}}&end={{end}}">free</a> +| copy:<a href="/copy_structured?start={{start}}&end={{end}}">structured</a> +/ <a href="/copy_free?start={{start}}&end={{end}}">free</a> +| move {% if move_up %}<a href="/move_up?start={{start}}&end={{end}}">up</a>{% else %}up{% endif %}/{% if move_down %}<a href="/move_down?start={{start}}&end={{end}}">down</a>{% else %}down{% endif %} +| <a href="/balance?stop={{nth+1}}">balance after</a> +]</span> +<table> +{% for l in booking_lines %} +{% if l.acc %} +<tr><td>{{l.acc|e}}</td><td class="money">{{l.money|e}}</td><td class="comment">{{l.comment|e}}</td></tr> +{% else %} +<tr><td><div class="comment full_line_comment">{{l.comment|e}}</div></td></tr> +{% endif %} +{% endfor %} +</table></p> +""" +add_form_header = """<form method="POST" action="{{action|e}}"> +<input type="submit" name="check" value="check" /> +<input type="submit" name="revert" value="revert" /> +""" +add_form_footer = """ +<input type="hidden" name="start" value={{start}} /> +<input type="hidden" name="end" value={{end}} /> +<input type="submit" name="save" value="save!"> +</form> +""" +add_free_html = """<br /> +<textarea name="booking" rows=10 cols=80> +{% for line in lines %}{{ line }} +{% endfor %} +</textarea> +""" +add_structured_html = """ +<input type="submit" name="add_taxes" value="add taxes" /> +<input type="submit" name="add_taxes2" value="add taxes2" /> +<input type="submit" name="add_sink" value="add sink" /> +<br /> +<input name="date" value="{{date|e}}" size=9 /> +<input name="description" value="{{desc|e}}" list="descriptions" /> +<textarea name="line_0_comment" rows=1 cols=20>{{head_comment|e}}</textarea> +<input type="submit" name="line_0_add" value="[+]" /> +<br /> +{% for line in booking_lines %} +<input name="line_{{line.i}}_account" value="{{line.acc|e}}" size=40 list="accounts" /> +<input type="number" name="line_{{line.i}}_amount" step=0.01 value="{{line.amt}}" size=10 /> +<input name="line_{{line.i}}_currency" value="{{line.curr|e}}" size=3 list="currencies" /> +<input type="submit" name="line_{{line.i}}_delete" value="[x]" /> +<input type="submit" name="line_{{line.i}}_delete_after" value="[XX]" /> +<input type="submit" name="line_{{line.i}}_add" value="[+]" /> +<textarea name="line_{{line.i}}_comment" rows=1 cols={% if line.comm_cols %}{{line.comm_cols}}{% else %}20{% endif %}>{{line.comment|e}}</textarea> +<br /> +{% endfor %} +{% for name, items in datalist_sets.items() %} +<datalist id="{{name}}"> +{% for item in items %} + <option value="{{item|e}}">{{item|e}}</option> +{% endfor %} +</datalist> +{% endfor %} +""" def apply_booking_to_account_balances(account_sums, account, currency, amount): @@ -256,7 +337,7 @@ class LedgerDB(PlomDB): self.bookings = [] self.comments = [] self.real_lines = [] - super().__init__('_ledger') + super().__init__(db_path) ret = parse_lines(self.real_lines) self.bookings += ret[0] self.comments += ret[1] @@ -433,55 +514,9 @@ class LedgerDB(PlomDB): class LedgerServer(PlomServer): - header = """<html> -<meta charset="UTF-8"> -<style> -body { color: #000000; } -table { margin-bottom: 2em; } -th, td { text-align: left } -input[type=number] { text-align: right; font-family: monospace; } -.money { font-family: monospace; text-align: right; } -.comment { font-style: italic; color: #777777; } -.meta { font-size: 0.75em; color: #777777; } -.full_line_comment { display: block; white-space: nowrap; width: 0; } -</style> -<body> -<a href="/">ledger</a> -<a href="/balance">balance</a> -<a href="/add_free">add free</a> -<a href="/add_structured">add structured</a> -<hr /> -""" - booking_tmpl = jinja2.Template(""" -<p id="{{nth}}"><a href="#{{nth}}">{{date}}</a> {{desc}} <span class="comment">{{head_comment|e}}</span><br /> -<span class="meta">[edit: <a href="/add_structured?start={{start}}&end={{end}}">structured</a> -/ <a href="/add_free?start={{start}}&end={{end}}">free</a> -| copy:<a href="/copy_structured?start={{start}}&end={{end}}">structured</a> -/ <a href="/copy_free?start={{start}}&end={{end}}">free</a> -| move {% if move_up %}<a href="/move_up?start={{start}}&end={{end}}">up</a>{% else %}up{% endif %}/{% if move_down %}<a href="/move_down?start={{start}}&end={{end}}">down</a>{% else %}down{% endif %} -| <a href="/balance?stop={{nth+1}}">balance after</a> -]</span> -<table> -{% for l in booking_lines %} -{% if l.acc %} -<tr><td>{{l.acc|e}}</td><td class="money">{{l.money|e}}</td><td class="comment">{{l.comment|e}}</td></tr> -{% else %} -<tr><td><div class="comment full_line_comment">{{l.comment|e}}</div></td></tr> -{% endif %} -{% endfor %} -</table></p> -""") - add_form_header = """<form method="POST" action="{{action|e}}"> -<input type="submit" name="check" value="check" /> -<input type="submit" name="revert" value="revert" /> -""" - add_form_footer = """ -<input type="hidden" name="start" value={{start}} /> -<input type="hidden" name="end" value={{end}} /> -<input type="submit" name="save" value="save!"> -</form> -""" - footer = "</body>\n<html>" + + def pre_init(self): + self.html_head += [html_head] def do_POST(self): try: @@ -522,15 +557,14 @@ input[type=number] { text-align: right; font-family: monospace; } nth = db.get_nth_for_booking_of_start_line(new_start) if new_start > start: nth -= 1 - redir_url = f'/#{nth}' - self.send_code_and_headers(302, [('Location', redir_url)]) + self.redirect( f'/#{nth}') # otherwise just re-build editing form else: if '/add_structured' == parsed_url.path: edit_content = self.add_structured(db, start, end, temp_lines=lines, add_empty_line=add_empty_line) else: edit_content = self.add_free(db, start, end) - self.send_HTML(self.header + edit_content + self.footer) + self.send_HTML(edit_content) except PlomException as e: self.fail_400(e) @@ -541,29 +575,27 @@ input[type=number] { text-align: right; font-family: monospace; } start = int(params.get('start', ['0'])[0]) end = int(params.get('end', ['0'])[0]) db = LedgerDB() - page = self.header if parsed_url.path == '/balance': stop = params.get('stop', [None])[0] - page += self.balance_as_html(db, stop) + page = self.balance_as_html(db, stop) elif parsed_url.path == '/add_free': - page += self.add_free(db, start, end) + page = self.add_free(db, start, end) elif parsed_url.path == '/add_structured': - page += self.add_structured(db, start, end) + page = self.add_structured(db, start, end) elif parsed_url.path == '/copy_free': - page += self.add_free(db, start, end, copy=True) + page = self.add_free(db, start, end, copy=True) elif parsed_url.path == '/copy_structured': - page += self.add_structured(db, start, end, copy=True) + page = self.add_structured(db, start, end, copy=True) elif parsed_url.path == '/move_up': nth = self.move_up(db, start, end) - self.send_code_and_headers(302, [('Location', f'/#{nth}')]) + self.redirect(f'/#{nth}') return elif parsed_url.path == '/move_down': nth = self.move_down(db, start, end) - self.send_code_and_headers(302, [('Location', f'/#{nth}')]) + self.redirect(f'/#{nth}') return else: - page += self.ledger_as_html(db) - page += self.footer + page = self.ledger_as_html(db) self.send_HTML(page) except PlomException as e: self.fail_400(e) @@ -644,6 +676,7 @@ input[type=number] { text-align: right; font-family: monospace; } return f"<pre>{content}</pre>" def ledger_as_html(self, db): + booking_tmpl = jinja2.Template(booking_html) single_c_tmpl = jinja2.Template('<span class="comment">{{c|e}}</span><br />') ## elements_to_write = [] last_i = i = 0 ## @@ -665,7 +698,7 @@ input[type=number] { text-align: right; font-family: monospace; } if booking_line[1] is not None: money = f'{booking_line[1]} {booking_line[2]}' booking_lines += [{'acc': booking_line[0], 'money':money, 'comment':comment}] ## - elements_to_write += [self.booking_tmpl.render( + elements_to_write += [booking_tmpl.render( nth=nth, start=booking.start_line, end=booking_end, @@ -679,46 +712,14 @@ input[type=number] { text-align: right; font-family: monospace; } return '\n'.join(elements_to_write) def add_free(self, db, start=0, end=0, copy=False): - tmpl = jinja2.Template(self.add_form_header + """<br /> -<textarea name="booking" rows=10 cols=80> -{% for line in lines %}{{ line }} -{% endfor %} -</textarea> -""" + self.add_form_footer) + tmpl = jinja2.Template(add_form_header + add_free_html + add_form_footer) lines = db.get_lines(start, end) if copy: start = end = 0 return tmpl.render(action='add_free', start=start, end=end, lines=lines) def add_structured(self, db, start=0, end=0, copy=False, temp_lines=[], add_empty_line=None): - tmpl = jinja2.Template(self.add_form_header + """ -<input type="submit" name="add_taxes" value="add taxes" /> -<input type="submit" name="add_taxes2" value="add taxes2" /> -<input type="submit" name="add_sink" value="add sink" /> -<br /> -<input name="date" value="{{date|e}}" size=9 /> -<input name="description" value="{{desc|e}}" list="descriptions" /> -<textarea name="line_0_comment" rows=1 cols=20>{{head_comment|e}}</textarea> -<input type="submit" name="line_0_add" value="[+]" /> -<br /> -{% for line in booking_lines %} -<input name="line_{{line.i}}_account" value="{{line.acc|e}}" size=40 list="accounts" /> -<input type="number" name="line_{{line.i}}_amount" step=0.01 value="{{line.amt}}" size=10 /> -<input name="line_{{line.i}}_currency" value="{{line.curr|e}}" size=3 list="currencies" /> -<input type="submit" name="line_{{line.i}}_delete" value="[x]" /> -<input type="submit" name="line_{{line.i}}_delete_after" value="[XX]" /> -<input type="submit" name="line_{{line.i}}_add" value="[+]" /> -<textarea name="line_{{line.i}}_comment" rows=1 cols={% if line.comm_cols %}{{line.comm_cols}}{% else %}20{% endif %}>{{line.comment|e}}</textarea> -<br /> -{% endfor %} -{% for name, items in datalist_sets.items() %} -<datalist id="{{name}}"> -{% for item in items %} - <option value="{{item|e}}">{{item|e}}</option> -{% endfor %} -</datalist> -{% endfor %} -""" + self.add_form_footer) + tmpl = jinja2.Template(add_form_header + add_structured_html + add_form_footer) lines = temp_lines if len(''.join(temp_lines)) > 0 else db.get_lines(start, end) bookings, comments = parse_lines(lines, validate_bookings=False) if len(bookings) > 1: diff --git a/plomlib.py b/plomlib.py index 9dac712..92bfb85 100644 --- a/plomlib.py +++ b/plomlib.py @@ -9,10 +9,10 @@ class PlomException(Exception): class PlomDB: def __init__(self, db_name): - self.db_file = db_name + ".json" - self.lock_file = db_name+ ".lock" + self.db_file = db_name + self.lock_file = db_name+ '.lock' if os.path.exists(self.db_file): - with open(self.db_file, "r") as f: + with open(self.db_file, 'r') as f: self.read_db_file(f) def lock(self): @@ -78,27 +78,24 @@ class PlomDB: class PlomServer(BaseHTTPRequestHandler): - header = '' - footer = '' - - def run(self, port): - from http.server import HTTPServer - webServer = HTTPServer(('localhost', port), type(self)) - print(f"Server started http://localhost:{port}") - try: - webServer.serve_forever() - except KeyboardInterrupt: - pass - webServer.server_close() - print("Server stopped.") + + def __init__(self, *args, **kwargs): + self.html_head = ['<!DOCTYPE html>\n<html>\n<meta charset="UTF-8">'] + self.html_foot = ['</body>\n</html>'] + self.pre_init() + super().__init__(*args, **kwargs) + + def pre_init(self): + pass def fail_400(self, e): - page = f'{self.header}ERROR: {e}{self.footer}' - self.send_HTML(page, 400) + self.send_HTML(f'ERROR: {e}', 400) def send_HTML(self, html, code=200): self.send_code_and_headers(code, [('Content-type', 'text/html')]) - self.wfile.write(bytes(html, "utf-8")) + header = '\n'.join(self.html_head) + footer = '\n'.join(self.html_foot) + self.wfile.write(bytes(f'{header}\n{html}\n{footer}', 'utf-8')) def send_code_and_headers(self, code, headers=[]): self.send_response(code) @@ -106,6 +103,10 @@ class PlomServer(BaseHTTPRequestHandler): self.send_header(fieldname, content) self.end_headers() + def redirect(self, url='/'): + self.send_code_and_headers(302, [('Location', url)]) + + def run_server(port, server_class): from http.server import HTTPServer