diff --git a/server/cgi/db/question.py b/server/cgi/db/question.py
index 530fc43..2b389c5 100644
--- a/server/cgi/db/question.py
+++ b/server/cgi/db/question.py
@@ -54,11 +54,17 @@ class Question:
rop = ResponseOptionPool(cur)
return " ".join([i.render_short() for i in rop.select_by_question_id(self.id)])
+ def get_correct_response_percentage(self, cur):
+ rop = ResponseOptionPool(cur)
+ response_options = rop.select_by_question_id(self.id)
+ return sum([int(i.get_correctness()) for i in response_options]) / len(response_options) * 100
+
def render_short(self, cur):
time_label = self.get_time_label()
response_options = self.get_response_option_short_list(cur)
+ correct_percentage = round(self.get_correct_response_percentage(cur))
- return f'
'
+ return f''
class QuestionPool:
def __init__(self, db):
diff --git a/server/cgi/db/response_option.py b/server/cgi/db/response_option.py
index 8b822f5..877992c 100644
--- a/server/cgi/db/response_option.py
+++ b/server/cgi/db/response_option.py
@@ -22,7 +22,7 @@ class ResponseOption:
return self.questionID
def get_correctness(self):
- return correctness
+ return self.correctness
def render_short(self):
if self.correctness:
diff --git a/server/cgi/db/test.py b/server/cgi/db/test.py
index d1ab333..f9fb612 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
index 90774eb..e22d815 100644
--- a/server/cgi/question_list.py
+++ b/server/cgi/question_list.py
@@ -13,3 +13,34 @@ class QuestionList:
rendered_questions = [i.render_short(self.cursor) for i in qp.iter()]
return "\n".join(rendered_questions)
+
+ def count(self):
+ qp = QuestionPool(self.cursor)
+ return len(qp.object_pool.pool)
+
+ def get_avg_time(self):
+ qp = QuestionPool(self.cursor)
+ avg_time = sum([i.get_max_time() for i in qp.iter()]) / len(qp.object_pool.pool)
+ return self.get_time_label(avg_time)
+
+ def get_time_label(self, max_time):
+ total_time = 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"{round(hours)} год.")
+ if minutes:
+ total_label.append(f"{round(minutes)} хв.")
+ if seconds:
+ total_label.append(f"{round(seconds)} c.")
+
+ return " ".join(total_label)
diff --git a/server/cgi/response_option_list.py b/server/cgi/response_option_list.py
index f89d890..e1e82fd 100644
--- a/server/cgi/response_option_list.py
+++ b/server/cgi/response_option_list.py
@@ -13,3 +13,10 @@ class ResponseOptionList:
rendered_response_options = [i.render_full() for i in rop.iter()]
return "\n".join(rendered_response_options)
+
+ def count(self, correct = False):
+ rop = ResponseOptionPool(self.cursor)
+ if correct:
+ return len([i for i in rop.object_pool.pool if i.get_correctness()])
+ else:
+ return len(rop.object_pool.pool)
diff --git a/server/cgi/test_list.py b/server/cgi/test_list.py
index 91ab572..187b95a 100644
--- a/server/cgi/test_list.py
+++ b/server/cgi/test_list.py
@@ -6,7 +6,14 @@ class TestList:
def __init__(self, cursor):
self.cursor = cursor
- def render(self):
+ def render(self, *args):
tp = TestPool(self.cursor)
- rendered_chunks = [i.render_short() for i in tp.object_pool.pool]
+ if len(args) > 0:
+ rendered_chunks = [i.render_short() for i in tp.object_pool.pool if args[0] in i.get_name()]
+ else:
+ rendered_chunks = [i.render_short() for i in tp.object_pool.pool]
return "\n".join(rendered_chunks)
+
+ def count(self):
+ tp = TestPool(self.cursor)
+ return len(tp.object_pool.pool)
diff --git a/server/cgi/view.py b/server/cgi/view.py
index c4f170b..af19a96 100644
--- a/server/cgi/view.py
+++ b/server/cgi/view.py
@@ -20,6 +20,8 @@ class View:
self.supported_modes = {
"test-list": self.render_test_list,
"create-test": self.render_create_test,
+ "edit-test": self.render_edit_test,
+ "delete-test": self.render_delete_test,
"view-test": self.render_view_test,
"create-question": self.render_create_question,
"edit-question": self.render_edit_question,
@@ -28,6 +30,8 @@ class View:
"create-response-option": self.render_create_response_option,
"edit-response-option": self.render_edit_response_option,
"delete-response-option": self.render_delete_response_option,
+ "generate-response-options": self.render_generate_response_options,
+ "view-stats": self.render_view_stats
}
def get_db_connection(self):
@@ -79,10 +83,19 @@ class View:
cur.execute("SELECT id FROM test;")
test_amount = len(list(cur))
header = f'Всього тестів: {test_amount} '
+
+ if 'search' in self.args:
+ search = self.args['search']
+ else:
+ search = ""
- subheader = f'Створити новий тест '
+ subheader = f'Створити новий тест Переглянути статистику '
- content = TestList(cur).render()
+ tl = TestList(cur)
+ if 'search' in self.args:
+ content = tl.render(self.args['search'])
+ else:
+ content = tl.render()
return header, subheader, content
@@ -93,11 +106,50 @@ class View:
content = f''''''
return header, subheader, content
+ def render_edit_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"Редагувати тест "
+ subheader = "Вкажіть нові властивості тесту нижче "
+
+ content = f''''''
+
+ return header, subheader, content
+
+ def render_delete_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"Точно видалити цей тест? "
+ subheader = f"{test_name} "
+
+ 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]
@@ -110,7 +162,7 @@ class View:
header = f'#{self.args["id"]} {test_name} '
- subheader = f'Додати запитання '
+ subheader = f'Додати запитання Редагувати тест '
content = QuestionList(cur).render(self.args['id'])
content += ''
@@ -127,7 +179,7 @@ class View:
Максимальний час на відповідь (в сек):
-
+
'''
return header, subheader, content
@@ -151,7 +203,7 @@ class View:
Максимальний час на відповідь (в сек):
-
+
'''
return header, subheader, content
@@ -172,7 +224,7 @@ class View:
cur.execute(f"SELECT test.id FROM test JOIN question ON test.id = question.tstID WHERE question.id = {self.args['id']};")
test_id = iter(cur).__next__()[0]
- content = f'''Так, видалити Ні, залишити '''
+ content = f''''''
return header, subheader, content
@@ -211,7 +263,7 @@ class View:
Текст відповіді:
-
+
'''
return header, subheader, content
@@ -227,7 +279,7 @@ class View:
Текст відповіді:
-
+
'''
return header, subheader, content
@@ -242,6 +294,38 @@ class View:
cur.execute(f"SELECT question.id FROM question JOIN response_option ON question.id = response_option.qstID WHERE response_option.id = {self.args['id']};")
quest_id = iter(cur).__next__()[0]
- content = f'''Так, видалити Ні, залишити '''
+ content = f''''''
+
+ return header, subheader, content
+
+ def render_generate_response_options(self, cur):
+ header = f"Генерація варіантів відповідей "
+ subheader = f"Вкажіть параметри генерації нижче "
+
+ content = f''''''
+
+ return header, subheader, content
+
+ def render_view_stats(self, cur):
+ header = f"Статистика системи "
+ subheader = f'Повернутися на головну сторінку '
+ content = f''''''
+
+ test_count = TestList(cur).count()
+ content += f'Всього тестів: {test_count}
'
+
+ question_count = QuestionList(cur).count()
+ question_avg_time = QuestionList(cur).get_avg_time()
+ content += f'Всього запитань: {question_count} (в середньому по {round(question_count/test_count, 3)} зап./тест) В середньому на запитання дається {question_avg_time}
'
+
+ rol = ResponseOptionList(cur)
+ response_option_count = rol.count()
+ response_option_count_correct = rol.count(correct = True)
+ content += f'Всього варіантів відповідей: {response_option_count} З-поміж них правильних: {response_option_count_correct} ({round(response_option_count_correct/response_option_count*100, 2)}%)
'
return header, subheader, content
diff --git a/server/css/base_style.css b/server/css/base_style.css
index b9a06f7..15b4297 100644
--- a/server/css/base_style.css
+++ b/server/css/base_style.css
@@ -2,8 +2,9 @@
--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);
+ --blue-glassy: #fff9;
--red-gradient: linear-gradient(45deg, #ffd2cc, #ffcccc);
+ --magical: linear-gradient(45deg, #e0f2ff, #e4fff0, #eee0ff);
}
* {
@@ -34,11 +35,9 @@ header .top-half, header .lower-half {
text-align: center;
}
-header .top-half {
- margin-bottom: 12px;
-}
-
-header .lower-half input {
+header .top-half,
+header .lower-half input
+{
margin-bottom: 12px;
}
@@ -58,7 +57,10 @@ header .lower-half a.generic-button,
header .lower-half a.sub-button,
main * a.sub-button,
main * a.scary-button,
-main a.return-button
+main a.return-button,
+main a.delete-button,
+main a.cancel-button,
+main a.magic-button
{
text-decoration: none;
border-radius: 15px;
@@ -67,9 +69,7 @@ main a.return-button
font-weight: 700;
margin: 4px;
margin-top: 0;
- padding: 10px;
- padding-left: 20px;
- padding-right: 20px;
+ padding: 10px 20px 10px 20px;
display: inline-block;
}
@@ -83,7 +83,8 @@ header .lower-half a.generic-button:hover {
header .lower-half a.sub-button,
main * a.sub-button,
-main a.return-button
+main a.return-button,
+main a.cancel-button
{
border: 1px solid #000;
}
@@ -97,26 +98,45 @@ main * div.controls
justify-content: end;
}
-main * a.sub-button {
-}
-
-main * a.scary-button
+main * a.scary-button,
+main a.delete-button
{
background: var(--red-gradient);
color: #500;
}
+
+main a.magic-button
+{
+ background: var(--magical);
+ /*
+ animation-name: magic-ani;
+ animation-duration: 6s;
+ animation-iteration-count: infinite;
+ animation-timing-function: linear;
+ */
+}
+
main .test-short,
main .question-short,
-main .response-option
+main .response-option,
+main .piece-of-stats
{
padding: 20px;
- background: var(--blue-gradient-glassy);
+ background: var(--blue-glassy);
margin-bottom: 16px;
border-radius: 14px;
vertical-align: middle;
}
+main .piece-of-stats .header
+{
+ display: block;
+ font-size: 16pt;
+ font-weight: 600;
+ margin-bottom: 10px;
+}
+
main .test-short a.test-link,
main .question-short a.question-link {
display: block;
@@ -151,6 +171,52 @@ main * .sub-title {
margin-top: 12px;
}
+main form label
+{
+ display: block;
+ margin-bottom: 10px;
+ font-size: 12pt;
+}
+
+main form input,
+header .lower-half form input
+{
+ display: block;
+ border-radius: 20px;
+ padding: 12px 30px 12px 30px;
+ font-size: 15pt;
+ width: 90%;
+ margin: auto;
+ border: 1px solid #fff;
+ transition-duration: 300ms;
+}
+
+main form input:focus,
+header .lower-half form input:focus
+{
+ outline: none;
+ border: 1px solid;
+ transition-duration: 300ms;
+}
+
+
+main form input[type=submit]
+{
+ width: auto;
+ border: none;
+ border-radius: 20px;
+ padding: 12px 30px 12px 30px;
+ background: var(--yellow-gradient);
+ font-weight: 600;
+ font-size: 15pt;
+}
+
+main form input[type=submit]:hover
+{
+ cursor: pointer;
+ background: var(--yellow-gradient-bright);
+}
+
main * .response-option-short-list
{
margin-top: 12px;
@@ -166,7 +232,8 @@ main * .response-option-short
font-weight: 500;
}
-main .response-option a.response-option-mark {
+main .response-option a.response-option-mark
+{
font-size: 16pt;
text-decoration: none;
display: inline-block;
@@ -183,8 +250,24 @@ main .return-button-centerer
display: grid;
}
-main a.return-button
+main a.return-button,
+main a.delete-button,
+main a.cancel-button
{
margin: auto;
margin-bottom: 15px;
}
+
+header .lower-half form input
+{
+ transition-duration: 300ms;
+
+@keyframes magic-ani
+{
+ 0% {
+ filter: hue-rotate(0deg);
+ }
+ 100% {
+ filter: hue-rotate(360deg);
+ }
+}
diff --git a/server/delete-test.py b/server/delete-test.py
new file mode 100644
index 0000000..077b99d
--- /dev/null
+++ b/server/delete-test.py
@@ -0,0 +1,41 @@
+import mariadb as mdb
+import traceback
+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 '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 test.id FROM test JOIN question ON test.id = question.tstID WHERE question.id = {args['id']};")
+ #test_id = iter(cur).__next__()[0]
+
+ cur.execute(f"DELETE FROM test WHERE id = {args['id']};")
+ db_connection.commit()
+
+ print(f"Location: /\r\n\r\n")
+except Exception as e:
+ print(f"Content-Type: text/plain; charset=UTF-8\r\n\r\nНе вдалося видалити запитання ({e})\r\n{traceback.format_exc()}\r\n")
diff --git a/server/edit-test.py b/server/edit-test.py
new file mode 100644
index 0000000..4da2754
--- /dev/null
+++ b/server/edit-test.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 'id' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили ідентифікатор тесту\r\n")
+ sys.exit(0)
+
+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"UPDATE test SET name = '{escape_sql_string(args['name'])}' WHERE id = {args['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-test&id={args['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/generate-response-options.py b/server/generate-response-options.py
new file mode 100644
index 0000000..7464362
--- /dev/null
+++ b/server/generate-response-options.py
@@ -0,0 +1,56 @@
+import mariadb as mdb
+import random
+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 'id' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили ідентифікатор запитання, до якого генеруватимуться варіанти відповідей\r\n")
+ sys.exit(0)
+
+if not 'amount' in args:
+ print("Content-Type: text/plain; charset=UTF-8\r\n\r\nВи не зазначили кількість нових варіантів відповідей\r\n")
+ sys.exit(0)
+
+cur = db_connection.cursor()
+
+try:
+ chosen_the_right_one = False
+ for i in range(int(args['amount'])):
+ if not chosen_the_right_one:
+ if int(args['amount']) == i + 1:
+ correct = True
+ else:
+ correct = random.random() > 0.6
+ else:
+ correct = False
+
+ label = str(random.randint(0, 120))
+ if correct:
+ chosen_the_right_one = True
+
+ cur.execute(f"INSERT INTO response_option ( label, qstID, corct ) VALUES ( '{escape_sql_string(label)}', {args['id']}, {correct} );")
+ db_connection.commit()
+
+ print(f"Location: /index.py?mode=view-question&id={args['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")