09d996ead0
- Alembic 0003: колонка ai_hint (TEXT NULL) - API черновика: отдаём aiHint, сохраняем из payload - Карточка вопроса: textarea подсказки для прохождения Made-with: Cursor
106 lines
3.4 KiB
Python
106 lines
3.4 KiB
Python
"""Контент редактора: тест + активная версия + дерево вопросов с правильными вариантами."""
|
|
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
|
|
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):
|
|
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,
|
|
}
|