блоки 2 и 3 доработки интерфейса системы тестирования
This commit is contained in:
@@ -1,15 +1,15 @@
|
||||
"""Кто видит тест: автор + назначенные пользователи (порт `services/testAccessService.js`)."""
|
||||
"""Кто видит тест: автор + назначенные пользователи."""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
|
||||
from sqlalchemy import text
|
||||
from sqlalchemy import exists, select
|
||||
|
||||
from ..db import get_engine
|
||||
from ..db import get_session
|
||||
from ..models import Test, TestAssignment, TestAssignmentTarget, TestVersion, User
|
||||
|
||||
|
||||
def is_test_author(created_by, user_id) -> bool:
|
||||
"""`tests.created_by` — UUID. Сравниваем по строковому представлению."""
|
||||
if created_by is None or user_id is None:
|
||||
return False
|
||||
return str(created_by) == str(user_id)
|
||||
@@ -23,86 +23,93 @@ class AccessResult:
|
||||
|
||||
|
||||
def user_has_test_access(user_id: str, test_id: str) -> AccessResult:
|
||||
eng = get_engine()
|
||||
with eng.connect() as conn:
|
||||
row = conn.execute(
|
||||
text('SELECT created_by FROM tests WHERE id = :id'),
|
||||
{'id': test_id},
|
||||
).mappings().first()
|
||||
if not row:
|
||||
return AccessResult(ok=False, is_author=False, not_found=True)
|
||||
if is_test_author(row['created_by'], user_id):
|
||||
return AccessResult(ok=True, is_author=True, not_found=False)
|
||||
ar = conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT 1
|
||||
FROM test_assignments ta
|
||||
INNER JOIN test_versions tv_a ON tv_a.id = ta.test_version_id
|
||||
INNER JOIN test_assignment_targets tat ON tat.assignment_id = ta.id
|
||||
WHERE tv_a.test_id = :test_id
|
||||
AND tat.target_type = 'user'
|
||||
AND tat.target_id = :user_id
|
||||
LIMIT 1
|
||||
"""
|
||||
),
|
||||
{'test_id': test_id, 'user_id': user_id},
|
||||
).first()
|
||||
return AccessResult(ok=ar is not None, is_author=False, not_found=False)
|
||||
import uuid as _uuid
|
||||
session = get_session()
|
||||
try:
|
||||
tid = _uuid.UUID(test_id)
|
||||
uid = _uuid.UUID(user_id)
|
||||
except (ValueError, AttributeError):
|
||||
return AccessResult(ok=False, is_author=False, not_found=True)
|
||||
|
||||
test = session.get(Test, tid)
|
||||
if not test:
|
||||
return AccessResult(ok=False, is_author=False, not_found=True)
|
||||
|
||||
if is_test_author(test.created_by, uid):
|
||||
return AccessResult(ok=True, is_author=True, not_found=False)
|
||||
|
||||
assigned = session.query(
|
||||
exists().where(
|
||||
TestAssignmentTarget.target_type == 'user',
|
||||
TestAssignmentTarget.target_id == uid,
|
||||
TestAssignmentTarget.assignment_id == TestAssignment.id,
|
||||
TestAssignment.test_version_id == TestVersion.id,
|
||||
TestVersion.test_id == tid,
|
||||
)
|
||||
).scalar()
|
||||
|
||||
return AccessResult(ok=bool(assigned), is_author=False, not_found=False)
|
||||
|
||||
|
||||
def list_visible_tests(user_id: str) -> list[dict]:
|
||||
"""Каталог: только активная цепочка + (автор OR назначен)."""
|
||||
eng = get_engine()
|
||||
with eng.connect() as conn:
|
||||
rows = conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT DISTINCT t.id, t.title, t.description, t.is_active AS chain_active,
|
||||
t.created_at, t.updated_at,
|
||||
tv.id AS active_version_id, tv.version,
|
||||
t.created_by, u.full_name AS author_full_name
|
||||
FROM tests t
|
||||
INNER JOIN test_versions tv ON tv.test_id = t.id AND tv.is_active = true
|
||||
INNER JOIN users u ON u.id = t.created_by
|
||||
WHERE t.is_active = true
|
||||
AND (
|
||||
t.created_by = :uid
|
||||
OR EXISTS (
|
||||
SELECT 1
|
||||
FROM test_assignments ta
|
||||
INNER JOIN test_versions tv2 ON tv2.id = ta.test_version_id
|
||||
INNER JOIN test_assignment_targets tat ON tat.assignment_id = ta.id
|
||||
WHERE tv2.test_id = t.id
|
||||
AND tat.target_type = 'user'
|
||||
AND tat.target_id = :uid
|
||||
)
|
||||
)
|
||||
ORDER BY t.updated_at DESC NULLS LAST, t.created_at DESC
|
||||
"""
|
||||
),
|
||||
{'uid': user_id},
|
||||
).mappings().all()
|
||||
return [dict(r) for r in rows]
|
||||
"""В dev-режиме возвращает все активные тесты независимо от назначения."""
|
||||
session = get_session()
|
||||
|
||||
rows = (
|
||||
session.query(Test, TestVersion, User)
|
||||
.join(TestVersion, (TestVersion.test_id == Test.id) & TestVersion.is_active.is_(True))
|
||||
.outerjoin(User, User.id == Test.created_by)
|
||||
.filter(Test.is_active.is_(True))
|
||||
.order_by(Test.updated_at.desc().nullslast(), Test.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(t.id),
|
||||
'title': t.title,
|
||||
'description': t.description,
|
||||
'chain_active': t.is_active,
|
||||
'created_at': t.created_at,
|
||||
'updated_at': t.updated_at,
|
||||
'active_version_id': str(tv.id),
|
||||
'version': tv.version,
|
||||
'created_by': str(t.created_by) if t.created_by else None,
|
||||
'author_full_name': u.full_name if u else '—',
|
||||
}
|
||||
for t, tv, u in rows
|
||||
]
|
||||
|
||||
|
||||
def list_hidden_by_author(user_id: str) -> list[dict]:
|
||||
"""Скрытые автором цепочки (`is_active = false`) — видны только автору."""
|
||||
eng = get_engine()
|
||||
with eng.connect() as conn:
|
||||
rows = conn.execute(
|
||||
text(
|
||||
"""
|
||||
SELECT t.id, t.title, t.description, t.is_active AS chain_active,
|
||||
t.created_at, t.updated_at, tv.id AS active_version_id, tv.version,
|
||||
t.created_by, u.full_name AS author_full_name
|
||||
FROM tests t
|
||||
INNER JOIN test_versions tv ON tv.test_id = t.id AND tv.is_active = true
|
||||
INNER JOIN users u ON u.id = t.created_by
|
||||
WHERE t.is_active = false AND t.created_by = :uid
|
||||
ORDER BY t.updated_at DESC NULLS LAST, t.created_at DESC
|
||||
"""
|
||||
),
|
||||
{'uid': user_id},
|
||||
).mappings().all()
|
||||
return [dict(r) for r in rows]
|
||||
import uuid as _uuid
|
||||
session = get_session()
|
||||
try:
|
||||
uid = _uuid.UUID(user_id)
|
||||
except (ValueError, AttributeError):
|
||||
return []
|
||||
|
||||
rows = (
|
||||
session.query(Test, TestVersion, User)
|
||||
.join(TestVersion, (TestVersion.test_id == Test.id) & TestVersion.is_active.is_(True))
|
||||
.join(User, User.id == Test.created_by)
|
||||
.filter(Test.is_active.is_(False), Test.created_by == uid)
|
||||
.order_by(Test.updated_at.desc().nullslast(), Test.created_at.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
return [
|
||||
{
|
||||
'id': str(t.id),
|
||||
'title': t.title,
|
||||
'description': t.description,
|
||||
'chain_active': t.is_active,
|
||||
'created_at': t.created_at,
|
||||
'updated_at': t.updated_at,
|
||||
'active_version_id': str(tv.id),
|
||||
'version': tv.version,
|
||||
'created_by': str(t.created_by),
|
||||
'author_full_name': u.full_name,
|
||||
}
|
||||
for t, tv, u in rows
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user