home · contact · privacy
af1a60cd148ec86a83bf27bad8759a7e1cf74e6f
[plomtask] / plomtask / http.py
1 """Web server stuff."""
2 from typing import Any
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.exceptions import HandledException, BadFormatException
10 from plomtask.db import DatabaseConnection, DatabaseFile
11 from plomtask.processes import Process
12
13 TEMPLATES_DIR = 'templates'
14
15
16 class TaskServer(HTTPServer):
17     """Variant of HTTPServer that knows .jinja as Jinja Environment."""
18
19     def __init__(self, db_file: DatabaseFile,
20                  *args: Any, **kwargs: Any) -> None:
21         super().__init__(*args, **kwargs)
22         self.db = db_file
23         self.jinja = JinjaEnv(loader=JinjaFSLoader(TEMPLATES_DIR))
24
25
26 class TaskHandler(BaseHTTPRequestHandler):
27     """Handles single HTTP request."""
28     server: TaskServer
29
30     def do_GET(self) -> None:
31         """Handle any GET request."""
32         try:
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)
38             elif 'day' == site:
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]
43                 try:
44                     id__ = int(id_) if id_ else None
45                 except ValueError as e:
46                     raise BadFormatException(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)
50             else:
51                 raise HandledException('Test!')
52             conn.commit()
53             conn.close()
54             self._send_html(html)
55         except HandledException as error:
56             self._send_msg(error, code=error.http_code)
57
58     def do_GET_calendar(self, conn: DatabaseConnection,
59                         start: str, end: str) -> str:
60         """Show Days."""
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)
64
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)
69
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))
74
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))
79
80     def do_POST(self) -> None:
81         """Handle any POST request."""
82         try:
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)
87             if 'day' == site:
88                 date = params.get('date', [''])[0]
89                 self.do_POST_day(conn, date, postvars)
90             elif 'process' == site:
91                 id_ = params.get('id', [''])[0]
92                 try:
93                     id__ = int(id_) if id_ else None
94                 except ValueError as e:
95                     raise BadFormatException(f'Bad ?id= value: {id_}') from e
96                 self.do_POST_process(conn, id__, postvars)
97             conn.commit()
98             conn.close()
99             self._redirect('/')
100         except HandledException as error:
101             self._send_msg(error, code=error.http_code)
102
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]
108         day.save(conn)
109
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]
117         try:
118             process.effort.set(float(effort))
119         except ValueError as e:
120             raise BadFormatException(f'Bad effort value: {effort}') from e
121         process.save(conn)
122
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
130
131     def _redirect(self, target: str) -> None:
132         self.send_response(302)
133         self.send_header('Location', target)
134         self.end_headers()
135
136     def _send_html(self, html: str, code: int = 200) -> None:
137         """Send HTML as proper HTTP response."""
138         self.send_response(code)
139         self.end_headers()
140         self.wfile.write(bytes(html, 'utf-8'))
141
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)