1 from http.server import BaseHTTPRequestHandler, HTTPServer
6 def build_page(eatable_rows, consumption_rows, eatables_selection, day_rows):
18 <form action="/" method="POST">
19 <td><input name="update" type="submit" value="update" /></td>
21 <tr><th>eatable</th><th>unit count</th><th>unit weight (g)</th><th>calories</th><th>sugar (g)</th></tr>
27 <td><select name="eatable_key">{eatables_selection}</select></td>
28 <td><input class="unit_count" name="unit_count" type="number" step="1" min="0" value="0" /></td>
33 <tr><th>day</th><th>calories</th><th>sugar (g)</th></tr>
37 <tr><th>title</th><th>calories</th><th>sugar (g)</th><th>standard weight (g)</th><th>comments</th><th>delete</th></tr>
43 <td><input name="title" type="text" value="" /></td>
44 <td><input name="cals" type="number" min="0" step="0.1" value="0" /></td>
45 <td><input name="sugar_g" type="number" min="0" step="0.1" value="0" /></td>
46 <td><input name="standard_g" type="number" min="1" step="0.1" value="1" /></td>
47 <td><input name="comments" type="text" value="" /></td>
54 var unit_count_inputs = document.getElementsByClassName("unit_count");
55 for (let i = 0; i < unit_count_inputs.length; i++) {
56 let input = unit_count_inputs[i];
57 let button = document.createElement('button');
58 button.innerHTML = '+1';
59 button.onclick = function(event) {
60 event.preventDefault();
61 input.value = parseInt(input.value) + 1;
63 input.insertAdjacentElement('afterend', button);
70 class LockFileDetected(Exception):
75 def __init__(self, title, cals, sugar_g, standard_g=100, comments=""):
77 self.cals = cals # per 100g
78 self.sugar_g = sugar_g # per 100g
79 self.standard_g = standard_g # common unit weight
80 self.comments = comments
86 "sugar_g": self.sugar_g,
87 "standard_g": self.standard_g,
88 "comments": self.comments
93 def __init__(self, eatable_key, unit_count=None):
94 self.eatable_key = eatable_key
95 self.unit_count = unit_count
99 "eatable_key": self.eatable_key,
100 "unit_count": self.unit_count,
105 def __init__(self, calories, sugar_g):
106 self.calories = calories
107 self.sugar_g = sugar_g
111 "calories": self.calories,
112 "sugar_g": self.sugar_g,
117 def __init__(self, load_from_file=True):
118 db_name = "calories_db"
119 self.db_file = db_name + ".json"
120 self.lock_file = db_name+ ".lock"
122 self.consumptions = []
124 self.today = Day(0, 0)
126 if load_from_file and os.path.exists(self.db_file):
127 with open(self.db_file, "r") as f:
128 self.from_dict(json.load(f))
130 def from_dict(self, d):
131 self.set_today_date(d["today_date"])
132 for k,v in d["eatables"].items():
133 self.add_eatable(k, Eatable(v["title"], v["cals"], v["sugar_g"], v["standard_g"], v["comments"]))
134 for c in d["consumptions"]:
135 self.add_consumption(Consumption(c["eatable_key"], c["unit_count"]))
136 for k,v in d["days"].items():
137 self.add_day(k, Day(v["calories"], v["sugar_g"]))
140 d = {"eatables": {}, "consumptions": [], "days":{}, "today_date":self.today_date}
141 for k,v in self.eatables.items():
142 d["eatables"][k] = v.to_dict()
143 for c in self.consumptions:
144 d["consumptions"] += [c.to_dict()]
145 for k,v in self.days.items():
146 d["days"][k] = v.to_dict()
149 def calc_consumption(self, c):
150 eatable = self.eatables[c.eatable_key]
151 calories = eatable.cals * c.unit_count
152 sugar_g = eatable.sugar_g * c.unit_count
153 # calories = float(eatable.cals / eatable.standard_g) * c.unit_count * c.unit_weight
154 # sugar_g = float(eatable.sugar_g / eatable.standard_g) * c.unit_count * c.unit_weight
155 self.today.calories += calories
156 self.today.sugar_g += sugar_g
157 return {"cals": calories, "sugar": sugar_g }
159 def eatables_selection(self, selection=None):
161 for k, v in sorted(self.eatables.items(), key=lambda item: item[1].title):
163 selected = ' selected' if k==selection else ''
164 html += '<option value="%s"%s>%s</option>' % (k, selected, v.title)
167 def add_eatable(self, id_, eatable):
168 self.eatables[id_] = eatable
170 def add_consumption(self, consumption):
171 self.consumptions += [consumption]
173 def add_day(self, date, day, archives_today=False):
175 date = date + str(datetime.datetime.now())[10:]
176 self.days[date] = day
178 def set_today_date(self, today_date):
179 self.today_date = today_date
181 def delete(self, id_):
182 del self.eatables[id_]
186 if os.path.exists(self.lock_file):
187 raise LockFileDetected
188 if os.path.exists(self.db_file):
189 shutil.copy(self.db_file, self.db_file + ".bak")
190 f = open(self.lock_file, "w+")
192 with open(self.db_file, "w") as f:
193 json.dump(self.to_dict(), f)
194 os.remove(self.lock_file)
197 class MyServer(BaseHTTPRequestHandler):
200 from uuid import uuid4
201 from urllib.parse import parse_qs
202 length = int(self.headers['content-length'])
203 postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)
205 def decode(key, i, is_num=True):
207 return float(postvars[key][i])
208 return postvars[key][i]
210 if 'delete' in postvars.keys():
211 for target in postvars['delete']:
212 to_delete += [target]
214 if 'eatable_uuid' in postvars.keys():
215 for uuid_encoded in postvars['eatable_uuid']:
217 if uuid not in to_delete:
218 e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
219 db.add_eatable(uuid, e)
221 if 'title' in postvars.keys() and len(postvars['title'][i]) > 0:
222 e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
223 db.add_eatable(str(uuid4()), e)
225 if 'eatable_key' in postvars.keys():
226 for eatable_key in postvars['eatable_key']:
227 c = Consumption(decode("eatable_key", i, False), decode("unit_count", i))
229 if c.unit_count == 0:
231 db.add_consumption(c)
233 if 'day_date' in postvars.keys():
234 for date in postvars['day_date']:
235 db.add_day((date), Day(decode("day_cals", i), decode("day_sugar", i)))
237 if 'new_date' in postvars.keys():
238 db.set_today_date(postvars["new_date"][0])
239 if 'archive_day' in postvars.keys():
240 new_cals = postvars["new_day_cals"][0]
241 new_sugar = postvars["new_day_sugar"][0]
242 db.add_day(db.today_date, Day(float(new_cals), float(new_sugar)), archives_today=True)
243 db.set_today_date(str(datetime.datetime.now())[:10])
247 self.send_response(302)
248 self.send_header('Location', '/')
250 except LockFileDetected:
251 self.send_response(400)
253 self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
256 self.send_response(200)
257 self.send_header("Content-type", "text/html")
262 for k,v in db.eatables.items():
264 "<input name=\"eatable_uuid\" type=\"hidden\" value=\"%s\" />"\
265 "<td><input name=\"title\" value=\"%s\" /></td>"\
266 "<td><input name=\"cals\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\
267 "<td><input name=\"sugar_g\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\
268 "<td><input name=\"standard_g\" type=\"number\" step=\"0.1\" min=\"1\" value=\"%1.f\" /></td>"\
269 "<td><input name=\"comments\" value=\"%s\" /></td>"\
270 "<td><input name=\"delete\" type=\"checkbox\" value=\"%s\" /></td>"\
271 "</tr>" % (k, v.title, v.cals, v.sugar_g, v.standard_g, v.comments, k)
273 for c in db.consumptions:
274 r = db.calc_consumption(c)
275 consumptions += "<tr />"\
276 "<td><select name=\"eatable_key\">%s</select></td>"\
277 "<td><input class=\"unit_count\" name=\"unit_count\" type=\"number\" min=\"0\" value=\"%d\" /></td>"\
281 "</tr>" % (db.eatables_selection(c.eatable_key), c.unit_count, r["cals"], r["sugar"])
283 for date, day in db.days.items():
285 "<td><input name=\"day_date\" type=\"hidden\" value=\"%s\" />%s</td>"\
286 "<td><input name=\"day_cals\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\
287 "<td><input name=\"day_sugar\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\
288 "</tr>" % (date, date[:10], day.calories, day.calories, day.sugar_g, day.sugar_g)
290 "<th>today:</th><th></th><th></th><th>archive?</th>"\
293 "<td><input name=\"new_date\" size=8 value=\"%s\" /></td>"\
294 "<td><input name=\"new_day_cals\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\
295 "<td><input name=\"new_day_sugar\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\
296 "<td><input name=\"archive_day\" type=\"checkbox\" /></td>"\
297 "</tr>" % (db.today_date, db.today.calories, db.today.calories, db.today.sugar_g, db.today.sugar_g)
298 page = build_page(eatables, consumptions, db.eatables_selection(), day_rows)
299 self.wfile.write(bytes(page, "utf-8"))
302 hostName = "localhost"
304 if __name__ == "__main__":
305 webServer = HTTPServer((hostName, serverPort), MyServer)
306 print("Server started http://%s:%s" % (hostName, serverPort))
308 webServer.serve_forever()
309 except KeyboardInterrupt:
311 webServer.server_close()
312 print("Server stopped.")