Smarter/smarter/question_sets.py
Arija A. a3a6316054
.html -> .j2 for Jinja2 correctness
Signed-off-by: Arija A. <ari@ari.lt>
2025-05-27 23:34:42 +03:00

214 lines
6.8 KiB
Python

from flask import (
Blueprint, render_template, request,
flash, redirect, url_for, g
)
from better_profanity import profanity
from .db import get_db
from .constants import categories
from .auth import login_required
from .utility import (
submitQuestion, deleteSetQuestions, getQuestionSets
)
bp = Blueprint("question_sets", __name__, url_prefix="/question-sets")
@bp.route("/browse")
def browse():
question_sets = getQuestionSets()
return render_template(
"question-sets/browse.j2",
question_sets=question_sets["question_sets"],
owned_question_sets=question_sets["owned_question_sets"],
for_game=False
)
@bp.route("/add", methods=["GET", "POST"])
@login_required()
def add():
if request.method == "GET":
return render_template("question-sets/add.j2")
name = request.form.get("name").strip()
error = None
if not name:
error = "Name is required"
elif profanity.contains_profanity(name):
error = "Question set name contains profanity"
elif get_db().execute("SELECT 1 FROM question_sets WHERE name = ?",
(name,)).fetchone():
error = "This question set name is already used"
if error is not None:
flash(error)
return render_template("question-sets/add.j2")
return redirect(url_for(
"question_sets.add_user_generated",
name=name, private=request.form.get("private")
))
@bp.route("/add/user-generated")
@login_required()
def add_user_generated():
questions = get_db().execute(
"""SELECT q.id, q.category, q.difficulty,
q.question, u.username AS creator
FROM questions AS q JOIN users AS u ON u.id = q.creator_id
WHERE q.source = 'user' AND q.verified = 1"""
).fetchall()
# Replace category numbers with strings
for question in questions:
question["category"] = categories[question["category"]]
return render_template(
"question-sets/add_user_questions.j2", questions=questions
)
@bp.route("/add/opentdb")
@login_required()
def add_opentdb():
return render_template("question-sets/add_otdb_questions.j2")
@bp.route("/check_question/<int:id>")
@login_required()
def check_question(id):
return {
"id_ok": bool(
get_db().execute(
"""SELECT 1 FROM questions WHERE id = ? AND
source = 'user' AND verified = 1""",
(id,)
).fetchone()
)
}
@bp.route("/submit", methods=["POST"])
@login_required()
def submit_set():
request_json = request.get_json()
name = request_json["name"].strip()
private = request_json["private"]
userQuestions = request_json["user_questions"]
otdbQuestions = request_json["otdb_questions"]
# Only private question sets can have questions
# from the Open Trivia Database
if not private and otdbQuestions:
flash("Non-private question sets can only have user created questions")
return {"success": False}
if len(userQuestions) + len(otdbQuestions) > 50:
flash("Limit of 50 questions exceeded")
return {"success": False}
if len(userQuestions) + len(otdbQuestions) < 5:
flash("Not enough questions provided")
return {"success": False}
db = get_db()
try:
# Create the question_set entry
question_set_id = db.execute(
"""INSERT INTO question_sets (name, creator_id, private)
VALUES (?, ?, ?)""", (name, int(g.user["id"]), int(private))
).lastrowid
used_ids = []
# Add otdb questions to the database and
# connect them to the question_set
for question in otdbQuestions:
# See if question already exists in the database
question_id = db.execute(
"""SELECT id FROM questions WHERE source = ? AND question = ?
AND type = ? AND category = ? AND difficulty = ?""",
("opentdb", question["question"], question["type"],
int(question["category"]), question["difficulty"])
).fetchone()
# Add question to DB if it doesn't exist
if question_id is None:
question_id, error = submitQuestion(
"opentdb", question["type"], None, question["category"],
question["difficulty"], question["question"],
question["correct_answer"], question["incorrect_answers"]
)
else:
question_id = question_id["id"]
# Filter duplicate questions
if question_id not in used_ids:
db.execute(
"""INSERT INTO question_set_questions (question_set_id,
question_id) VALUES (?, ?)""",
(question_set_id, question_id)
)
used_ids.append(question_id)
used_ids.clear()
# Connect user questions with the question set
for question in userQuestions:
# Don't accept question ids that have not
# been verified or don't exist
if not db.execute(
"""SELECT 1 FROM questions WHERE id = ? AND
source = 'user' AND verified = 1""",
(question,)
).fetchone():
flash("Some questions are not verified or user-generated")
return {"success": False}
# Filter duplicate questions
if question not in used_ids:
db.execute(
"""INSERT INTO question_set_questions (question_set_id,
question_id) VALUES (?, ?)""",
(question_set_id, question)
)
used_ids.append(question)
db.commit()
except (db.IntegrityError, ValueError):
flash(
"""An unexpected error occured while processing your request,
please try again later"""
)
return {"success": False}
flash("Question set successfully created")
return {"success": True}
@bp.route("/remove/<int:id>", methods=["POST"])
@login_required()
def remove(id):
db = get_db()
if not db.execute(
"SELECT 1 FROM question_sets WHERE id = ? AND creator_id = ?",
(id, g.user["id"])
).fetchone():
flash("You are not permitted to delete this set")
return redirect(url_for("question_sets.browse"), 403)
used_in_game = bool(db.execute(
"SELECT 1 FROM games WHERE question_set_id = ?",
(id,)
).fetchone())
if used_in_game:
flash("This question set is used in a game and cannot be deleted")
return redirect(url_for("question_sets.browse"))
db.execute("DELETE FROM question_sets WHERE id = ?", (id,))
deleteSetQuestions(id)
flash("Question set deleted")
return redirect(url_for("question_sets.browse"))