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 <input type="hidden" name="keep_visible" value="0">
28 <td><select name="eatable_key">{eatables_selection}</select></td>
29 <td><input class="unit_count" name="unit_count" type="number" step="1" min="0" value="0" /></td>
34 <tr><th>day</th><th>calories</th><th>sugar (g)</th></tr>
38 <tr><th>title</th><th>calories</th><th>sugar (g)</th><th>standard weight (g)</th><th>comments</th><th>delete</th></tr>
44 <td><input name="title" type="text" value="" /></td>
45 <td><input name="cals" type="number" min="0" step="0.1" value="0" /></td>
46 <td><input name="sugar_g" type="number" min="0" step="0.1" value="0" /></td>
47 <td><input name="standard_g" type="number" min="1" step="0.1" value="1" /></td>
48 <td><input name="comments" type="text" value="" /></td>
55 var unit_count_inputs = document.getElementsByClassName("unit_count");
56 for (let i = 0; i < unit_count_inputs.length; i++) {
57 let input = unit_count_inputs[i];
58 let button = document.createElement('button');
59 button.innerHTML = '+1';
60 button.onclick = function(event) {
61 event.preventDefault();
62 input.value = parseInt(input.value) + 1;
64 input.insertAdjacentElement('afterend', button);
71 class LockFileDetected(Exception):
76 def __init__(self, title, cals, sugar_g, standard_g=100, comments="", popularity=0):
78 self.cals = cals # per 100g
79 self.sugar_g = sugar_g # per 100g
80 self.standard_g = standard_g # common unit weight
81 self.comments = comments
82 self.popularity = popularity
88 "sugar_g": self.sugar_g,
89 "standard_g": self.standard_g,
90 "comments": self.comments,
91 "popularity": self.popularity
96 def __init__(self, eatable_key, unit_count=None, keep_visible=0):
97 self.eatable_key = eatable_key
98 self.unit_count = unit_count
99 self.keep_visible = keep_visible
103 "eatable_key": self.eatable_key,
104 "unit_count": self.unit_count,
105 "keep_visible": self.keep_visible
110 def __init__(self, calories, sugar_g):
111 self.calories = calories
112 self.sugar_g = sugar_g
116 "calories": self.calories,
117 "sugar_g": self.sugar_g,
122 def __init__(self, load_from_file=True):
123 db_name = "calories_db"
124 self.db_file = db_name + ".json"
125 self.lock_file = db_name+ ".lock"
127 self.consumptions = []
129 self.today = Day(0, 0)
131 if load_from_file and os.path.exists(self.db_file):
132 with open(self.db_file, "r") as f:
133 self.from_dict(json.load(f))
135 def from_dict(self, d):
136 self.set_today_date(d["today_date"])
137 for k,v in d["eatables"].items():
138 self.add_eatable(k, Eatable(v["title"], v["cals"], v["sugar_g"], v["standard_g"], v["comments"]))
139 for c in d["consumptions"]:
140 self.add_consumption(Consumption(c["eatable_key"], c["unit_count"]))
141 for k,v in d["days"].items():
142 self.add_day(k, Day(v["calories"], v["sugar_g"]))
145 d = {"eatables": {}, "consumptions": [], "days":{}, "today_date":self.today_date}
146 for k,v in self.eatables.items():
147 d["eatables"][k] = v.to_dict()
148 for c in self.consumptions:
149 d["consumptions"] += [c.to_dict()]
150 for k,v in self.days.items():
151 d["days"][k] = v.to_dict()
154 def calc_consumption(self, c):
155 eatable = self.eatables[c.eatable_key]
156 calories = eatable.cals * c.unit_count
157 sugar_g = eatable.sugar_g * c.unit_count
158 # calories = float(eatable.cals / eatable.standard_g) * c.unit_count * c.unit_weight
159 # sugar_g = float(eatable.sugar_g / eatable.standard_g) * c.unit_count * c.unit_weight
160 self.today.calories += calories
161 self.today.sugar_g += sugar_g
162 return {"cals": calories, "sugar": sugar_g }
164 def eatables_selection(self, selection=None):
166 for k, v in sorted(self.eatables.items(), key=lambda item: item[1].title):
168 selected = ' selected' if k==selection else ''
169 html += '<option value="%s"%s>%s</option>' % (k, selected, v.title)
172 def add_eatable(self, id_, eatable):
173 self.eatables[id_] = eatable
175 def add_consumption(self, consumption):
176 self.consumptions += [consumption]
178 def add_day(self, date, day, archives_today=False):
180 date = date + str(datetime.datetime.now())[10:]
181 self.days[date] = day
183 def set_today_date(self, today_date):
184 self.today_date = today_date
186 def delete(self, id_):
187 del self.eatables[id_]
191 if os.path.exists(self.lock_file):
192 raise LockFileDetected
193 if os.path.exists(self.db_file):
194 shutil.copy(self.db_file, self.db_file + ".bak")
195 f = open(self.lock_file, "w+")
197 with open(self.db_file, "w") as f:
198 json.dump(self.to_dict(), f)
199 os.remove(self.lock_file)
202 class MyServer(BaseHTTPRequestHandler):
205 from uuid import uuid4
206 from urllib.parse import parse_qs
207 length = int(self.headers['content-length'])
208 postvars = parse_qs(self.rfile.read(length).decode(), keep_blank_values=1)
210 def decode(key, i, is_num=True):
212 return float(postvars[key][i])
213 return postvars[key][i]
215 if 'delete' in postvars.keys():
216 for target in postvars['delete']:
217 to_delete += [target]
219 if 'eatable_uuid' in postvars.keys():
220 for uuid_encoded in postvars['eatable_uuid']:
222 if uuid not in to_delete:
223 e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
224 db.add_eatable(uuid, e)
226 if 'title' in postvars.keys() and len(postvars['title'][i]) > 0:
227 e = Eatable(decode("title", i, False), decode("cals", i), decode("sugar_g", i), decode("standard_g", i), decode("comments", i, False))
228 db.add_eatable(str(uuid4()), e)
230 if 'eatable_key' in postvars.keys():
231 for eatable_key in postvars['eatable_key']:
232 c = Consumption(decode("eatable_key", i, False), decode("unit_count", i), decode("keep_visible", i))
234 if c.unit_count == 0 and c.keep_visible == 0:
236 db.add_consumption(c)
238 if 'day_date' in postvars.keys():
239 for date in postvars['day_date']:
240 db.add_day((date), Day(decode("day_cals", i), decode("day_sugar", i)))
242 if 'new_date' in postvars.keys():
243 db.set_today_date(postvars["new_date"][0])
244 if 'archive_day' in postvars.keys():
245 new_cals = postvars["new_day_cals"][0]
246 new_sugar = postvars["new_day_sugar"][0]
247 db.add_day(db.today_date, Day(float(new_cals), float(new_sugar)), archives_today=True)
248 db.set_today_date(str(datetime.datetime.now())[:10])
249 for c in db.consumptions:
251 db.eatables[c.eatable_key].popularity += 1
254 for k, v in sorted(db.eatables.items(), key=lambda item: -item[1].popularity):
255 db.add_consumption(Consumption(k, 0))
257 if (default_slots <= 0):
261 self.send_response(302)
262 self.send_header('Location', '/')
264 except LockFileDetected:
265 self.send_response(400)
267 self.wfile.write(bytes("Sorry, lock file!", "utf-8"))
270 self.send_response(200)
271 self.send_header("Content-type", "text/html")
276 for k,v in db.eatables.items():
278 "<input name=\"eatable_uuid\" type=\"hidden\" value=\"%s\" />"\
279 "<td><input name=\"title\" value=\"%s\" /></td>"\
280 "<td><input name=\"cals\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\
281 "<td><input name=\"sugar_g\" type=\"number\" step=\"0.1\" min=\"0\" value=\"%.1f\" /></td>"\
282 "<td><input name=\"standard_g\" type=\"number\" step=\"0.1\" min=\"1\" value=\"%1.f\" /></td>"\
283 "<td><input name=\"comments\" value=\"%s\" /></td>"\
284 "<td><input name=\"delete\" type=\"checkbox\" value=\"%s\" /></td>"\
285 "</tr>" % (k, v.title, v.cals, v.sugar_g, v.standard_g, v.comments, k)
287 for c in db.consumptions:
288 r = db.calc_consumption(c)
289 consumptions += "<tr />"\
290 "<input type=\"hidden\" name=\"keep_visible\" value=\"1\">"\
291 "<td><select name=\"eatable_key\">%s</select></td>"\
292 "<td><input class=\"unit_count\" name=\"unit_count\" type=\"number\" min=\"0\" value=\"%d\" /></td>"\
296 "</tr>" % (db.eatables_selection(c.eatable_key), c.unit_count, r["cals"], r["sugar"])
298 for date, day in db.days.items():
300 "<td><input name=\"day_date\" type=\"hidden\" value=\"%s\" />%s</td>"\
301 "<td><input name=\"day_cals\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\
302 "<td><input name=\"day_sugar\" type=\"hidden\" step=\"0.1\" min=\"0\" value=\"%.1f\" />%.1f</td>"\
303 "</tr>" % (date, date[:10], day.calories, day.calories, day.sugar_g, day.sugar_g)
305 "<th>today:</th><th></th><th></th><th>archive?</th>"\
308 "<td><input name=\"new_date\" size=8 value=\"%s\" /></td>"\
309 "<td><input name=\"new_day_cals\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\
310 "<td><input name=\"new_day_sugar\" type=\"hidden\" value=\"%.1f\" readonly />%.1f</td>"\
311 "<td><input name=\"archive_day\" type=\"checkbox\" /></td>"\
312 "</tr>" % (db.today_date, db.today.calories, db.today.calories, db.today.sugar_g, db.today.sugar_g)
313 page = build_page(eatables, consumptions, db.eatables_selection(), day_rows)
314 self.wfile.write(bytes(page, "utf-8"))
317 hostName = "localhost"
319 if __name__ == "__main__":
320 webServer = HTTPServer((hostName, serverPort), MyServer)
321 print("Server started http://%s:%s" % (hostName, serverPort))
323 webServer.serve_forever()
324 except KeyboardInterrupt:
326 webServer.server_close()
327 print("Server stopped.")