-from http.server import BaseHTTPRequestHandler, HTTPServer
import os
import json
import datetime
+import jinja2
+from plomlib import PlomDB, PlomException, run_server, PlomHandler
-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">
+<form action="{{homepage}}" 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><select name="eatable_key">{eatables_selection}</select></td>
<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">{% 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>
</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];
}
</script>
-</html>
"""
-class LockFileDetected(Exception):
- pass
class Eatable:
"popularity": self.popularity
}
+
class Consumption:
def __init__(self, eatable_key, unit_count=None, keep_visible=0):
"keep_visible": self.keep_visible
}
+
class Day:
def __init__(self, calories, sugar_g):
"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"])
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
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 ConsumptionsHandler(PlomHandler):
-class MyServer(BaseHTTPRequestHandler):
+ def app_init(self, handler):
+ default_path = '/consumptions'
+ handler.add_route('GET', default_path, self.show_db)
+ handler.add_route('POST', default_path, self.write_db)
+ return 'consumptions', default_path
def do_POST(self):
+ self.try_do(self.write_db)
+
+ def write_db(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])
to_delete += [target]
i = 0
if 'eatable_uuid' in postvars.keys():
- for uuid_encoded in postvars['eatable_uuid']:
- uuid = uuid_encoded
+ for uuid in postvars['eatable_uuid']:
if uuid not in to_delete:
e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
db.add_eatable(uuid, e)
default_slots -= 1
if (default_slots <= 0):
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"))
+ db.write()
+ homepage = self.apps['consumptions'] if hasattr(self, 'apps') else self.homepage
+ self.redirect(homepage)
def do_GET(self):
- self.send_response(200)
- self.send_header("Content-type", "text/html")
- self.end_headers()
- db = Database()
+ self.try_do(self.show_db)
- eatables = ""
+ def show_db(self):
+ db = CaloriesDB()
+ 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>%s</td>"\
- "<td class\"number\"><input class=\"unit_count number\" name=\"unit_count\" type=\"number\" min=\"0\" step=\"0.1\" value=\"%.1f\" /></td>"\
- "<td></td>"\
- "<td class=\"number\">%.1f</td>"\
- "<td class=\"number\">%.1f</td>"\
- "</tr>" % (c.eatable_key, db.eatables[c.eatable_key].title, c.unit_count, 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}',
+ }]
+ homepage = self.apps['consumptions'] if hasattr(self, 'apps') else self.homepage
+ page = jinja2.Template(tmpl).render(
+ homepage = homepage,
+ 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, ConsumptionsHandler)