home · contact · privacy
Add foreign key restraints, expand and fix tests, add deletion and forking.
[misc] / income_progress_bars.py
index bccfc24142896db7d4bf945a8ec37d80f90564b7..5d9a887323feb2cc7da85f1e81dae7bc696d83f2 100644 (file)
@@ -1,13 +1,12 @@
-from http.server import BaseHTTPRequestHandler, HTTPServer
 import os
 import json
 import jinja2
+from plomlib import PlomDB, PlomException, run_server, PlomHandler 
 
-hostName = "localhost"
-serverPort = 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>
-<form action="/" method="POST">
+<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="{{homepage}}" 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,57 +102,46 @@ table {
 <input type="submit" name="update" value="update inputs" />
 <input type="submit" name="finish" value="finish day" />
 </form>
-</body
-</html>""")
+"""
 
-db_default = {
-  "timestamp_year": 0,
-  "timestamp_month": 0,
-  "timestamp_week": 0,
-  "year_income": 0,
-  "month_income": 0,
-  "week_income": 0,
-  "workday_hourly_rate_1": 10,
-  "workday_minutes_worked_1": 0,
-  "workday_hourly_rate_2": 25,
-  "workday_minutes_worked_2": 0,
-  "workday_hourly_rate_3": 50,
-  "workday_minutes_worked_3": 0,
-  "year_goal": 20000,
-  "workdays_per_month": 16
-}
-db_file = "db.json"
-lock_file = "db.lock"
-def load_db():
-    if os.path.exists(db_file):
-        with open(db_file, "r") as f:
-            return json.load(f)
-    else:
-        return db_default
 
-# class Database:
-#     data_default = {
-#       "timestamp_year": 0,
-#       "timestamp_month": 0,
-#       "timestamp_week": 0,
-#       "year_income": 0,
-#       "month_income": 0,
-#       "week_income": 0,
-#       "workday_hourly_rate_1": 10,
-#       "workday_minutes_worked_1": 0,
-#       "workday_hourly_rate_2": 25,
-#       "workday_minutes_worked_2": 0,
-#       "workday_hourly_rate_3": 50,
-#       "workday_minutes_worked_3": 0,
-#       "year_goal": 20000,
-#       "workdays_per_month": 16
-#     }
-#     def __init__(self):
-#         if os.path.exists(db_file):
-#             with open(db_file, "r") as f:
-#                 return json.load(f)
-#         else:
-#             return db_default
+class IncomeDB(PlomDB):
+
+    def __init__(self):
+        # defaults
+        self.timestamp_year = 0,
+        self.timestamp_month = 0,
+        self.timestamp_week = 0,
+        self.year_income = 0,
+        self.month_income = 0,
+        self.week_income = 0,
+        self.workday_hourly_rate_1 = 10,
+        self.workday_hourly_rate_2 = 25,
+        self.workday_hourly_rate_3 = 50,
+        self.workday_minutes_worked_1 = 0,
+        self.workday_minutes_worked_2 = 0,
+        self.workday_minutes_worked_3 = 0,
+        self.year_goal = 20000,
+        self.workdays_per_month = 16
+        super().__init__(db_path)
+
+    def read_db_file(self, f):
+        d = json.load(f)
+        for k, v in d.items():
+            if not hasattr(self, k):
+                raise PlomException("bad key in db: " + k)
+            setattr(self, k, v)
+
+    def to_dict(self):
+        keys = [k for k in dir(self) if (not k.startswith('_')) and (not callable(getattr(self, k)))]
+        d = {}
+        for k in keys:
+            d[k] = getattr(self, k)
+        return d
+
+    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):
@@ -172,120 +160,109 @@ class ProgressBar:
             if time_progress > 0:
                 self.success = success_income / time_progress
 
-class MyServer(BaseHTTPRequestHandler):
+
+class IncomeProgressHandler(PlomHandler):
+    
+    def app_init(self, handler):
+        default_path = '/income_progress'
+        handler.add_route('GET', default_path, self.display_income_progress) 
+        handler.add_route('POST', default_path, self.post_income_update) 
+        return 'income_progress', default_path 
 
     def do_POST(self):
+        self.try_do(self.post_income_update)
+
+    def post_income_update(self):
         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 = load_db()
-        db["workday_minutes_worked_1"] = int(postvars['workday_minutes_worked_1'][0])
-        db["workday_minutes_worked_2"] = int(postvars['workday_minutes_worked_2'][0])
-        db["workday_minutes_worked_3"] = int(postvars['workday_minutes_worked_3'][0])
-        db["workday_hourly_rate_1"] = int(postvars['workday_hourly_rate_1'][0])
-        db["workday_hourly_rate_2"] = int(postvars['workday_hourly_rate_2'][0])
-        db["workday_hourly_rate_3"] = int(postvars['workday_hourly_rate_3'][0])
-        db["year_goal"] = int(postvars['year_goal'][0])
-        db["workdays_per_month"] = int(postvars['workdays_per_month'][0])
+        db = IncomeDB()
+        db.workday_minutes_worked_1 = int(postvars['workday_minutes_worked_1'][0])
+        db.workday_minutes_worked_2 = int(postvars['workday_minutes_worked_2'][0])
+        db.workday_minutes_worked_3 = int(postvars['workday_minutes_worked_3'][0])
+        db.workday_hourly_rate_1 = int(postvars['workday_hourly_rate_1'][0])
+        db.workday_hourly_rate_2 = int(postvars['workday_hourly_rate_2'][0])
+        db.workday_hourly_rate_3 = int(postvars['workday_hourly_rate_3'][0])
+        db.year_goal = int(postvars['year_goal'][0])
+        db.workdays_per_month = int(postvars['workdays_per_month'][0])
         if 'finish' in postvars.keys():
-            day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"]
-            day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"]
-            day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"]
-            db["year_income"] += day_income
-            db["month_income"] += day_income
-            db["week_income"] += day_income
-            db["workday_minutes_worked_1"] = 0
-            db["workday_minutes_worked_2"] = 0
-            db["workday_minutes_worked_3"] = 0
-        if self.fail_on_lockfile():
-            return
-        with open(lock_file, "w+"): pass
-        with open(db_file, "w") as f:
-            json.dump(db, f)
-        os.remove(lock_file)
-        self.send_response(302)
-        self.send_header('Location', '/')
-        self.end_headers()
+            day_income = (db.workday_minutes_worked_1 / 60.0) * db.workday_hourly_rate_1
+            day_income += (db.workday_minutes_worked_2 / 60.0) * db.workday_hourly_rate_2
+            day_income += (db.workday_minutes_worked_3 / 60.0) * db.workday_hourly_rate_3
+            db.year_income += day_income
+            db.month_income += day_income
+            db.week_income += day_income
+            db.workday_minutes_worked_1 = 0
+            db.workday_minutes_worked_2 = 0
+            db.workday_minutes_worked_3 = 0
+        db.write_db()
+        homepage = self.apps['income_progress'] if hasattr(self, 'apps') else self.homepage
+        self.redirect(homepage)
 
     def do_GET(self):
+        self.try_do(self.display_income_progress)
+
+    def display_income_progress(self):
         import datetime
         import calendar
-        db = load_db()
+        db = IncomeDB()
         today = datetime.datetime.now()
         update_db = False
-        if today.year != db["timestamp_year"]:
-            db["timestamp_year"] = today.year
-            db["year_income"] = 0
+        if today.year != db.timestamp_year:
+            db.timestamp_year = today.year
+            db.timestamp_month = today.month
+            db.year_income = 0
+            db.month_income = 0
             update_db = True
-        if today.month != db["timestamp_month"]:
-            db["timestamp_month"] = today.month
-            db["month_income"] = 0
+        if today.month != db.timestamp_month:
+            db.timestamp_month = today.month
+            db.month_income = 0
             update_db = True
-        if today.isocalendar()[1] != db["timestamp_week"]:
-            db["timestamp_week"] = today.isocalendar()[1]
-            db["week_income"] = 0
+        if today.isocalendar()[1] != db.timestamp_week:
+            db.timestamp_week = today.isocalendar()[1]
+            db.week_income = 0
             update_db = True
-        if self.fail_on_lockfile():
-            return
         if update_db:
             print("Resetting timestamp")
-            with open(lock_file, "w+"): pass
-            with open(db_file, "w") as f:
-                json.dump(db, f)
-            os.remove(lock_file)
+            db.write_db()
         day_of_year = today.toordinal() - datetime.date(today.year, 1, 1).toordinal() + 1
         year_length = 365 + calendar.isleap(today.year)
-        workday_goal = db["year_goal"] / 12 / db["workdays_per_month"]
-        workdays_per_week = (db["workdays_per_month"] * 12) / (year_length / 7)
-        month_goal = db["year_goal"] / 12
-        week_goal = db["year_goal"] / (year_length / 7)
-        day_income = (db["workday_minutes_worked_1"] / 60.0) * db["workday_hourly_rate_1"]
-        day_income += (db["workday_minutes_worked_2"] / 60.0) * db["workday_hourly_rate_2"]
-        day_income += (db["workday_minutes_worked_3"] / 60.0) * db["workday_hourly_rate_3"]
-        year_plus = db["year_income"] + day_income
-        month_plus = db["month_income"] + day_income
-        week_plus = db["week_income"] + day_income
+        workday_goal = db.year_goal / 12 / db.workdays_per_month
+        workdays_per_week = (db.workdays_per_month * 12) / (year_length / 7)
+        month_goal = db.year_goal / 12
+        week_goal = db.year_goal / (year_length / 7)
+        day_income = (db.workday_minutes_worked_1 / 60.0) * db.workday_hourly_rate_1
+        day_income += (db.workday_minutes_worked_2 / 60.0) * db.workday_hourly_rate_2
+        day_income += (db.workday_minutes_worked_3 / 60.0) * db.workday_hourly_rate_3
+        year_plus = db.year_income + day_income
+        month_plus = db.month_income + day_income
+        week_plus = db.week_income + day_income
         progress_time_year = day_of_year / year_length
         progress_time_month = today.day / calendar.monthrange(today.year, today.month)[1]
         progress_time_week = today.weekday() / 7
-        progress_bars = [ProgressBar("year", year_plus, db["year_goal"], progress_time_year),
+        progress_bars = [ProgressBar("year", year_plus, db.year_goal, progress_time_year),
                          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(
+        homepage = self.apps['income_progress'] if hasattr(self, 'apps') else self.homepage
+        page = jinja2.Template(tmpl).render(
+                homepage = homepage,
                 progress_bars = progress_bars,
-                workday_hourly_rate_1=db["workday_hourly_rate_1"],
-                workday_minutes_worked_1=db["workday_minutes_worked_1"],
-                workday_hourly_rate_2=db["workday_hourly_rate_2"],
-                workday_minutes_worked_2=db["workday_minutes_worked_2"],
-                workday_hourly_rate_3=db["workday_hourly_rate_3"],
-                workday_minutes_worked_3=db["workday_minutes_worked_3"],
-                year_goal=db["year_goal"],
-                month_goal=month_goal,
-                week_goal=week_goal,
-                workdays_per_month=db["workdays_per_month"],
-                workday_goal=workday_goal,
-                workdays_per_week=workdays_per_week,
+                workday_hourly_rate_1 = db.workday_hourly_rate_1,
+                workday_minutes_worked_1 = db.workday_minutes_worked_1,
+                workday_hourly_rate_2 = db.workday_hourly_rate_2,
+                workday_minutes_worked_2 = db.workday_minutes_worked_2,
+                workday_hourly_rate_3 = db.workday_hourly_rate_3,
+                workday_minutes_worked_3 = db.workday_minutes_worked_3,
+                year_goal = db.year_goal,
+                month_goal = month_goal,
+                week_goal = week_goal,
+                workdays_per_month = db.workdays_per_month,
+                workday_goal = workday_goal,
+                workdays_per_week = workdays_per_week,
                 )
-        self.send_response(200)
-        self.send_header("Content-type", "text/html")
-        self.end_headers()
-        self.wfile.write(bytes(page, "utf-8"))
+        self.send_HTML(page)
 
-    def fail_on_lockfile(self):
-        if os.path.exists(lock_file):
-            self.send_response(400)
-            self.end_headers()
-            self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
-            return True
-        return False
 
 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.")
+    run_server(server_port, IncomeProgressHandler)