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.

156 lines
5.2 KiB

"""Главный blueprint — посадочная страница, статистика и health-чек."""
from __future__ import annotations
from flask import Blueprint, jsonify, render_template
from .. import db as app_db
from ..auth.decorators import login_required
from ..auth.decorators import current_user as get_current_user
main_bp = Blueprint('main', __name__)
def _get_stats() -> dict:
"""Собирает статистику прохождений для дашборда."""
from sqlalchemy import func, case
from ..db import get_session
from ..models import Department, User, Test, TestAttempt
session = get_session()
try:
total_tests = session.query(func.count(Test.id)).scalar() or 0
active_tests = session.query(func.count(Test.id)).filter(Test.is_active.is_(True)).scalar() or 0
total_completed = (
session.query(func.count(TestAttempt.id))
.filter(TestAttempt.status == 'completed')
.scalar() or 0
)
total_passed = (
session.query(func.count(TestAttempt.id))
.filter(TestAttempt.status == 'completed', TestAttempt.passed.is_(True))
.scalar() or 0
)
total_users = session.query(func.count(User.id)).filter(User.is_active.is_(True)).scalar() or 0
# Статистика по отделам
dept_rows = (
session.query(
Department.name,
func.count(TestAttempt.id).label('total'),
func.sum(
case((TestAttempt.passed.is_(True), 1), else_=0)
).label('passed_count'),
)
.join(User, User.department_id == Department.id)
.join(TestAttempt, TestAttempt.user_id == User.id)
.filter(TestAttempt.status == 'completed')
.group_by(Department.id, Department.name)
.order_by(Department.name)
.all()
)
from ..models import TestVersion
recent_rows = (
session.query(
TestAttempt.started_at,
TestAttempt.completed_at,
TestAttempt.passed,
TestAttempt.correct_count,
TestAttempt.total_questions,
User.full_name,
User.login,
Test.title.label('test_title'),
)
.join(User, User.id == TestAttempt.user_id)
.join(TestVersion, TestVersion.id == TestAttempt.test_version_id)
.join(Test, Test.id == TestVersion.test_id)
.filter(TestAttempt.status == 'completed')
.order_by(TestAttempt.completed_at.desc())
.limit(10)
.all()
)
dept_stats = [
{
'name': r.name,
'total': r.total,
'passed': int(r.passed_count or 0),
'rate': round(100 * int(r.passed_count or 0) / r.total) if r.total else 0,
}
for r in dept_rows
]
recent_list = [
{
'user': r.full_name or r.login,
'test': r.test_title,
'passed': r.passed,
'score': f'{r.correct_count}/{r.total_questions}' if r.correct_count is not None else '',
'at': r.completed_at.strftime('%d.%m %H:%M') if r.completed_at else '',
}
for r in recent_rows
]
pass_rate = round(100 * total_passed / total_completed) if total_completed else 0
return {
'total_tests': total_tests,
'active_tests': active_tests,
'total_completed': total_completed,
'total_passed': total_passed,
'total_users': total_users,
'pass_rate': pass_rate,
'dept_stats': dept_stats,
'recent': recent_list,
}
except Exception:
return {
'total_tests': 0, 'active_tests': 0, 'total_completed': 0,
'total_passed': 0, 'total_users': 0, 'pass_rate': 0,
'dept_stats': [], 'recent': [],
}
@main_bp.route('/health')
def health():
"""Smoke-проверка приложения и подключений к БД (без авторизации)."""
db_status = app_db.ping()
overall = 'ok' if db_status.get('main') == 'ok' else 'degraded'
return jsonify(
status=overall,
service='testing-flask-app',
db=db_status,
)
@main_bp.route('/')
@login_required
def index():
return render_template('index.html')
@main_bp.route('/stats')
@login_required
def stats_page():
return render_template('stats.html', stats=_get_stats())
@main_bp.route('/assignments')
@login_required
def assignments_page():
from ..db import get_session
from ..models import Test, TestVersion
from sqlalchemy.orm import selectinload
session = get_session()
tests = (
session.query(Test)
.join(TestVersion, (TestVersion.test_id == Test.id) & TestVersion.is_active.is_(True))
.filter(Test.is_active.is_(True))
.order_by(Test.title)
.all()
)
return render_template('assignments.html', tests=[
{'id': str(t.id), 'title': t.title or '(без названия)'}
for t in tests
])