1 """Web server stuff."""
3 from http.server import BaseHTTPRequestHandler
4 from http.server import HTTPServer
5 from urllib.parse import urlparse, parse_qs
6 from os.path import split as path_split
7 from jinja2 import Environment as JinjaEnv, FileSystemLoader as JinjaFSLoader
8 from plomtask.days import Day, todays_date
9 from plomtask.misc import HandledException
10 from plomtask.db import DatabaseConnection, DatabaseFile
11 from plomtask.processes import Process
13 TEMPLATES_DIR = 'templates'
16 class TaskServer(HTTPServer):
17 """Variant of HTTPServer that knows .jinja as Jinja Environment."""
19 def __init__(self, db_file: DatabaseFile,
20 *args: Any, **kwargs: Any) -> None:
21 super().__init__(*args, **kwargs)
23 self.jinja = JinjaEnv(loader=JinjaFSLoader(TEMPLATES_DIR))
26 class TaskHandler(BaseHTTPRequestHandler):
27 """Handles single HTTP request."""
30 def do_GET(self) -> None:
31 """Handle any GET request."""
33 conn, site, params = self._init_handling()
34 if 'calendar' == site:
35 start = params.get('start', [''])[0]
36 end = params.get('end', [''])[0]
37 html = self.do_GET_calendar(conn, start, end)
39 date = params.get('date', [todays_date()])[0]
40 html = self.do_GET_day(conn, date)
41 elif 'process' == site:
42 id_ = params.get('id', [None])[0]
44 id__ = int(id_) if id_ else None
45 except ValueError as e:
46 raise HandledException(f'Bad ?id= value: {id_}') from e
47 html = self.do_GET_process(conn, id__)
48 elif 'processes' == site:
49 html = self.do_GET_processes(conn)
51 raise HandledException('Test!')
55 except HandledException as error:
58 def do_GET_calendar(self, conn: DatabaseConnection,
59 start: str, end: str) -> str:
61 days = Day.all(conn, date_range=(start, end), fill_gaps=True)
62 return self.server.jinja.get_template('calendar.html').render(
63 days=days, start=start, end=end)
65 def do_GET_day(self, conn: DatabaseConnection, date: str) -> str:
66 """Show single Day."""
67 day = Day.by_date(conn, date, create=True)
68 return self.server.jinja.get_template('day.html').render(day=day)
70 def do_GET_process(self, conn: DatabaseConnection, id_: int | None) -> str:
71 """Show process of id_."""
72 return self.server.jinja.get_template('process.html').render(
73 process=Process.by_id(conn, id_, create=True))
75 def do_GET_processes(self, conn: DatabaseConnection) -> str:
76 """Show all Processes."""
77 return self.server.jinja.get_template('processes.html').render(
78 processes=Process.all(conn))
80 def do_POST(self) -> None:
81 """Handle any POST request."""
83 conn, site, params = self._init_handling()
84 length = int(self.headers['content-length'])
85 postvars = parse_qs(self.rfile.read(length).decode(),
86 keep_blank_values=True)
88 date = params.get('date', [''])[0]
89 self.do_POST_day(conn, date, postvars)
90 elif 'process' == site:
91 id_ = params.get('id', [''])[0]
93 id__ = int(id_) if id_ else None
94 except ValueError as e:
95 raise HandledException(f'Bad ?id= value: {id_}') from e
96 self.do_POST_process(conn, id__, postvars)
100 except HandledException as error:
101 self._send_msg(error)
103 def do_POST_day(self, conn: DatabaseConnection,
104 date: str, postvars: dict[str, list[str]]) -> None:
105 """Update or insert Day of date and fields defined in postvars."""
106 day = Day.by_date(conn, date, create=True)
107 day.comment = postvars['comment'][0]
110 def do_POST_process(self, conn: DatabaseConnection, id_: int | None,
111 postvars: dict[str, list[str]]) -> None:
112 """Update or insert Process of id_ and fields defined in postvars."""
113 process = Process.by_id(conn, id_, create=True)
114 process.title.set(postvars['title'][0])
115 process.description.set(postvars['description'][0])
116 effort = postvars['effort'][0]
118 process.effort.set(float(effort))
119 except ValueError as e:
120 raise HandledException(f'Bad effort value: {effort}') from e
123 def _init_handling(self) -> \
124 tuple[DatabaseConnection, str, dict[str, list[str]]]:
125 conn = DatabaseConnection(self.server.db)
126 parsed_url = urlparse(self.path)
127 site = path_split(parsed_url.path)[1]
128 params = parse_qs(parsed_url.query)
129 return conn, site, params
131 def _redirect(self, target: str) -> None:
132 self.send_response(302)
133 self.send_header('Location', target)
136 def _send_html(self, html: str, code: int = 200) -> None:
137 """Send HTML as proper HTTP response."""
138 self.send_response(code)
140 self.wfile.write(bytes(html, 'utf-8'))
142 def _send_msg(self, msg: Exception, code: int = 400) -> None:
143 """Send message in HTML formatting as HTTP response."""
144 html = self.server.jinja.get_template('msg.html').render(msg=msg)
145 self._send_html(html, code)