Дорабоки интерфейса системы тестирования. Раздел 1 Шапка+Верхний brick
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user