Дорабоки интерфейса системы тестирования. Раздел 1 Шапка+Верхний brick

This commit is contained in:
Константин Лебединский
2026-04-29 14:55:43 +05:00
parent 1c4dacbc85
commit eff3fda5b0
34 changed files with 3339 additions and 576 deletions
+112
View File
@@ -42,6 +42,14 @@ from ..services.document_extract import (
from ..services.document_gen import generation_for_import_document
from ..services.draft_validator import LlmError
from ..services.editor_content import HttpError as EditorHttpError, get_editor_content
from ..services.test_attempt import (
HttpError as AttemptHttpError,
get_attempt_review_for_user,
get_play_content,
list_test_attempts_for_author,
start_attempt,
submit_attempt,
)
from ..services.test_access import is_test_author, list_hidden_by_author, list_visible_tests
from ..services.test_chain import has_any_attempt_for_test
from ..services.test_draft import (
@@ -150,6 +158,10 @@ def api_test_summary(test_id):
if not acc.ok:
return jsonify(error=RU['notFound']), 404
has_attempts = False
with eng.connect() as conn:
has_attempts = has_any_attempt_for_test(conn, test_id)
return jsonify(
test={
'id': str(row['id']),
@@ -163,6 +175,7 @@ def api_test_summary(test_id):
'updatedAt': row['updated_at'].isoformat() if row['updated_at'] else None,
'createdBy': str(row['created_by']) if row['created_by'] else None,
'authorFullName': row['author_full_name'],
'hasAttempts': bool(has_attempts),
},
isAuthor=is_author,
hasActiveVersion=row['active_version_id'] is not None,
@@ -293,6 +306,85 @@ def api_patch_test(test_id):
return jsonify(id=test_id, chainActive=chain)
@tests_bp.route('/api/tests/<test_id>/attempts/start', methods=['POST'])
@login_required
def api_start_attempt(test_id):
user = current_user()
eng = get_engine()
try:
out = start_attempt(eng, user.id, test_id)
except AttemptHttpError as e:
return jsonify(error=e.message), e.status
return jsonify(out), 201
@tests_bp.route('/api/tests/<test_id>/attempts/<attempt_id>/play', methods=['GET'])
@login_required
def api_attempt_play(test_id, attempt_id):
user = current_user()
eng = get_engine()
try:
out = get_play_content(eng, user.id, test_id, attempt_id)
except AttemptHttpError as e:
return jsonify(error=e.message), e.status
return jsonify(out)
@tests_bp.route('/api/tests/<test_id>/attempts/<attempt_id>/submit', methods=['POST'])
@login_required
def api_attempt_submit(test_id, attempt_id):
user = current_user()
eng = get_engine()
body = request.get_json(silent=True) or {}
try:
out = submit_attempt(eng, user.id, test_id, attempt_id, body.get('answers'))
except AttemptHttpError as e:
return jsonify(error=e.message), e.status
return jsonify(out)
@tests_bp.route('/api/tests/<test_id>/attempts/<attempt_id>/review', methods=['GET'])
@login_required
def api_attempt_review(test_id, attempt_id):
user = current_user()
eng = get_engine()
try:
out = get_attempt_review_for_user(eng, user.id, test_id, attempt_id)
except AttemptHttpError as e:
return jsonify(error=e.message), e.status
return jsonify(out)
@tests_bp.route('/api/tests/<test_id>/attempts', methods=['GET'])
@login_required
def api_attempts_list(test_id):
user = current_user()
eng = get_engine()
try:
rows = list_test_attempts_for_author(eng, user.id, test_id)
except AttemptHttpError as e:
return jsonify(error=e.message), e.status
return jsonify(
attempts=[
{
'id': str(r['id']),
'userId': str(r['user_id']),
'status': r['status'],
'attemptNumber': r['attempt_number'],
'startedAt': r['started_at'].isoformat() if r['started_at'] else None,
'completedAt': r['completed_at'].isoformat() if r['completed_at'] else None,
'correctCount': r['correct_count'],
'totalQuestions': r['total_questions'],
'passed': r['passed'],
'testVersion': r['test_version'],
'attempterName': r['attempter_name'],
'attempterLogin': r['attempter_login'],
}
for r in rows
]
)
# ─── AI ──────────────────────────────────────────────────────────────
@tests_bp.route('/api/tests/<test_id>/ai/generate-test', methods=['POST'])
@@ -463,3 +555,23 @@ def tests_editor_page(test_id):
return ('Доступ запрещён.', 403)
return render_template('500.html'), 500
return render_template('tests/editor.html', content=content, test_id=test_id)
@tests_bp.route('/tests/<test_id>/attempt/<attempt_id>', methods=['GET'])
@login_required
def tests_attempt_page(test_id, attempt_id):
return render_template('tests/attempt.html', test_id=test_id, attempt_id=attempt_id)
@tests_bp.route('/tests/<test_id>/attempts/<attempt_id>/review', methods=['GET'])
@login_required
def tests_attempt_review_page(test_id, attempt_id):
user = current_user()
eng = get_engine()
try:
review = get_attempt_review_for_user(eng, user.id, test_id, attempt_id)
except AttemptHttpError as e:
if e.status == 404:
return render_template('404.html'), 404
return (e.message, e.status)
return render_template('tests/attempt_review.html', test_id=test_id, review=review)