serve.py
1#!/usr/bin/env python3
2
3import sqlite3
4import base64
5from http.server import BaseHTTPRequestHandler, HTTPServer
6from sqlite3 import Error
7from urllib.parse import urlparse, parse_qs
8
9hostName = "localhost"
10serverPort = 8080
11db_file = "npo.db"
12html_title = "Nationella Prov"
13html_body = """
14<html>
15 <head>
16 <title>{title}</title>
17 <style>{style}</style>
18 </head>
19 <body>
20 {content}
21 </body>
22</html>
23"""
24html_default = """
25<div id="head">
26 <div>
27 <form action="/" method="get">
28 <label for="course">Kurs:</label>
29 <select name="course" id="course" onchange="this.form.submit();">
30 <option value="">alla</option>
31 {courses}
32 </select>
33 <label for="semester">Termin:</label>
34 <select name="semester" id="semester" onchange="this.form.submit();">
35 <option value="">alla</option>
36 {semesters}
37 </select>
38 {tags}
39 </form>
40 </div>
41</div>
42<div id="main">
43 <div>
44 {tasks}
45 </div>
46</div>
47"""
48html_default_task = """
49<div class="task">
50 <a href="{link}">
51 <span class="task-head">
52 <h1>{course}: {semester}</h1>
53 <h2>{tags}</h2>
54 </span>
55 <span class="image">
56 <img src="{image}"/>
57 </span>
58 </a>
59</div>
60"""
61html_image = """
62<div class="image">
63 <img src="{image}"/>
64</div>
65"""
66html_task = """
67<div id="head" class="task">
68 <span class="task-head">
69 <h1>{course}: {semester}</h1>
70 <h2>{tags}</h2>
71 </span>
72</div>
73<div id="main">
74 {images}
75</div>
76"""
77css = """
78body {
79 background-color : #d8dee9;
80 margin : 0;
81}
82#head {
83 background-color : #2e3440;
84 line-height : 2em;
85}
86#head>div * {
87 vertical-align : middle;
88}
89#head>div {
90 background-color : #2e3440;
91 color : #ffffff;
92 min-width : 640px;
93 width : 80%;
94 margin : 0 auto;
95 padding : 10px 0px 5px;
96}
97#head label {
98 margin-right : 10px;
99}
100#head select {
101 margin-right : 20px;
102}
103#main {
104 min-width : 640px;
105 width : 80%;
106 margin : 0 auto;
107 padding : 20px;
108}
109#main .image {
110 display : block;
111 background-color : #ffffff;
112 padding : 10px;
113 margin-bottom : 20px;
114}
115.task .task-head {
116 display : block;
117 background-color : #2e3440;
118 color : #ffffff;
119 padding : 1px 20px;
120}
121.task .task-head h1 {
122 font-size : 1.5em;
123}
124.task .task-head h2 {
125 font-size : 1em;
126}
127.task a {
128 text-decoration : none;
129}
130.task a:hover .task-head {
131 background-color : #3b4252;
132}
133.tag {
134 display : inline;
135 white-space : nowrap;
136}
137img {
138 max-width : 100%;
139}
140"""
141
142class Task:
143 def __init__(self, row):
144 self.id = row[0]
145 self.course = row[1]
146 self.semester = row[2]
147 self.number = row[3]
148 self.tags = []
149 self.images = []
150
151 def append_tag(self, tag):
152 self.tags.append(tag)
153
154 def append_image(self, image):
155 self.images.append(image)
156
157 def load(taskid):
158 return DataManager.select_task(taskid)
159
160 def all(course, semester):
161 return list(DataManager.select_all_tasks(course, semester))
162
163
164class DataManager:
165 def select_all_tags(course, semester):
166 connection = None
167 try:
168 connection = sqlite3.connect(db_file)
169 cursor = connection.cursor()
170 cursor.execute("""
171 SELECT DISTINCT tagname
172 FROM tag
173 INNER JOIN tasktag, task
174 ON tasktag.tagid=tag.id
175 AND tasktag.taskid=task.id
176 {where}
177 ORDER BY tagname
178 """.format(where = ""
179 if (not course and not semester)
180 else "WHERE course=?" if (course and not semester)
181 else "WHERE semester=?" if (semester and not course)
182 else "WHERE course=? AND semester=?"
183 ), () if (not course and not semester)
184 else (course, ) if (course and not semester)
185 else (semester, ) if (semester and not course)
186 else (course, semester, ))
187 for row in cursor.fetchall():
188 yield row[0]
189 cursor.close()
190 except sqlite3.Error as e:
191 print("Failed to read data:", e)
192 finally:
193 if connection:
194 connection.close()
195
196 def select_all_courses(semester):
197 connection = None
198 try:
199 connection = sqlite3.connect(db_file)
200 cursor = connection.cursor()
201 cursor.execute("""
202 SELECT DISTINCT course
203 FROM task
204 {where}
205 ORDER BY course
206 """.format(where = "WHERE semester=?" if semester else ""),
207 (semester, ) if semester else ())
208 for row in cursor.fetchall():
209 yield row[0]
210 cursor.close()
211 except sqlite3.Error as e:
212 print("Failed to read data:", e)
213 finally:
214 if connection:
215 connection.close()
216
217 def select_all_semesters(course):
218 connection = None
219 try:
220 connection = sqlite3.connect(db_file)
221 cursor = connection.cursor()
222 cursor.execute("""
223 SELECT DISTINCT semester
224 FROM task
225 {where}
226 ORDER BY semester
227 """.format(where = "WHERE course=?" if course else ""),
228 (course, ) if course else ())
229 for row in cursor.fetchall():
230 yield row[0]
231 cursor.close()
232 except sqlite3.Error as e:
233 print("Failed to read data:", e)
234 finally:
235 if connection:
236 connection.close()
237
238 def select_task_data(row, connection):
239 task = Task(row)
240
241 try:
242 cursor = connection.cursor()
243 cursor.execute("""
244 SELECT tagname
245 FROM tag
246 INNER JOIN tasktag ON tasktag.tagid=tag.id
247 WHERE tasktag.taskid=?
248 ORDER BY tagname
249 """, (task.id, ))
250 for row in cursor.fetchall():
251 task.append_tag(row[0])
252 cursor.close()
253
254 cursor = connection.cursor()
255 cursor.execute("""
256 SELECT *
257 FROM image
258 WHERE taskid=?
259 ORDER BY idx
260 """, (task.id, ))
261 for row in cursor.fetchall():
262 task.append_image(row[0])
263 cursor.close()
264 except sqlite3.Error as e:
265 print("Failed to read data:", e)
266
267 return task
268
269 def select_task(taskid):
270 connection = None
271 task = None
272 try:
273 connection = sqlite3.connect(db_file)
274 cursor = connection.cursor()
275 cursor.execute("""
276 SELECT *
277 FROM task
278 WHERE id=?
279 """, (taskid, ))
280 row = cursor.fetchone()
281 task = DataManager.select_task_data(row, connection) if row else None
282 cursor.close()
283 except sqlite3.Error as e:
284 print("Failed to read data:", e)
285 finally:
286 if connection:
287 connection.close()
288 return task
289
290 def select_all_tasks(course, semester):
291 connection = None
292 try:
293 connection = sqlite3.connect(db_file)
294 cursor = connection.cursor()
295 cursor.execute("""
296 SELECT *
297 FROM task
298 {where}
299 """.format(where = ""
300 if (not course and not semester)
301 else "WHERE course=?" if (course and not semester)
302 else "WHERE semester=?" if (semester and not course)
303 else "WHERE course=? AND semester=?"
304 ), () if (not course and not semester)
305 else (course, ) if (course and not semester)
306 else (semester, ) if (semester and not course)
307 else (course, semester, ))
308 for row in cursor.fetchall():
309 yield DataManager.select_task_data(row, connection)
310 cursor.close()
311
312 except sqlite3.Error as e:
313 print("Failed to read data:", e)
314 finally:
315 if connection:
316 connection.close()
317
318 def select_image(imageid):
319 connection = None
320 image = None
321 try:
322 connection = sqlite3.connect(db_file)
323 cursor = connection.cursor()
324 cursor.execute("""
325 SELECT image
326 FROM image
327 WHERE id = ?
328 """, (imageid, ))
329 row = cursor.fetchone()
330 image = row[0] if row else None
331 cursor.close()
332
333 except sqlite3.Error as e:
334 print("Failed to read data:", e)
335 finally:
336 if connection:
337 connection.close()
338 return image
339
340
341class WebServer(BaseHTTPRequestHandler):
342 def do_HEADERS(self):
343 self.send_response(200)
344 if self.path.startswith("/image"):
345 self.send_header("Content-type", "image/png")
346 else:
347 self.send_header("Content-type", "text/html")
348 self.end_headers()
349
350 def do_GET(self):
351 self.do_HEADERS()
352 self.wfile.write(bytes(html_body.format(
353 title = html_title,
354 style = css,
355 content = self.task_page() if self.path.startswith("/task")
356 else self.render_image() if self.path.startswith("/image")
357 else self.default_page()
358 ), "utf-8"))
359
360 def default_page_courses(self, qs, courses):
361 html = ""
362 for course in courses:
363 html += """
364 <option {selected} value="{course}">{course}</option>
365 """.format(
366 selected = "selected=\"selected\"" if qs["course"] == course else "",
367 course = course
368 )
369 return html
370
371 def default_page_semesters(self, qs, semesters):
372 html = ""
373 for semester in semesters:
374 html += """
375 <option {selected} value="{semester}">{semester}</option>
376 """.format(
377 selected = "selected=\"selected\"" if qs["semester"] == semester else "",
378 semester = semester
379 )
380 return html
381
382 def default_page_tags(self, qs, tags):
383 html = ""
384 for tag in tags:
385 html += """
386 <div class="tag">
387 <input onchange="this.form.submit();" {checked} name="tag" type="checkbox" id="tag_{tag}" value="{tag}" />
388 <label for="tag_{tag}">{tag}</label>
389 </div>
390 """.format(
391 checked = "checked=\"checked\"" if tag in qs["tags"] else "",
392 tag = tag
393 )
394 return html
395
396 def default_page_tasks(self, tasks):
397 html = ""
398 for task in tasks:
399 html += html_default_task.format(
400 course = task.course,
401 semester = task.semester,
402 tags = ", ".join(task.tags),
403 link = "/task?id={taskid}".format(taskid = task.id),
404 image = "/image?id={imageid}".format(imageid = task.images[0])
405 )
406 return html
407
408 def default_page(self):
409 qs_raw = parse_qs(urlparse(self.path).query)
410 qs = {
411 "course" : qs_raw["course"][0] if "course" in qs_raw.keys() else "",
412 "semester" : qs_raw["semester"][0] if "semester" in qs_raw.keys() else "",
413 }
414 courses = list(DataManager.select_all_courses(qs["semester"]))
415 semesters = list(DataManager.select_all_semesters(qs["course"]))
416 tags = list(DataManager.select_all_tags(qs["course"], qs["semester"]))
417 qs["tags"] = set(tags) & set(qs_raw["tag"] if "tag" in qs_raw.keys() else "")
418 tasks = (task
419 for task in Task.all(qs["course"], qs["semester"])
420 if len(qs["tags"]) == 0
421 or len(set(task.tags) & set(qs["tags"])) == len(qs["tags"]))
422
423 return html_default.format(
424 courses = self.default_page_courses(qs, courses),
425 semesters = self.default_page_semesters(qs, semesters),
426 tags = self.default_page_tags(qs, tags),
427 tasks = self.default_page_tasks(tasks)
428 )
429
430 def task_page(self):
431 qs_raw = parse_qs(urlparse(self.path).query)
432 task = Task.load(qs_raw["id"][0] if "id" in qs_raw.keys() else "")
433
434 return html_task.format(
435 course = task.course,
436 semester = task.semester,
437 tags = ", ".join(task.tags),
438 images = "".join(html_image.format(
439 image = "/image?id=%s" % image
440 ) for image in task.images)
441 ) if task else "Error: task does not exist"
442
443 def render_image(self):
444 qs_raw = parse_qs(urlparse(self.path).query)
445 image = DataManager.select_image(qs_raw["id"][0] if "id" in qs_raw.keys() else "")
446
447 self.wfile.write(image if image else b"")
448
449
450if __name__ == "__main__":
451 webServer = HTTPServer((hostName, serverPort), WebServer)
452 print("Server started at http://%s:%s" % (hostName, serverPort))
453
454 try:
455 webServer.serve_forever()
456 except KeyboardInterrupt:
457 pass
458
459 webServer.server_close()
460 print("Server stopped.")