Files
ACORE/core/access_summary.py
lokses a62f023a90 employees: сводка итоговых разрешений на карточке сотрудника
На карточке Employee — вкладка «Доступ (сводка)»: по каждому
ограниченному разделу портала показывает итог (есть/нет) и источник
(какая группа дала право, individual grant/revoke override, личное право).

Считается теми же правилами, что Employee.has_perm (override имеет
приоритет над группами), поэтому сводка 1-в-1 совпадает с реальной
проверкой доступа. Править по-прежнему: поле «Группы» + инлайн
«Доп. права на разделы портала» (grant/revoke).

- core/access_summary.py: employee_access_summary()
- EmployeeAdmin: readonly effective_access_summary + вкладка
- core/tests_access_summary.py: 6 тестов (в т.ч. совпадение с has_perm)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-09-21 17:21:58 +03:00

92 lines
3.4 KiB
Python

"""Сводка итоговых разрешений сотрудника на разделы портала.
Считает по тем же правилам, что и Employee.has_perm:
override (grant/revoke) имеет приоритет над группами и личными правами.
Источник каждого доступа показывается администратору на карточке сотрудника.
"""
from __future__ import annotations
from dataclasses import dataclass, field
from django.utils import timezone
from core.portal_sections import RESTRICTED_KEYS, get_section
@dataclass
class AccessRow:
key: str
label: str
allowed: bool
override: str | None = None # 'grant' | 'revoke' | None
group_sources: list[str] = field(default_factory=list)
individual: bool = False # прямое user_permission (не через override)
def source_text(self) -> str:
if self.override == 'grant':
return 'Индивидуально: выдано (override)'
if self.override == 'revoke':
return 'Индивидуально: отозвано (override)'
parts = []
if self.group_sources:
parts.append('Группы: ' + ', '.join(self.group_sources))
if self.individual:
parts.append('личное право')
return ' + '.join(parts) if parts else '—'
def _perm_for_key(key: str) -> str:
"""Право раздела: core.access_<key> (см. portal_permission_codenames)."""
return f'access_{key}'
def employee_access_summary(employee) -> list[AccessRow]:
"""Список строк доступа сотрудника по ограниченным разделам портала."""
today = timezone.now().date()
# codename -> [имена групп, дающих это право]
group_perm_map: dict[str, list[str]] = {}
for group in employee.groups.prefetch_related('permissions').all():
for perm in group.permissions.all():
group_perm_map.setdefault(perm.codename, []).append(group.name)
# прямые личные права (не через override)
individual_codes = set(
employee.user_permissions.values_list('codename', flat=True)
)
# действующие сейчас override-ы
overrides: dict[str, str] = {}
for ov in employee.permission_overrides.filter(is_active=True).select_related('permission'):
if ov.valid_from and ov.valid_from > today:
continue
if ov.valid_until and ov.valid_until < today:
continue
overrides[ov.permission.codename] = ov.override_type
rows: list[AccessRow] = []
for key in RESTRICTED_KEYS:
section = get_section(key)
label = section.label if section else key
codename = _perm_for_key(key)
override = overrides.get(codename)
group_sources = group_perm_map.get(codename, [])
individual = codename in individual_codes
if override == 'revoke':
allowed = False
elif override == 'grant':
allowed = True
else:
allowed = bool(group_sources) or individual
rows.append(AccessRow(
key=key,
label=label,
allowed=allowed,
override=override,
group_sources=group_sources,
individual=individual,
))
return rows