diff --git a/server/cgi/db/question.py b/server/cgi/db/question.py
index c603bba..6edf398 100644
--- a/server/cgi/db/question.py
+++ b/server/cgi/db/question.py
@@ -1,4 +1,4 @@
-from object_pool import ObjectPool
+from db.object_pool import ObjectPool
class Question:
def init(self, sID, title, max_time, test_id):
@@ -24,6 +24,32 @@ class Question:
def get_test_id(self):
return self.test_id
+ def render_short(self):
+ total_time = self.max_time
+
+ hours = total_time // 3600
+ total_time -= hours * 3600
+
+ minutes = total_time // 60
+ total_time -= minutes * 60
+
+ seconds = total_time
+
+ total_label = []
+ if hours:
+ total_label.append(f"{hours} год.")
+ if minutes:
+ total_label.append(f"{minutes} хв.")
+ if seconds:
+ total_label.append(f"{seconds} c.")
+
+ total_time = " ".join(total_label)
+
+ if int(self.max_time) > 0:
+ return f'
'
+ else:
+ return f''
+
'''
class QuestionPool:
def __init__(self):
@@ -36,8 +62,14 @@ class QuestionPool:
'''
class QuestionPool:
- def __init__(self):
+ def __init__(self, db):
self.object_pool = ObjectPool("question", Question)
if db:
self.object_pool.load_from_db(db)
+
+ def iter(self):
+ return iter(self.object_pool.pool)
+
+ def select_by_test_id(self, test_id):
+ return [i for i in self.object_pool.pool if i.get_test_id() == int(test_id)]
diff --git a/server/cgi/db/response_option.py b/server/cgi/db/response_option.py
index 405f936..45133dc 100644
--- a/server/cgi/db/response_option.py
+++ b/server/cgi/db/response_option.py
@@ -1,4 +1,4 @@
-from object_pool import ObjectPool
+from db.object_pool import ObjectPool
class ResponseOption:
def init(self, sID, label, questionID, correctness):
@@ -24,20 +24,31 @@ class ResponseOption:
def get_correctness(self):
return correctness
-class ResponceOptionPool:
- def __init__(self):
+ def render_short(self):
+ if self.correctness:
+ c_mark = "+ "
+ else:
+ c_mark = " "
+
+ return f'{c_mark} {self.label}'
+
+ def render_full(self):
+ if self.correctness:
+ c_mark = "+"
+ else:
+ c_mark = "-"
+
+ return f''
+
+class ResponseOptionPool:
+ def __init__(self, db):
self.object_pool = ObjectPool("response_option", ResponseOption)
if db:
self.object_pool.load_from_db(db)
-'''
-class ResponceOptionPool:
- def __init__(self):
- self.pool = []
+ def iter(self):
+ return iter(self.object_pool.pool)
- def load_from_db(self, cur):
- db.execute("SELECT * FROM response_option;")
- self.pool = [ResponseOption().init_from_data(i) for i in cur]
- return self
-'''
+ def select_by_question_id(self, question_id):
+ return [i for i in self.object_pool.pool if i.get_question_id() == int(question_id)]
diff --git a/server/cgi/db/test.py b/server/cgi/db/test.py
index ec899bf..d1ab333 100644
--- a/server/cgi/db/test.py
+++ b/server/cgi/db/test.py
@@ -17,7 +17,7 @@ class Test:
return self.name
def render_short(self):
- return f''
+ return f''
class TestPool:
def __init__(self, db):
diff --git a/server/cgi/question_list.py b/server/cgi/question_list.py
new file mode 100644
index 0000000..3c32fef
--- /dev/null
+++ b/server/cgi/question_list.py
@@ -0,0 +1,15 @@
+from db.question import QuestionPool
+
+class QuestionList:
+ def __init__(self, cursor):
+ self.cursor = cursor
+
+ def render(self, test_id = None):
+ qp = QuestionPool(self.cursor)
+
+ if test_id:
+ rendered_questions = [i.render_short() for i in qp.select_by_test_id(test_id)]
+ else:
+ rendered_questions = [i.render_short() for i in qp.iter()]
+
+ return "\n".join(rendered_questions)
diff --git a/server/cgi/response_option_list.py b/server/cgi/response_option_list.py
new file mode 100644
index 0000000..f89d890
--- /dev/null
+++ b/server/cgi/response_option_list.py
@@ -0,0 +1,15 @@
+from db.response_option import ResponseOptionPool
+
+class ResponseOptionList:
+ def __init__(self, cursor):
+ self.cursor = cursor
+
+ def render(self, question_id = None):
+ rop = ResponseOptionPool(self.cursor)
+
+ if question_id:
+ rendered_response_options = [i.render_full() for i in rop.select_by_question_id(question_id)]
+ else:
+ rendered_response_options = [i.render_full() for i in rop.iter()]
+
+ return "\n".join(rendered_response_options)
diff --git a/server/cgi/view.py b/server/cgi/view.py
index ca30e8e..1520ccb 100644
--- a/server/cgi/view.py
+++ b/server/cgi/view.py
@@ -3,6 +3,10 @@ import json
import os
from test_list import TestList
+from question_list import QuestionList
+from response_option_list import ResponseOptionList
+
+from httputils import escape_html
def readfile(path):
if os.path.exists(path):
@@ -13,6 +17,15 @@ class View:
self.args = query_args
self.connector_data = connector_data
self.db_connection = None
+ self.supported_modes = {
+ "test-list": self.render_test_list,
+ "create-test": self.render_create_test,
+ "view-test": self.render_view_test,
+ "create-question": self.render_create_question,
+ "edit-question": self.render_edit_question,
+ "view-question": self.render_view_question,
+ "create-response-option": self.render_create_response_option,
+ }
def get_db_connection(self):
if not self.db_connection:
@@ -49,29 +62,124 @@ class View:
else:
mode = "test-list"
- if mode == "test-list":
- cur.execute("SELECT id FROM test;")
- test_amount = len(list(cur))
- header = f'Всього тестів: {test_amount}
'
-
- subheader = f'
Створити тест'
-
- content = TestList(cur).render()
-
- elif mode == "create-test":
- header = f"Створити новий тест"
- subheader = "Введіть властивості нового тесту нижче"
-
- content = f'''
'''
-
- else:
+ if mode not in self.supported_modes:
header = f"No such view mode: {mode}
"
subheader = f'Повернутися на головну сторінку'
content = ""
-
+ else:
+ header, subheader, content = self.supported_modes[mode](cur)
+
dbc.close()
return self.format_page(header, subheader, content)
+
+ def render_test_list(self, cur):
+ cur.execute("SELECT id FROM test;")
+ test_amount = len(list(cur))
+ header = f'Всього тестів: {test_amount}
'
+
+ subheader = f'
Створити новий тест'
+
+ content = TestList(cur).render()
+
+ return header, subheader, content
+
+ def render_create_test(self, cur):
+ header = f"Створити новий тест
"
+ subheader = "Введіть властивості нового тесту нижче"
+
+ content = f''''''
+
+ return header, subheader, content
+
+ def render_view_test(self, cur):
+ cur.execute(f"SELECT name FROM test WHERE id = {self.args['id']};")
+ test_name = next(iter(cur), [None])[0]
+
+ if not test_name:
+ header = f"Такого тесту не існує: {self.args['id']}
"
+ subheader = f'Повернутися на головну сторінку'
+ content = ""
+ return header, subheader, content
+
+ header = f'#{self.args["id"]}{test_name}'
+
+ subheader = f'Додати запитання'
+
+ content = QuestionList(cur).render(self.args['id'])
+
+ return header, subheader, content
+
+ def render_create_question(self, cur):
+ header = f"Додати нове запитання
"
+ subheader = "Введіть властивості нового запитання нижче"
+
+ content = f''''''
+
+ return header, subheader, content
+
+ def render_edit_question(self, cur):
+ cur.execute(f"SELECT title, mtime FROM question WHERE id = {self.args['id']};")
+ question_title, question_max_time = next(iter(cur), [None, None])
+
+ if not question_title:
+ header = f"Такого запитання не існує: {self.args['id']}
"
+ subheader = f'Повернутися на головну сторінку'
+ content = ""
+ return header, subheader, content
+
+ header = f"Змінити запитання
"
+ subheader = "Відредагуйте властивості запитання нижче"
+
+ content = f''''''
+
+ return header, subheader, content
+
+ def render_view_question(self, cur):
+ cur.execute(f"SELECT title FROM question WHERE id = {self.args['id']};")
+ question_name = next(iter(cur), [None])[0]
+
+ if not question_name:
+ header = f"Такого запитання не існує: {self.args['id']}
"
+ subheader = f'Повернутися на головну сторінку'
+ content = ""
+ return header, subheader, content
+
+ header = f'#{self.args["id"]}{question_name}'
+
+ subheader = f'Додати варіант відповіді' + \
+ f'Редагувати запитання'
+
+ content = ResponseOptionList(cur).render(self.args['id'])
+
+ return header, subheader, content
+
+ def render_create_response_option(self, cur):
+ header = f"Додати новий варіант відповіді
"
+ subheader = "Введіть властивості варіанту відповіді нижче"
+
+ content = f''''''
+
+ return header, subheader, content
diff --git a/server/create-question.py b/server/create-question.py
new file mode 100644
index 0000000..0daf5aa
--- /dev/null
+++ b/server/create-question.py
@@ -0,0 +1,47 @@
+import mariadb as mdb
+import json
+import sys
+import os
+
+from httputils import parse_query, escape_sql_string
+
+def readfile(path):
+ if os.path.exists(path):
+ return open(path).read()
+
+args = {"host": "127.0.0.1",
+ "port": 3306,
+ "user": "root",
+ "password": "",
+ "database": "test_holder"}
+
+settings = json.loads(readfile("cgi/db-settings.json"))
+args.update(settings)
+
+db_connection = mdb.connect(**args)
+
+args = parse_query(os.environ['QUERY_STRING'])
+
+if not 'test_id' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили ідентифікатор тесту, до якого має залежати запитання\r\n")
+ sys.exit(0)
+
+if not 'title' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили текст запитання\r\n")
+ sys.exit(0)
+
+if not 'mtime' in args:
+ args['mtime'] = 0
+
+cur = db_connection.cursor()
+
+try:
+ cur.execute(f"INSERT INTO question ( title, mtime, tstID ) VALUES ( '{escape_sql_string(args['title'])}', {args['mtime']}, {args['test_id']} );")
+ db_connection.commit()
+
+ cur.execute(f"SELECT id FROM question ORDER BY id DESC;")
+
+ new_id = iter(cur).__next__()[0]
+ print(f"Location: /index.py?mode=view-question&id={new_id}\r\n\r\n")
+except Exception as e:
+ print(f"Content-Type: text/plain; charset=UTF-8\r\n\r\nНе вдалося створити нове запитання ({e})\r\n")
diff --git a/server/create-response-option.py b/server/create-response-option.py
new file mode 100644
index 0000000..5e809b1
--- /dev/null
+++ b/server/create-response-option.py
@@ -0,0 +1,47 @@
+import mariadb as mdb
+import json
+import sys
+import os
+
+from httputils import parse_query, escape_sql_string
+
+def readfile(path):
+ if os.path.exists(path):
+ return open(path).read()
+
+args = {"host": "127.0.0.1",
+ "port": 3306,
+ "user": "root",
+ "password": "",
+ "database": "test_holder"}
+
+settings = json.loads(readfile("cgi/db-settings.json"))
+args.update(settings)
+
+db_connection = mdb.connect(**args)
+
+args = parse_query(os.environ['QUERY_STRING'])
+
+if not 'question_id' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили ідентифікатор запитання, якого стосується цей варіант відповіді\r\n")
+ sys.exit(0)
+
+if not 'label' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили текст варіанту відповіді\r\n")
+ sys.exit(0)
+
+if not 'mtime' in args:
+ args['mtime'] = 0
+
+cur = db_connection.cursor()
+
+try:
+ cur.execute(f"INSERT INTO response_option ( label, qstID, corct ) VALUES ( '{escape_sql_string(args['label'])}', {args['question_id']}, 0 );")
+ db_connection.commit()
+
+ #cur.execute(f"SELECT id FROM response ORDER BY id DESC;")
+
+ #new_id = iter(cur).__next__()[0]
+ print(f"Location: /index.py?mode=view-question&id={args['question_id']}\r\n\r\n")
+except Exception as e:
+ print(f"Content-Type: text/plain; charset=UTF-8\r\n\r\nНе вдалося створити новий варіант відповіді ({e})\r\n")
diff --git a/server/create-test.py b/server/create-test.py
new file mode 100644
index 0000000..1253530
--- /dev/null
+++ b/server/create-test.py
@@ -0,0 +1,40 @@
+import mariadb as mdb
+import json
+import sys
+import os
+
+from httputils import parse_query
+
+def readfile(path):
+ if os.path.exists(path):
+ return open(path).read()
+
+args = {"host": "127.0.0.1",
+ "port": 3306,
+ "user": "root",
+ "password": "",
+ "database": "test_holder"}
+
+settings = json.loads(readfile("cgi/db-settings.json"))
+args.update(settings)
+
+db_connection = mdb.connect(**args)
+
+args = parse_query(os.environ['QUERY_STRING'])
+
+if not 'name' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили назву тесту\r\n")
+ sys.exit(0)
+
+cur = db_connection.cursor()
+
+try:
+ cur.execute(f"INSERT INTO test ( name ) VALUES ( '{args['name']}' );")
+ db_connection.commit()
+
+ cur.execute(f"SELECT id FROM test ORDER BY id DESC;")
+
+ new_id = iter(cur).__next__()[0]
+ print(f"Location: /index.py?mode=view-test&id={new_id}\r\n\r\n")
+except:
+ print(f"Content-Type: text/plain\r\n\r\nНе вдалося створити новий тест\r\n")
diff --git a/server/css/base_style.css b/server/css/base_style.css
index c74a4c7..81d44c3 100644
--- a/server/css/base_style.css
+++ b/server/css/base_style.css
@@ -1,8 +1,162 @@
+:root {
+ --yellow-gradient: linear-gradient(45deg, #ffe88d, #fff8b7);
+ --yellow-gradient-bright: linear-gradient(45deg, #ffeba1, #fffacb);
+ --blue-gradient-subtle: linear-gradient(45deg, #e7ebff, #e7efff);
+ --blue-gradient-glassy: linear-gradient(45deg, #fff9, #fff9);
+ --red-gradient: linear-gradient(45deg, #ffd2cc, #ffcccc);
+}
+
* {
margin: 0;
padding: 0;
+ font-family: sans-serif;
}
html {
width: 100%;
+ background: var(--blue-gradient-subtle);
+}
+
+body {
+ max-width: 600px;
+ margin: auto;
+}
+
+header {
+ border: solid 2px;
+ padding: 10px;
+ margin-top: 20px;
+ margin-bottom: 20px;
+ border-radius: 8px;
+}
+
+header .top-half, header .lower-half {
+ text-align: center;
+}
+
+header .top-half {
+ margin-bottom: 12px;
+}
+
+header .lower-half input {
+ margin-bottom: 12px;
+}
+
+header .top-half .view-test-id-tag {
+ color: #333333;
+ font-size: 16pt;
+ margin-right: 12px;
+}
+
+header .top-half .view-test-main-tag {
+ color: #000;
+ font-size: 20pt;
+ font-weight: 600;
+}
+
+header .lower-half a.generic-button,
+header .lower-half a.sub-button,
+main * a.sub-button,
+main * a.scary-button
+{
+ text-decoration: none;
+ border-radius: 15px;
+ color: #000;
+ text-align: center;
+ font-weight: 700;
+ margin: 4px;
+ margin-top: 0;
+ padding: 10px;
+ padding-left: 20px;
+ padding-right: 20px;
+ display: inline-block;
+}
+
+header .lower-half a.generic-button {
+ background: var(--yellow-gradient);
+}
+
+header .lower-half a.generic-button:hover {
+ background: var(--yellow-gradient-bright);
+}
+
+header .lower-half a.sub-button,
+main * a.sub-button
+{
+ border: 1px solid #000;
+}
+
+main * div.controls
+{
+ display: grid;
+ grid-auto-flow: column;
+ margin-top: 13px;
+ grid-auto-columns: max-content;
+ justify-content: end;
+}
+
+main * a.sub-button {
+}
+
+main * a.scary-button
+{
+ background: var(--red-gradient);
+ color: #500;
+}
+
+main .test-short,
+main .question-short,
+main .response-option
+{
+ padding: 20px;
+ background: var(--blue-gradient-glassy);
+ margin-top: 16px;
+ border-radius: 14px;
+ vertical-align: middle;
+}
+
+main .test-short a.test-link,
+main .question-short a.question-link {
+ display: block;
+ text-decoration: none;
+}
+
+main .test-short a.test-link .main-label {
+ color: #002;
+ font-weight: 700;
+ font-size: 16pt;
+}
+
+main .test-short a.test-link .sub-label,
+main .question-short a.question-link .sub-label {
+ color: #333;
+ margin-right: 11px;
+}
+
+main .test-short a.test-link {
+ display: block;
+ text-decoration: none;
+}
+
+main .question-short a.question-link .main-label {
+ color: #002;
+ font-weight: 600;
+ font-size: 15pt;
+}
+
+main * .sub-title {
+ display: block;
+ margin-top: 12px;
+}
+
+main .response-option a.response-option-mark {
+ font-size: 16pt;
+ text-decoration: none;
+ display: inline-block;
+ padding: 2px 10px 3px 10px;
+ margin-right: 12px;
+ color: #001;
+ border-radius: 24px;
+ border: solid 1px;
+ font-family: monospace;
}
diff --git a/server/edit-question.py b/server/edit-question.py
new file mode 100644
index 0000000..993b9aa
--- /dev/null
+++ b/server/edit-question.py
@@ -0,0 +1,47 @@
+import mariadb as mdb
+import json
+import sys
+import os
+
+from httputils import parse_query, escape_sql_string
+
+def readfile(path):
+ if os.path.exists(path):
+ return open(path).read()
+
+args = {"host": "127.0.0.1",
+ "port": 3306,
+ "user": "root",
+ "password": "",
+ "database": "test_holder"}
+
+settings = json.loads(readfile("cgi/db-settings.json"))
+args.update(settings)
+
+db_connection = mdb.connect(**args)
+
+args = parse_query(os.environ['QUERY_STRING'])
+
+if not 'question_id' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили ідентифікатор тесту, до якого має залежати запитання\r\n")
+ sys.exit(0)
+
+if not 'title' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили текст запитання\r\n")
+ sys.exit(0)
+
+if not 'mtime' in args or not args['mtime']:
+ args['mtime'] = 0
+
+cur = db_connection.cursor()
+
+try:
+ cur.execute(f"UPDATE question SET title = '{escape_sql_string(args['title'])}', mtime = {args['mtime']} WHERE id = {args['question_id']};")
+ db_connection.commit()
+
+ #cur.execute(f"SELECT id FROM question ORDER BY id DESC;")
+
+ #new_id = iter(cur).__next__()[0]
+ print(f"Location: /index.py?mode=view-question&id={args['question_id']}\r\n\r\n")
+except Exception as e:
+ print(f"Content-Type: text/plain; charset=UTF-8\r\n\r\nНе вдалося створити нове запитання ({e})\r\n")
diff --git a/server/flip-correctness.py b/server/flip-correctness.py
new file mode 100644
index 0000000..60f7d5b
--- /dev/null
+++ b/server/flip-correctness.py
@@ -0,0 +1,44 @@
+import mariadb as mdb
+import json
+import sys
+import os
+
+from httputils import parse_query, escape_sql_string
+
+def readfile(path):
+ if os.path.exists(path):
+ return open(path).read()
+
+args = {"host": "127.0.0.1",
+ "port": 3306,
+ "user": "root",
+ "password": "",
+ "database": "test_holder"}
+
+settings = json.loads(readfile("cgi/db-settings.json"))
+args.update(settings)
+
+db_connection = mdb.connect(**args)
+
+args = parse_query(os.environ['QUERY_STRING'])
+
+if not 'res_opt_id' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили ідентифікатор варіанту відповіді\r\n")
+ sys.exit(0)
+
+cur = db_connection.cursor()
+
+try:
+ cur.execute(f"SELECT corct, qstID FROM response_option WHERE id = {args['res_opt_id']};")
+ data = iter(cur).__next__()
+ new_correctness = not bool(data[0])
+
+ cur.execute(f"UPDATE response_option SET corct = {int(new_correctness)} WHERE id = {args['res_opt_id']};")
+ db_connection.commit()
+
+ #cur.execute(f"SELECT id FROM response ORDER BY id DESC;")
+
+ #new_id = iter(cur).__next__()[0]
+ print(f"Location: /index.py?mode=view-question&id={data[1]}\r\n\r\n")
+except Exception as e:
+ print(f"Content-Type: text/plain; charset=UTF-8\r\n\r\nНе вдалося створити новий варіант відповіді ({e})\r\n")
diff --git a/server/httputils.py b/server/httputils.py
index dbbb6ae..a9920a3 100644
--- a/server/httputils.py
+++ b/server/httputils.py
@@ -1,3 +1,11 @@
+html_escaping = {
+ "<": "<",
+ ">": ">",
+ "\"": """
+}
+
+
+
def decode_url(url):
i = 0
end = len(url)
@@ -14,6 +22,9 @@ def decode_url(url):
i += 3
except:
i += 3
+ elif url[i] == "+":
+ decode_buffer += " "
+ i += 1
else:
decode_buffer += url[i]
i += 1
@@ -29,3 +40,12 @@ def parse_query(query):
query_dict[decode_url(k)] = decode_url(v)
return query_dict
+
+def escape_sql_string(s):
+ return s.replace("'", "''")
+
+def escape_html(s):
+ for k, v in html_escaping.items():
+ s = s.replace(k, v)
+
+ return s
diff --git a/server/index.py b/server/index.py
index 6c0e679..d007c13 100644
--- a/server/index.py
+++ b/server/index.py
@@ -1,43 +1,14 @@
import sys
import os
-print(f"PWD: {os.getcwd()}", file=sys.stderr)
+#print(f"PWD: {os.getcwd()}", file=sys.stderr)
-print(f"Environ: {os.environ}", file=sys.stderr)
+#print(f"Environ: {os.environ}", file=sys.stderr)
sys.path.insert(0, "/root/ipz-server-1/server/cgi/")
from view import View
from httputils import parse_query
-'''
-def decode_url(url):
- i = 0
- end = len(url)
- decode_buffer = ''
- char_buffer = bytearray()
-
- while i < end:
- if url[i] == '%':
- try:
- char_buffer.append(int(f'0x{url[i+1:i+3]}', 16))
- decode_buffer += char_buffer.decode("UTF-8")
- del char_buffer[:]
- i += 3
- except:
- i += 3
- else:
- decode_buffer += url[i]
- i += 1
-
- return decode_buffer
-
-query = [i for i in os.environ['QUERY_STRING'].split("&") if i]
-
-query_dict = {}
-for i in query:
- k, v = i.split("=")
- query_dict[decode_url(k)] = decode_url(v)
-'''
query_dict = parse_query(os.environ['QUERY_STRING'])