Add support for adding a question to a new category from the admin site
This commit is contained in:
parent
b032e793de
commit
deadf5a61b
@ -5,22 +5,22 @@ from QuizTheWord import database
|
|||||||
from QuizTheWord.database import get_question
|
from QuizTheWord.database import get_question
|
||||||
|
|
||||||
|
|
||||||
Admin = Blueprint("admin", __name__, template_folder="templates")
|
Admin = Blueprint("admin", __name__, url_prefix="/admin", template_folder="templates", static_folder="static")
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/admin/")
|
@Admin.route("/")
|
||||||
@roles_required("admin")
|
@roles_required("admin")
|
||||||
def index():
|
def index():
|
||||||
return render_template("admin/index.html", title="admin")
|
return render_template("admin/index.html", title="admin")
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/admin/questions/")
|
@Admin.route("/questions/")
|
||||||
@roles_required("admin")
|
@roles_required("admin")
|
||||||
def questions():
|
def questions():
|
||||||
return render_template("admin/question_list.html")
|
return render_template("admin/question_list.html")
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/admin/questions/edit/<int:question_id>", methods=["GET", "POST"])
|
@Admin.route("/questions/edit/<int:question_id>", methods=["GET", "POST"])
|
||||||
@roles_required("admin")
|
@roles_required("admin")
|
||||||
def edit_question(question_id):
|
def edit_question(question_id):
|
||||||
if request.method == "POST":
|
if request.method == "POST":
|
||||||
@ -32,6 +32,8 @@ def edit_question(question_id):
|
|||||||
multiple_choice_difficulty = request.form.get("multiple_choice_difficulty", None, int)
|
multiple_choice_difficulty = request.form.get("multiple_choice_difficulty", None, int)
|
||||||
wrong_answers = request.form.getlist("wrong_answers")
|
wrong_answers = request.form.getlist("wrong_answers")
|
||||||
hidden_answer_difficulty = request.form.get("hidden_answer_difficulty", None, int)
|
hidden_answer_difficulty = request.form.get("hidden_answer_difficulty", None, int)
|
||||||
|
data["create_multiple_choice"] = request.form.get("create_multiple_choice", False, bool)
|
||||||
|
data["create_hidden_answer"] = request.form.get("create_hidden_answer", False, bool)
|
||||||
if multiple_choice_difficulty is not None:
|
if multiple_choice_difficulty is not None:
|
||||||
data["multiple_choice_difficulty"] = multiple_choice_difficulty
|
data["multiple_choice_difficulty"] = multiple_choice_difficulty
|
||||||
if len(wrong_answers) > 0:
|
if len(wrong_answers) > 0:
|
||||||
@ -40,13 +42,14 @@ def edit_question(question_id):
|
|||||||
data["hidden_answer_difficulty"] = hidden_answer_difficulty
|
data["hidden_answer_difficulty"] = hidden_answer_difficulty
|
||||||
database.update_question(question_id, data)
|
database.update_question(question_id, data)
|
||||||
return redirect(url_for("admin.edit_question", question_id=question_id))
|
return redirect(url_for("admin.edit_question", question_id=question_id))
|
||||||
|
|
||||||
question: database.AllQuestions = get_question(database.AllQuestions, question_id)
|
question: database.AllQuestions = get_question(database.AllQuestions, question_id)
|
||||||
if "application/json" in request.accept_mimetypes.values():
|
if "application/json" in request.accept_mimetypes.values():
|
||||||
return jsonify(question.get_dict())
|
return jsonify(question.get_dict())
|
||||||
return render_template("admin/edit_question.html", question=question)
|
return render_template("admin/edit_question.html", question=question)
|
||||||
|
|
||||||
|
|
||||||
@Admin.route("/admin/question_query")
|
@Admin.route("/question_query")
|
||||||
@roles_required("admin")
|
@roles_required("admin")
|
||||||
def query_questions():
|
def query_questions():
|
||||||
offset = request.args.get("offset", type=int)
|
offset = request.args.get("offset", type=int)
|
||||||
@ -63,7 +66,7 @@ def query_questions():
|
|||||||
response_dict["rows"].append({
|
response_dict["rows"].append({
|
||||||
"id": question.question_id,
|
"id": question.question_id,
|
||||||
"question_id": question.question_id,
|
"question_id": question.question_id,
|
||||||
"question": question.question_text,
|
"question_text": question.question_text,
|
||||||
"answer": question.answer,
|
"answer": question.answer,
|
||||||
"multiple_choice": getattr(question, "multiple_choice", None) is not None,
|
"multiple_choice": getattr(question, "multiple_choice", None) is not None,
|
||||||
"hidden_answer": getattr(question, "hidden_answer", None) is not None,
|
"hidden_answer": getattr(question, "hidden_answer", None) is not None,
|
||||||
|
3
QuizTheWord/admin/static/style.css
Normal file
3
QuizTheWord/admin/static/style.css
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
.add-category-btn {
|
||||||
|
margin-top: 1rem;
|
||||||
|
}
|
@ -7,6 +7,7 @@
|
|||||||
<script src="https://kit.fontawesome.com/f2d4307e62.js" crossorigin="anonymous"></script>
|
<script src="https://kit.fontawesome.com/f2d4307e62.js" crossorigin="anonymous"></script>
|
||||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/css/bootstrap.min.css">
|
||||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css">
|
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootswatch/4.5.2/cosmo/bootstrap.min.css">
|
||||||
|
<link rel="stylesheet" href="{{ url_for('admin.static', filename='style.css') }}">
|
||||||
{% block head %}{% endblock %}
|
{% block head %}{% endblock %}
|
||||||
</head>
|
</head>
|
||||||
<body class="d-flex flex-column h-100">
|
<body class="d-flex flex-column h-100">
|
||||||
|
@ -14,79 +14,155 @@
|
|||||||
<label for="addresses">Bible Verses</label>
|
<label for="addresses">Bible Verses</label>
|
||||||
<input id="addresses" name="addresses" type="text" class="form-control" value="{{ question.addresses }}">
|
<input id="addresses" name="addresses" type="text" class="form-control" value="{{ question.addresses }}">
|
||||||
</div>
|
</div>
|
||||||
{% if question.multiple_choice is not none %}
|
<label class="mt-3">Multiple Choice</label>
|
||||||
<label class="mt-3">Multiple Choice</label>
|
<div class="container border mb-1">
|
||||||
<div class="container border mb-1">
|
<div id="multiple-choice-form" class="form-group"></div>
|
||||||
<div class="form-group">
|
</div>
|
||||||
<label for="multiple_choice_difficulty">Difficulty</label>
|
<label class="mt-3">Hidden Answer</label>
|
||||||
<div class="form-group">
|
<div class="container border mb-1">
|
||||||
<select id="multiple_choice_difficulty" name="multiple_choice_difficulty" class="custom-select">
|
<div id="hidden-answer-form" class="form-group"></div>
|
||||||
<option {% if question.multiple_choice.multiple_choice_difficulty is none %}selected=""{% endif %}>None</option>
|
</div>
|
||||||
<option {% if question.multiple_choice.multiple_choice_difficulty == 1 %}selected=""{% endif %} value="1">Easy</option>
|
|
||||||
<option {% if question.multiple_choice.multiple_choice_difficulty == 2 %}selected=""{% endif %} value="2">Medium</option>
|
|
||||||
<option {% if question.multiple_choice.multiple_choice_difficulty == 3 %}selected=""{% endif %} value="3">Hard</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label>Wrong Answers</label>
|
|
||||||
<ul id="wrong_answer_list" class="list-group">
|
|
||||||
{% for answer in question.multiple_choice.wrong_answers %}
|
|
||||||
<li class="list-group-item d-flex justify-content-between align-items-center wrong_answer">
|
|
||||||
<input id="answer_{{ loop.index0 }}" name="wrong_answers" class="form-control" value="{{ answer }}">
|
|
||||||
<button class="btn" type="button" onclick="remove_item($(this).parent())"><i class="fas fa-times"></i></button>
|
|
||||||
</li>
|
|
||||||
{% endfor %}
|
|
||||||
</ul>
|
|
||||||
<button id="add_wrong_answer" class="btn btn-secondary" type="button">Add Answer</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
{% if question.hidden_answer is not none %}
|
|
||||||
<label class="mt-3">Hidden Answer</label>
|
|
||||||
<div class="container border mb-1">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="hidden_answer_difficulty">Difficulty</label>
|
|
||||||
<div class="form-group">
|
|
||||||
<select id="hidden_answer_difficulty" name="hidden_answer_difficulty" class="custom-select">
|
|
||||||
<option {% if question.hidden_answer.hidden_answer_difficulty is none %}selected=""{% endif %}>None</option>
|
|
||||||
<option {% if question.hidden_answer.hidden_answer_difficulty == 1 %}selected=""{% endif %} value="1">Easy</option>
|
|
||||||
<option {% if question.hidden_answer.hidden_answer_difficulty == 2 %}selected=""{% endif %} value="2">Medium</option>
|
|
||||||
<option {% if question.hidden_answer.hidden_answer_difficulty == 3 %}selected=""{% endif %} value="3">Hard</option>
|
|
||||||
</select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
<button id="save" name="save" type="submit" class="btn btn-primary">Save</button>
|
<button id="save" name="save" type="submit" class="btn btn-primary">Save</button>
|
||||||
</form>
|
</form>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
{% block scripts %}
|
{% block scripts %}
|
||||||
<script>
|
<script>
|
||||||
$("#add_wrong_answer").on("click", () => {
|
let question = {{ question.get_dict()|tojson }};
|
||||||
let item = $("<li>")
|
|
||||||
.addClass("list-group-item d-flex justify-content-between align-items-center wrong_answer")
|
if (question.hasOwnProperty("multiple_choice")) {
|
||||||
.append($("<input>")
|
add_multiple_choice();
|
||||||
.attr("name", "wrong_answers")
|
} else {
|
||||||
.addClass("form-control"))
|
$("#multiple-choice-form")
|
||||||
.append();
|
.append($("<button>")
|
||||||
let btn = $("<button>")
|
.attr({"id": "add-multiple-choice-btn", "type": "button"})
|
||||||
.attr("type", "button")
|
.addClass("btn btn-secondary add-category-btn")
|
||||||
.addClass("btn")
|
.text("Add Multiple Choice")
|
||||||
.on("click", () => {
|
.on("click", () => {
|
||||||
remove_item(item);
|
$("#add-multiple-choice-btn").remove();
|
||||||
})
|
$("#multiple-choice-form").append($("<input>").attr({"hidden": true, "name": "create_multiple_choice", "value": true}))
|
||||||
.append($("<i>")
|
add_multiple_choice();
|
||||||
.addClass("fas fa-times"));
|
}));
|
||||||
item.append(btn);
|
}
|
||||||
$("#wrong_answer_list").append(item);
|
if (question.hasOwnProperty("hidden_answer")) {
|
||||||
})
|
add_hidden_answer();
|
||||||
|
} else {
|
||||||
|
$("#hidden-answer-form")
|
||||||
|
.append($("<button>")
|
||||||
|
.attr({"id": "add-hidden-answer-btn", "type": "button"})
|
||||||
|
.addClass("btn btn-secondary add-category-btn")
|
||||||
|
.text("Add Hidden Answer")
|
||||||
|
.on("click", () => {
|
||||||
|
$("#add-hidden-answer-btn").remove();
|
||||||
|
$("#hidden-answer-form").append($("<input>").attr({"hidden": true, "name": "create_hidden_answer", "value": true}))
|
||||||
|
add_hidden_answer();
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
function remove_item(element) {
|
function remove_item(element) {
|
||||||
if ($("#wrong_answer_list").children().length > 1) {
|
if ($("#wrong_answer_list").children().length > 1) {
|
||||||
element.remove();
|
element.remove();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function create_difficulty_selection(category) {
|
||||||
|
let option_none = $("<option>").text("None")
|
||||||
|
let option_easy = $("<option>").attr("value", 1).text("Easy")
|
||||||
|
let option_medium = $("<option>").attr("value", 2).text("Medium")
|
||||||
|
let option_hard = $("<option>").attr("value", 3).text("Hard")
|
||||||
|
if (question.hasOwnProperty(category)) {
|
||||||
|
switch (question[category]["difficulty"]) {
|
||||||
|
case 1:
|
||||||
|
option_easy.attr("selected", true);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
option_medium.attr("selected", true);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
option_hard.attr("selected", true);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
option_none.attr("selected", true);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
option_none.attr("selected", true);
|
||||||
|
}
|
||||||
|
return $("<div>")
|
||||||
|
.addClass("form-group")
|
||||||
|
.append($("<select>")
|
||||||
|
.addClass("custom-select")
|
||||||
|
.attr({"id": category+"_difficulty", "name": category+"_difficulty"})
|
||||||
|
.append(option_none)
|
||||||
|
.append(option_easy)
|
||||||
|
.append(option_medium)
|
||||||
|
.append(option_hard));
|
||||||
|
}
|
||||||
|
|
||||||
|
function create_wrong_answer_item(text) {
|
||||||
|
let item = $("<li>")
|
||||||
|
.addClass("list-group-item d-flex justify-content-between align-items-center wrong_answer")
|
||||||
|
.append($("<input>")
|
||||||
|
.attr("name", "wrong_answers")
|
||||||
|
.addClass("form-control")
|
||||||
|
.val(text))
|
||||||
|
.append($("<button>")
|
||||||
|
.addClass("btn")
|
||||||
|
.attr("type", "button")
|
||||||
|
.on("click", () => {
|
||||||
|
remove_item(item);
|
||||||
|
})
|
||||||
|
.append($("<i>")
|
||||||
|
.addClass("fas fa-times")));
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_multiple_choice() {
|
||||||
|
let label = $("<label>")
|
||||||
|
.attr("for", "multiple_choice_difficulty")
|
||||||
|
.text("Difficulty");
|
||||||
|
|
||||||
|
let difficulty = create_difficulty_selection("multiple_choice");
|
||||||
|
|
||||||
|
let wrong_answer_list = $("<ul>")
|
||||||
|
.attr("id", "wrong_answer_list")
|
||||||
|
.addClass("list_group pl-0");
|
||||||
|
if (question.hasOwnProperty("multiple_choice")) {
|
||||||
|
$.each(question["multiple_choice"]["wrong_answers"], (i, answer) => {
|
||||||
|
let item = create_wrong_answer_item(answer);
|
||||||
|
wrong_answer_list.append(item);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let item = create_wrong_answer_item("");
|
||||||
|
wrong_answer_list.append(item);
|
||||||
|
}
|
||||||
|
let wrong_answers = $("<div>")
|
||||||
|
.addClass("form-group")
|
||||||
|
.append($("<label>").text("Wrong Answers"))
|
||||||
|
.append(wrong_answer_list)
|
||||||
|
.append($("<button>")
|
||||||
|
.attr({"id": "add_wrong_answer", "type": "button"})
|
||||||
|
.addClass("btn btn-secondary")
|
||||||
|
.text("Add Answer")
|
||||||
|
.on("click", () => {
|
||||||
|
let item = create_wrong_answer_item("");
|
||||||
|
wrong_answer_list.append(item);
|
||||||
|
}));
|
||||||
|
$("#multiple-choice-form")
|
||||||
|
.append(label)
|
||||||
|
.append(difficulty)
|
||||||
|
.append(wrong_answers);
|
||||||
|
}
|
||||||
|
|
||||||
|
function add_hidden_answer() {
|
||||||
|
let label = $("<label>")
|
||||||
|
.attr("for", "hidden_answer_difficulty")
|
||||||
|
.text("Difficulty");
|
||||||
|
|
||||||
|
let difficulty = create_difficulty_selection("hidden_answer");
|
||||||
|
|
||||||
|
$("#hidden-answer-form")
|
||||||
|
.append(label)
|
||||||
|
.append(difficulty);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
@ -54,7 +54,7 @@
|
|||||||
title: "Question ID",
|
title: "Question ID",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
field: "question",
|
field: "question_text",
|
||||||
title: "Question",
|
title: "Question",
|
||||||
filterControl: "input",
|
filterControl: "input",
|
||||||
},
|
},
|
||||||
|
@ -36,16 +36,16 @@ def hidden_answer_category():
|
|||||||
def multiple_choice_category():
|
def multiple_choice_category():
|
||||||
easy = database.get_random_multiple_choice(1)
|
easy = database.get_random_multiple_choice(1)
|
||||||
easy.randomize_answer_list()
|
easy.randomize_answer_list()
|
||||||
# medium = database.get_random_multiple_choice(2)
|
medium = database.get_random_multiple_choice(2)
|
||||||
# medium.randomize_answer_list()
|
medium.randomize_answer_list()
|
||||||
# hard = database.get_random_multiple_choice(3)
|
hard = database.get_random_multiple_choice(3)
|
||||||
# hard.randomize_answer_list()
|
hard.randomize_answer_list()
|
||||||
return render_template(
|
return render_template(
|
||||||
"multiple_choice.html",
|
"multiple_choice.html",
|
||||||
title="Multiple Choice",
|
title="Multiple Choice",
|
||||||
easy=easy,
|
easy=easy,
|
||||||
# medium=medium,
|
medium=medium,
|
||||||
# hard=hard,
|
hard=hard,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ class HiddenAnswer(Base):
|
|||||||
answer = association_proxy("all_question_relationship", "answer")
|
answer = association_proxy("all_question_relationship", "answer")
|
||||||
addresses = association_proxy("all_question_relationship", "addresses")
|
addresses = association_proxy("all_question_relationship", "addresses")
|
||||||
|
|
||||||
def __init__(self, question_id, difficulty, hint, base_question):
|
def __init__(self, question_id, difficulty=None, hint=None, base_question=None):
|
||||||
self.question_id = question_id
|
self.question_id = question_id
|
||||||
self.hidden_answer_difficulty = difficulty
|
self.hidden_answer_difficulty = difficulty
|
||||||
self.hidden_answer_hint = hint
|
self.hidden_answer_hint = hint
|
||||||
@ -145,7 +145,7 @@ class MultipleChoice(Base):
|
|||||||
answer = association_proxy("all_question_relationship", "answer")
|
answer = association_proxy("all_question_relationship", "answer")
|
||||||
addresses = association_proxy("all_question_relationship", "addresses")
|
addresses = association_proxy("all_question_relationship", "addresses")
|
||||||
|
|
||||||
def __init__(self, question_id, difficulty, hint, wrong_answers, base_question):
|
def __init__(self, question_id, difficulty=None, hint=None, wrong_answers=None, base_question=None):
|
||||||
self.question_id = question_id
|
self.question_id = question_id
|
||||||
self.multiple_choice_difficulty = difficulty
|
self.multiple_choice_difficulty = difficulty
|
||||||
self.multiple_choice_hint = hint
|
self.multiple_choice_hint = hint
|
||||||
@ -259,6 +259,14 @@ def query_all_questions(offset, limit, query: dict = None, sort=None, order=None
|
|||||||
def update_question(question_id, data):
|
def update_question(question_id, data):
|
||||||
session = get_session()
|
session = get_session()
|
||||||
question: AllQuestions = session.query(AllQuestions).get(question_id)
|
question: AllQuestions = session.query(AllQuestions).get(question_id)
|
||||||
|
if data["create_multiple_choice"]:
|
||||||
|
multiple_choice = MultipleChoice(question_id)
|
||||||
|
session.add(multiple_choice)
|
||||||
|
question.multiple_choice = multiple_choice
|
||||||
|
if data["create_hidden_answer"]:
|
||||||
|
hidden_answer = HiddenAnswer(question_id)
|
||||||
|
session.add(hidden_answer)
|
||||||
|
question.hidden_answer = hidden_answer
|
||||||
for column in data.keys():
|
for column in data.keys():
|
||||||
if column in AllQuestions.__table__.columns:
|
if column in AllQuestions.__table__.columns:
|
||||||
setattr(question, column, data[column])
|
setattr(question, column, data[column])
|
||||||
|
@ -11,6 +11,22 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div id="medium-question" class="container-md align-content-center question" style="display: none">
|
||||||
|
<div class="card question-text">
|
||||||
|
<p id="medium-question">{{ medium.question_text }}</p>
|
||||||
|
{% for answer in medium.answer_list %}
|
||||||
|
<button id="{{ medium.question_id }}-{{ loop.index0 }}" class="btn btn-secondary m-1" onclick="checkAnswer({{ medium.question_id }}, {{ loop.index0 }}, '{{ answer }}')">{{ answer }}</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="hard-question" class="container-md align-content-center question" style="display: none">
|
||||||
|
<div class="card question-text">
|
||||||
|
<p id="hard-question">{{ hard.question_text }}</p>
|
||||||
|
{% for answer in hard.answer_list %}
|
||||||
|
<button id="{{ hard.question_id }}-{{ loop.index0 }}" class="btn btn-secondary m-1" onclick="checkAnswer({{ hard.question_id }}, {{ loop.index0 }}, '{{ answer }}')">{{ answer }}</button>
|
||||||
|
{% endfor %}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="text-center difficulty">
|
<div class="text-center difficulty">
|
||||||
<div class="btn-group btn-group-toggle" data-toggle="buttons" onclick="change_difficulty()">
|
<div class="btn-group btn-group-toggle" data-toggle="buttons" onclick="change_difficulty()">
|
||||||
|
Loading…
Reference in New Issue
Block a user