ae54d93442
Section-based portal access, matrix прав по группам, аккаунтинг-портал, управление группами пользователей, справочники, дашборд и общий каркас навигации/стилей. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
86 lines
3.3 KiB
Python
86 lines
3.3 KiB
Python
"""Dev-only: просмотр портала «как» роль/группа с её ограничениями.
|
|
|
|
Включается только при DEBUG. Состояние — в session['dev_access_preview'].
|
|
Реальные права пользователя не меняются; меняется только проверка
|
|
user_can_access / user_can_manage_hr_portal.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass
|
|
|
|
from django.conf import settings
|
|
from django.contrib.auth.models import Group
|
|
|
|
SESSION_KEY = 'dev_access_preview'
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class AccessPreviewPreset:
|
|
id: str
|
|
label: str
|
|
group_names: tuple[str, ...] = ()
|
|
|
|
|
|
# Пустой id = без превью (полный доступ admin/superuser как обычно).
|
|
ACCESS_PREVIEW_PRESETS: tuple[AccessPreviewPreset, ...] = (
|
|
AccessPreviewPreset('', 'Как есть (мой доступ)'),
|
|
AccessPreviewPreset('pharmacist', 'Фармацевт', ('Фармацевт',)),
|
|
AccessPreviewPreset('pharmacy_manager', 'Заведующий аптекой', ('Заведующий аптекой',)),
|
|
AccessPreviewPreset('office', 'Офисный сотрудник', ('Офисный сотрудник',)),
|
|
AccessPreviewPreset('hr', 'HR', ('HR',)),
|
|
AccessPreviewPreset('it', 'IT', ('IT',)),
|
|
AccessPreviewPreset('network_admin', 'Администратор сети', ('Администратор сети',)),
|
|
AccessPreviewPreset('basic', 'Только базовые разделы', ()),
|
|
)
|
|
|
|
_PRESETS_BY_ID = {p.id: p for p in ACCESS_PREVIEW_PRESETS}
|
|
|
|
|
|
def access_preview_available(user) -> bool:
|
|
if not getattr(settings, 'DEBUG', False):
|
|
return False
|
|
if not user or not getattr(user, 'is_authenticated', False):
|
|
return False
|
|
return bool(getattr(user, 'is_staff', False) or getattr(user, 'is_superuser', False))
|
|
|
|
|
|
def get_preview_id(user) -> str:
|
|
return (getattr(user, '_access_preview', None) or '').strip()
|
|
|
|
|
|
def resolve_preset(preview_id: str | None) -> AccessPreviewPreset | None:
|
|
if preview_id is None:
|
|
return None
|
|
return _PRESETS_BY_ID.get(preview_id)
|
|
|
|
|
|
def preview_permission_strings(preview_id: str) -> frozenset[str] | None:
|
|
"""Набор 'app_label.codename' для пресета. None — превью выключено."""
|
|
preset = resolve_preset(preview_id or '')
|
|
if preset is None or not preset.id:
|
|
return None
|
|
if not preset.group_names:
|
|
return frozenset()
|
|
codes: set[str] = set()
|
|
groups = Group.objects.filter(name__in=preset.group_names).prefetch_related(
|
|
'permissions__content_type',
|
|
)
|
|
for group in groups:
|
|
for perm in group.permissions.all():
|
|
codes.add(f'{perm.content_type.app_label}.{perm.codename}')
|
|
return frozenset(codes)
|
|
|
|
|
|
def attach_preview_to_user(user, preview_id: str | None) -> None:
|
|
"""Вешает на user атрибуты для portal_access (на один request)."""
|
|
pid = (preview_id or '').strip()
|
|
if not pid or pid not in _PRESETS_BY_ID:
|
|
user._access_preview = ''
|
|
user._access_preview_perms = None
|
|
user._access_preview_groups = None
|
|
return
|
|
preset = _PRESETS_BY_ID[pid]
|
|
user._access_preview = pid
|
|
user._access_preview_perms = preview_permission_strings(pid)
|
|
user._access_preview_groups = frozenset(preset.group_names)
|