commit: 3417e539dfa27b6462f986960d2a853ef5cd3ae7
parent: ea99c4abc110d7ca06f7e40501c3ac3ba7bab942
author: Chris Noxz <chris@noxz.tech>
date: Mon, 4 Dec 2023 13:57:24 +0100
create web server
1 file changed, 416 insertions(+)
diff --git a/serve.py b/serve.py
@@ -0,0 +1,416 @@
+#!/usr/bin/env python3
+
+import sqlite3
+import base64
+from http.server import BaseHTTPRequestHandler, HTTPServer
+from sqlite3 import Error
+from urllib.parse import urlparse, parse_qs
+
+hostName = "localhost"
+serverPort = 8080
+db_file = "npo.db"
+html_title = "Nationella Prov"
+html_body = """
+<html>
+ <head>
+ <title>{title}</title>
+ <style>{style}</style>
+ </head>
+ <body>
+ {content}
+ </body>
+</html>
+"""
+html_default = """
+<div id="head">
+ <div>
+ <form action="/" method="get">
+ <label for="course">Kurs:</label>
+ <select name="course" id="course" onchange="this.form.submit();">
+ <option value="">alla</option>
+ {courses}
+ </select>
+ <label for="semester">Termin:</label>
+ <select name="semester" id="semester" onchange="this.form.submit();">
+ <option value="">alla</option>
+ {semesters}
+ </select>
+ {tags}
+ </form>
+ </div>
+</div>
+<div id="main">
+ <div>
+ {tasks}
+ </div>
+</div>
+"""
+html_default_task = """
+<div class="task">
+ <div class="task-head">
+ <h1>{course}: {semester}</h1>
+ <h2>{tags}</h2>
+ </div>
+ <a href="{link}">
+ <img src="data:image/png;base64,{image}"/>
+ </a>
+</div>
+"""
+html_task = """
+<div id="head" class="task">
+ <div class="task-head">
+ <h1>{course}: {semester}</h1>
+ <h2>{tags}</h2>
+ </div>
+</div>
+<div id="main">
+ {images}
+</div>
+"""
+css = """
+body {
+ background-color : #d8dee9;
+ margin : 0;
+}
+#head {
+ background-color : #2e3440;
+ line-height : 2em;
+}
+#head>div * {
+ vertical-align : middle;
+}
+#head>div {
+ background-color : #2e3440;
+ color : #ffffff;
+ min-width : 640px;
+ width : 80%;
+ margin : 0 auto;
+ padding : 10px 0px 5px;
+}
+#head label {
+ margin-right : 10px;
+}
+#head select {
+ margin-right : 20px;
+}
+#main {
+ min-width : 640px;
+ width : 80%;
+ margin : 0 auto;
+ padding : 20px;
+}
+#main .task, #main .image {
+ background-color : #ffffff;
+ padding : 10px;
+ margin-bottom : 20px;
+}
+#main .task .task-head {
+ background-color : #2e3440;
+ color : #ffffff;
+ padding : 1px 20px;
+}
+#main .task .task-head h1 {
+ font-size : 1.5em;
+}
+#main .task .task-head h2 {
+ font-size : 1em;
+}
+.tag {
+ display : inline;
+ white-space : nowrap;
+}
+img {
+ max-width : 100%
+}
+"""
+
+class Task:
+ def __init__(self, row):
+ self.id = row[0]
+ self.course = row[1]
+ self.semester = row[2]
+ self.number = row[3]
+ self.tags = []
+ self.images = []
+
+ def append_tag(self, tag):
+ self.tags.append(tag)
+
+ def append_image(self, image):
+ self.images.append(image)
+
+ def load(taskid):
+ return DataManager.select_task(taskid)
+
+ def all(course, semester):
+ return list(DataManager.select_all_tasks(course, semester))
+
+
+class DataManager:
+ def select_all_tags(course, semester):
+ connection = None
+ try:
+ connection = sqlite3.connect(db_file)
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT DISTINCT tagname
+ FROM tag
+ INNER JOIN tasktag, task
+ ON tasktag.tagid=tag.id
+ AND tasktag.taskid=task.id
+ {where}
+ ORDER BY tagname
+ """.format(where = ""
+ if (not course and not semester)
+ else "WHERE course=?" if (course and not semester)
+ else "WHERE semester=?" if (semester and not course)
+ else "WHERE course=? AND semester=?"
+ ), () if (not course and not semester)
+ else (course, ) if (course and not semester)
+ else (semester, ) if (semester and not course)
+ else (course, semester, ))
+ for row in cursor.fetchall():
+ yield row[0]
+ cursor.close()
+ except sqlite3.Error as e:
+ print("Failed to read data:", e)
+ finally:
+ if connection:
+ connection.close()
+
+ def select_all_courses(semester):
+ connection = None
+ try:
+ connection = sqlite3.connect(db_file)
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT DISTINCT course
+ FROM task
+ {where}
+ ORDER BY course
+ """.format(where = "WHERE semester=?" if semester else ""),
+ (semester, ) if semester else ())
+ for row in cursor.fetchall():
+ yield row[0]
+ cursor.close()
+ except sqlite3.Error as e:
+ print("Failed to read data:", e)
+ finally:
+ if connection:
+ connection.close()
+
+ def select_all_semesters(course):
+ connection = None
+ try:
+ connection = sqlite3.connect(db_file)
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT DISTINCT semester
+ FROM task
+ {where}
+ ORDER BY semester
+ """.format(where = "WHERE course=?" if course else ""),
+ (course, ) if course else ())
+ for row in cursor.fetchall():
+ yield row[0]
+ cursor.close()
+ except sqlite3.Error as e:
+ print("Failed to read data:", e)
+ finally:
+ if connection:
+ connection.close()
+
+ def select_task_data(row, connection):
+ task = Task(row)
+
+ try:
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT tagname
+ FROM tag
+ INNER JOIN tasktag ON tasktag.tagid=tag.id
+ WHERE tasktag.taskid=?
+ ORDER BY tagname
+ """, (task.id, ))
+ for row in cursor.fetchall():
+ task.append_tag(row[0])
+ cursor.close()
+
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT *
+ FROM image
+ WHERE taskid=?
+ ORDER BY idx
+ """, (task.id, ))
+ for row in cursor.fetchall():
+ task.append_image(row[3])
+ cursor.close()
+ except sqlite3.Error as e:
+ print("Failed to read data:", e)
+
+ return task
+
+ def select_task(taskid):
+ connection = None
+ task = None
+ try:
+ connection = sqlite3.connect(db_file)
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT *
+ FROM task
+ WHERE id=?
+ """, (taskid, ))
+ row = cursor.fetchone()
+ task = DataManager.select_task_data(row, connection) if row else None
+ cursor.close()
+ except sqlite3.Error as e:
+ print("Failed to read data:", e)
+ finally:
+ if connection:
+ connection.close()
+ return task
+
+ def select_all_tasks(course, semester):
+ connection = None
+ try:
+ connection = sqlite3.connect(db_file)
+ cursor = connection.cursor()
+ cursor.execute("""
+ SELECT *
+ FROM task
+ {where}
+ """.format(where = ""
+ if (not course and not semester)
+ else "WHERE course=?" if (course and not semester)
+ else "WHERE semester=?" if (semester and not course)
+ else "WHERE course=? AND semester=?"
+ ), () if (not course and not semester)
+ else (course, ) if (course and not semester)
+ else (semester, ) if (semester and not course)
+ else (course, semester, ))
+ for row in cursor.fetchall():
+ yield DataManager.select_task_data(row, connection)
+ cursor.close()
+
+ except sqlite3.Error as e:
+ print("Failed to read data:", e)
+ finally:
+ if connection:
+ connection.close()
+
+
+class WebServer(BaseHTTPRequestHandler):
+ def do_HEADERS(self):
+ self.send_response(200)
+ self.send_header("Content-type", "text/html")
+ self.end_headers()
+
+ def do_GET(self):
+ self.do_HEADERS()
+ self.wfile.write(bytes(html_body.format(
+ title = html_title,
+ style = css,
+ content = self.task_page()
+ if self.path.startswith("/task")
+ else self.default_page()
+ ), "utf-8"))
+
+ def default_page_courses(self, qs, courses):
+ html = ""
+ for course in courses:
+ html += """
+ <option {selected} value="{course}">{course}</option>
+ """.format(
+ selected = "selected=\"selected\"" if qs["course"] == course else "",
+ course = course
+ )
+ return html
+
+ def default_page_semesters(self, qs, semesters):
+ html = ""
+ for semester in semesters:
+ html += """
+ <option {selected} value="{semester}">{semester}</option>
+ """.format(
+ selected = "selected=\"selected\"" if qs["semester"] == semester else "",
+ semester = semester
+ )
+ return html
+
+ def default_page_tags(self, qs, tags):
+ html = ""
+ for tag in tags:
+ html += """
+ <div class="tag">
+ <input onchange="this.form.submit();" {checked} name="tag" type="checkbox" id="tag_{tag}" value="{tag}" />
+ <label for="tag_{tag}">{tag}</label>
+ </div>
+ """.format(
+ checked = "checked=\"checked\"" if tag in qs["tags"] else "",
+ tag = tag
+ )
+ return html
+
+ def default_page_tasks(self, tasks):
+ html = ""
+ for task in tasks:
+ html += html_default_task.format(
+ course = task.course,
+ semester = task.semester,
+ tags = ", ".join(task.tags),
+ link = "/task?id={taskid}".format(taskid = task.id),
+ image = base64.b64encode(task.images[0]).decode("ascii")
+ )
+ return html
+
+ def default_page(self):
+ qs_raw = parse_qs(urlparse(self.path).query)
+ qs = {
+ "course" : qs_raw["course"][0] if "course" in qs_raw.keys() else "",
+ "semester" : qs_raw["semester"][0] if "semester" in qs_raw.keys() else "",
+ }
+ courses = list(DataManager.select_all_courses(qs["semester"]))
+ semesters = list(DataManager.select_all_semesters(qs["course"]))
+ tags = list(DataManager.select_all_tags(qs["course"], qs["semester"]))
+ qs["tags"] = set(tags) & set(qs_raw["tag"] if "tag" in qs_raw.keys() else "")
+ tasks = (task
+ for task in Task.all(qs["course"], qs["semester"])
+ if len(qs["tags"]) == 0
+ or len(set(task.tags) & set(qs["tags"])) == len(qs["tags"]))
+
+ return html_default.format(
+ courses = self.default_page_courses(qs, courses),
+ semesters = self.default_page_semesters(qs, semesters),
+ tags = self.default_page_tags(qs, tags),
+ tasks = self.default_page_tasks(tasks)
+ )
+
+ def task_page(self):
+ qs_raw = parse_qs(urlparse(self.path).query)
+ task = Task.load(qs_raw["id"][0] if "id" in qs_raw.keys() else "")
+
+ return html_task.format(
+ course = task.course,
+ semester = task.semester,
+ tags = ", ".join(task.tags),
+ images = "".join("""
+ <div class="image">
+ <img src="data:image/png;base64,%s"/>
+ </div>
+ """ % base64.b64encode(image).decode("ascii") for image in task.images)
+ ) if task else "Error: task does not exist"
+
+
+if __name__ == "__main__":
+ webServer = HTTPServer((hostName, serverPort), WebServer)
+ print("Server started at http://%s:%s" % (hostName, serverPort))
+
+ try:
+ webServer.serve_forever()
+ except KeyboardInterrupt:
+ pass
+
+ webServer.server_close()
+ print("Server stopped.")