a62f023a90
На карточке 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>
92 lines
3.4 KiB
Python
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
|