You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
3.5 KiB
105 lines
3.5 KiB
"""Контент редактора: тест + активная версия + дерево вопросов с правильными вариантами.""" |
|
from __future__ import annotations |
|
|
|
import uuid as _uuid |
|
|
|
from sqlalchemy import func |
|
from sqlalchemy.orm import selectinload |
|
|
|
from ..db import get_session |
|
from ..messages import RU |
|
from ..models import AnswerOption, Question, Test, TestVersion |
|
from .test_access import is_test_author, is_test_edit_open |
|
from .test_chain import has_any_attempt_for_test |
|
|
|
|
|
class HttpError(Exception): |
|
def __init__(self, status: int, message: str): |
|
super().__init__(message) |
|
self.status = status |
|
self.message = message |
|
|
|
|
|
def load_questions_for_version(session, test_version_id, *, include_correct: bool) -> list[dict]: |
|
if not isinstance(test_version_id, _uuid.UUID): |
|
try: |
|
test_version_id = _uuid.UUID(str(test_version_id)) |
|
except (ValueError, AttributeError): |
|
return [] |
|
|
|
questions = ( |
|
session.query(Question) |
|
.options(selectinload(Question.options)) |
|
.filter(Question.test_version_id == test_version_id) |
|
.order_by(Question.question_order) |
|
.all() |
|
) |
|
out = [] |
|
for q in questions: |
|
options = [] |
|
for o in sorted(q.options, key=lambda x: x.option_order): |
|
base = { |
|
'id': str(o.id), |
|
'text': o.text, |
|
'optionOrder': o.option_order, |
|
} |
|
if include_correct: |
|
base['isCorrect'] = bool(o.is_correct) |
|
options.append(base) |
|
out.append({ |
|
'id': str(q.id), |
|
'text': q.text, |
|
'questionOrder': q.question_order, |
|
'hasMultipleAnswers': bool(q.has_multiple_answers), |
|
'aiHint': (q.ai_hint or '').strip() or None, |
|
'options': options, |
|
}) |
|
return out |
|
|
|
|
|
def get_editor_content(user_id: str, test_id: str) -> dict: |
|
session = get_session() |
|
try: |
|
tid = _uuid.UUID(test_id) |
|
except (ValueError, AttributeError): |
|
raise HttpError(404, 'Тест не найден.') |
|
|
|
test = session.get(Test, tid) |
|
if not test: |
|
raise HttpError(404, 'Тест не найден.') |
|
if not is_test_author(test.created_by, user_id) and not is_test_edit_open(): |
|
raise HttpError(403, 'Доступ запрещён.') |
|
|
|
active_version = ( |
|
session.query(TestVersion) |
|
.filter(TestVersion.test_id == tid, TestVersion.is_active.is_(True)) |
|
.first() |
|
) |
|
if not active_version: |
|
raise HttpError(400, 'Нет активной версии теста.') |
|
|
|
version_count = ( |
|
session.query(func.count(TestVersion.id)) |
|
.filter(TestVersion.test_id == tid) |
|
.scalar() or 0 |
|
) |
|
|
|
questions = load_questions_for_version(session, active_version.id, include_correct=True) |
|
has_attempts = has_any_attempt_for_test(session, tid) |
|
|
|
return { |
|
'test': { |
|
'id': str(test.id), |
|
'title': test.title, |
|
'description': test.description, |
|
'passingThreshold': test.passing_threshold, |
|
'timeLimit': test.time_limit, |
|
'hintsEnabled': bool(test.hints_enabled), |
|
'resultMode': test.result_mode or 'end', |
|
'hasAttempts': bool(has_attempts), |
|
'versionCount': version_count, |
|
'hasForkRisk': bool(has_attempts) or version_count > 1, |
|
}, |
|
'activeVersionId': str(active_version.id), |
|
'questions': questions, |
|
}
|
|
|