332 lines
16 KiB
Python
332 lines
16 KiB
Python
import mariadb as mdb
|
||
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):
|
||
return open(path).read()
|
||
|
||
class View:
|
||
def __init__(self, query_args, connector_data = {}):
|
||
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,
|
||
"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,
|
||
"delete-question": self.render_delete_question,
|
||
"view-question": self.render_view_question,
|
||
"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):
|
||
if not self.db_connection:
|
||
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)
|
||
|
||
args.update(self.connector_data)
|
||
|
||
self.db_connection = mdb.connect(**args)
|
||
|
||
return self.db_connection
|
||
|
||
def format_page(self, header, subheader, content):
|
||
base_layout = readfile("html/base_layout.html")
|
||
|
||
base_layout = base_layout.replace("%HEADER%", header)
|
||
base_layout = base_layout.replace("%SUBHEADER%", subheader)
|
||
base_layout = base_layout.replace("%CONTENT%", content)
|
||
|
||
return base_layout
|
||
|
||
def render_page(self):
|
||
dbc = self.get_db_connection()
|
||
cur = dbc.cursor()
|
||
|
||
if 'mode' in self.args:
|
||
mode = self.args['mode']
|
||
else:
|
||
mode = "test-list"
|
||
|
||
if mode not in self.supported_modes:
|
||
header = f"<h2>No such view mode: {mode}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
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'<h2>Всього тестів: {test_amount}</h2>'
|
||
|
||
if 'search' in self.args:
|
||
search = self.args['search']
|
||
else:
|
||
search = ""
|
||
|
||
subheader = f'<form action="/index.py"><input name="search" placeholder="Шукати тести" value="{search}"></form><br><a class="generic-button" href="?mode=create-test">Створити новий тест</a><a class="sub-button" href="?mode=view-stats">Переглянути статистику</a>'
|
||
|
||
tl = TestList(cur)
|
||
if 'search' in self.args:
|
||
content = tl.render(self.args['search'])
|
||
else:
|
||
content = tl.render()
|
||
|
||
return header, subheader, content
|
||
|
||
def render_create_test(self, cur):
|
||
header = f"<h2>Створити новий тест</h2>"
|
||
subheader = "<i>Введіть властивості нового тесту нижче</i>"
|
||
|
||
content = f'''<form action="/create-test.py">
|
||
<label for="name">Назва тесту:</label>
|
||
<input type="text" name="name" placeholder="Введіть назву..." required><br>
|
||
<input type="submit" class="submit-button" value="Створити">
|
||
</form>'''
|
||
|
||
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"<h2>Такого тесту не існує: {self.args['id']}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
content = ""
|
||
return header, subheader, content
|
||
|
||
header = f"<h2>Редагувати тест</h2>"
|
||
subheader = "<i>Вкажіть нові властивості тесту нижче</i>"
|
||
|
||
content = f'''<form action="/edit-test.py">
|
||
<input type="text" name="id" value="{self.args['id']}" style="display:none;">
|
||
<label for="name">Назва тесту:</label>
|
||
<input type="text" name="name" placeholder="Введіть назву..." value="{test_name}" required><br>
|
||
<input type="submit" class="submit-button" value="Зберегти">
|
||
</form>'''
|
||
|
||
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"<h2>Такого тесту не існує: {self.args['id']}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
content = ""
|
||
return header, subheader, content
|
||
|
||
header = f"<h2>Точно видалити цей тест?</h2>"
|
||
subheader = f"<i>{test_name}</i>"
|
||
|
||
content = f'''<div class="return-button-centerer"><a class="delete-button" href="/delete-test.py?id={self.args["id"]}">Так, видалити</a><br><a class="cancel-button" href="/">Ні, залишити</a></div>'''
|
||
|
||
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"<h2>Такого тесту не існує: {self.args['id']}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
content = ""
|
||
return header, subheader, content
|
||
|
||
header = f'<span class="view-test-id-tag">#{self.args["id"]}</span><span class="view-test-main-tag">{test_name}</span>'
|
||
|
||
subheader = f'<a class="generic-button" href="?mode=create-question&test_id={self.args["id"]}">Додати запитання</a><a class="sub-button" href="?mode=edit-test&id={self.args["id"]}">Редагувати тест</a>'
|
||
|
||
content = QuestionList(cur).render(self.args['id'])
|
||
content += '<div class="return-button-centerer"><a class="return-button" href="/index.py">Назад до переліку тестів</a></div>'
|
||
|
||
return header, subheader, content
|
||
|
||
def render_create_question(self, cur):
|
||
header = f"<h2>Додати нове запитання</h2>"
|
||
subheader = "<i>Введіть властивості нового запитання нижче</i>"
|
||
|
||
content = f'''<form action="/create-question.py">
|
||
<input type="text" name="test_id" value="{self.args["test_id"]}" style="display:none;">
|
||
<label for="title">Текст запитання:</label>
|
||
<input type="text" name="title" placeholder="Введіть запитання..." required><br>
|
||
<label for="mtime">Максимальний час на відповідь (в сек):</label>
|
||
<input type="number" name="mtime" placeholder="Наприклад, 120"><br>
|
||
<input type="submit" class="submit-button" value="Додати">
|
||
</form>'''
|
||
|
||
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"<h2>Такого запитання не існує: {self.args['id']}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
content = ""
|
||
return header, subheader, content
|
||
|
||
header = f"<h2>Змінити запитання</h2>"
|
||
subheader = "<i>Відредагуйте властивості запитання нижче</i>"
|
||
|
||
content = f'''<form action="/edit-question.py">
|
||
<input type="text" name="question_id" value="{self.args["id"]}" style="display:none;">
|
||
<label for="title">Текст запитання:</label>
|
||
<input type="text" name="title" placeholder="Введіть запитання..." value="{escape_html(question_title)}" required><br>
|
||
<label for="mtime">Максимальний час на відповідь (в сек):</label>
|
||
<input type="number" name="mtime" placeholder="Наприклад, 120" value="{question_max_time}"><br>
|
||
<input type="submit" class="submit-button" value="Зберегти">
|
||
</form>'''
|
||
|
||
return header, subheader, content
|
||
|
||
def render_delete_question(self, cur):
|
||
cur.execute(f"SELECT title FROM question WHERE id = {self.args['id']};")
|
||
question_title = next(iter(cur), [None])[0]
|
||
|
||
if not question_title:
|
||
header = f"<h2>Такого запитання не існує: {self.args['id']}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
content = ""
|
||
return header, subheader, content
|
||
|
||
header = f"<h2>Точно видалити запитання?</h2>"
|
||
subheader = f"<i>{question_title}</i>"
|
||
|
||
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'''<div class="return-button-centerer"><a class="delete-button" href="/delete-question.py?id={self.args["id"]}">Так, видалити</a><br><a class="cancel-button" href="/index.py?mode=view-test&id={test_id}">Ні, залишити</a></div>'''
|
||
|
||
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"<h2>Такого запитання не існує: {self.args['id']}</h2>"
|
||
subheader = f'<a href="/index.py">Повернутися на головну сторінку</a>'
|
||
content = ""
|
||
return header, subheader, content
|
||
|
||
header = f'<span class="view-test-id-tag">#{self.args["id"]}</span><span class="view-test-main-tag">{question_name}</span>'
|
||
|
||
subheader = f'<a class="generic-button" href="?mode=create-response-option&question_id={self.args["id"]}">Додати варіант відповіді</a>' + \
|
||
f'<a class="sub-button" href="?mode=edit-question&id={self.args["id"]}">Редагувати запитання</a>'
|
||
|
||
content = ResponseOptionList(cur).render(self.args['id'])
|
||
|
||
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'<div class="return-button-centerer"><a class="return-button" href="/index.py?mode=view-test&id={test_id}">Назад до тесту</a></div>'
|
||
|
||
|
||
return header, subheader, content
|
||
|
||
def render_create_response_option(self, cur):
|
||
header = f"<h2>Додати новий варіант відповіді</h2>"
|
||
subheader = "<i>Введіть властивості варіанту відповіді нижче</i>"
|
||
|
||
content = f'''<form action="/create-response-option.py">
|
||
<input type="text" name="question_id" value="{self.args["question_id"]}" style="display:none;">
|
||
<label for="label">Текст відповіді:</label>
|
||
<input type="text" name="label" placeholder="Введіть текст відповіді..." required><br>
|
||
<input type="submit" class="submit-button" value="Додати">
|
||
</form>'''
|
||
|
||
return header, subheader, content
|
||
|
||
def render_edit_response_option(self, cur):
|
||
cur.execute(f"SELECT label FROM response_option WHERE id = {self.args['id']};")
|
||
respose_option_label = next(iter(cur), [None])[0]
|
||
|
||
header = f"<h2>Редагувати варіант відповіді</h2>"
|
||
subheader = "<i>Введіть властивості варіанту відповіді нижче</i>"
|
||
|
||
content = f'''<form action="/edit-response-option.py">
|
||
<input type="text" name="id" value="{self.args["id"]}" style="display:none;">
|
||
<label for="label">Текст відповіді:</label>
|
||
<input type="text" name="label" placeholder="Введіть текст відповіді..." value="{respose_option_label}" required><br>
|
||
<input type="submit" class="submit-button" value="Зберегти">
|
||
</form>'''
|
||
|
||
return header, subheader, content
|
||
|
||
def render_delete_response_option(self, cur):
|
||
cur.execute(f"SELECT label FROM response_option WHERE id = {self.args['id']};")
|
||
response_option_label = next(iter(cur), [None])[0]
|
||
|
||
header = f"<h2>Точно видалити цей варіант відповіді?</h2>"
|
||
subheader = f"<i>{response_option_label}</i>"
|
||
|
||
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'''<div class="return-button-centerer"><a class="delete-button" href="/delete-response-option.py?id={self.args["id"]}">Так, видалити</a><br><a class="cancel-button" href="/index.py?mode=view-question&id={quest_id}">Ні, залишити</a></div>'''
|
||
|
||
return header, subheader, content
|
||
|
||
def render_generate_response_options(self, cur):
|
||
header = f"<h2>Генерація варіантів відповідей</h2>"
|
||
subheader = f"<i>Вкажіть параметри генерації нижче</i>"
|
||
|
||
content = f'''<form action="/generate-response-options.py">
|
||
<input type="text" name="id" value="{self.args["id"]}" style="display:none;">
|
||
<label for="label">Кількість варіантів:</label>
|
||
<input type="number" name="amount" placeholder="Наприклад, 4" required><br>
|
||
<input type="submit" class="magic-button" value="Згенерувати">
|
||
</form>'''
|
||
|
||
return header, subheader, content
|
||
|
||
def render_view_stats(self, cur):
|
||
header = f"<h2>Статистика системи</h2>"
|
||
subheader = f'<a class="sub-button" href="/">Повернутися на головну сторінку</a>'
|
||
content = f''''''
|
||
|
||
test_count = TestList(cur).count()
|
||
content += f'<div class="piece-of-stats"><span class="header">Тести</span>Всього тестів: {test_count}</div>'
|
||
|
||
question_count = QuestionList(cur).count()
|
||
question_avg_time = QuestionList(cur).get_avg_time()
|
||
content += f'<div class="piece-of-stats"><span class="header">Запитання</span>Всього запитань: {question_count} (в середньому по {round(question_count/test_count, 3)} зап./тест)<br>В середньому на запитання дається {question_avg_time}</div>'
|
||
|
||
rol = ResponseOptionList(cur)
|
||
response_option_count = rol.count()
|
||
response_option_count_correct = rol.count(correct = True)
|
||
content += f'<div class="piece-of-stats"><span class="header">Варіанти відповідей</span>Всього варіантів відповідей: {response_option_count}<br>З-поміж них правильних: {response_option_count_correct} ({round(response_option_count_correct/response_option_count*100, 2)}%)</div>'
|
||
|
||
return header, subheader, content
|