146 lines
3.9 KiB
Python
146 lines
3.9 KiB
Python
"""HQ executive role helpers and DRF permission classes.
|
|
|
|
Settled matrix (v1):
|
|
- buh, director (+ their admins until specified otherwise): view only
|
|
- gm + gm_admin: view + register active sites (URL) + sync from sites
|
|
- gm only (not gm_admin): approve cycle close
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from rest_framework import permissions
|
|
|
|
from apps.accounts.models import User
|
|
|
|
# View-only executives (no site registry, sync, or approvals).
|
|
VIEW_ONLY_STATUSES = frozenset(
|
|
{
|
|
User.STATUS_BUH,
|
|
User.STATUS_BU_ADMIN,
|
|
User.STATUS_DIRECTOR,
|
|
User.STATUS_DIRECTOR_ADMIN,
|
|
}
|
|
)
|
|
|
|
# Can register site dashboard URLs into the HQ mirror registry.
|
|
SITE_REGISTRY_STATUSES = frozenset(
|
|
{
|
|
User.STATUS_GM,
|
|
User.STATUS_GM_ADMIN,
|
|
}
|
|
)
|
|
|
|
# Can trigger site → HQ mirror sync.
|
|
SYNC_STATUSES = frozenset(
|
|
{
|
|
User.STATUS_GM,
|
|
User.STATUS_GM_ADMIN,
|
|
}
|
|
)
|
|
|
|
# Cycle close approval — GM only (explicitly not gm_admin).
|
|
CYCLE_CLOSE_APPROVER_STATUSES = frozenset(
|
|
{
|
|
User.STATUS_GM,
|
|
}
|
|
)
|
|
|
|
# Product user-management role (IT Central). Django superuser remains break-glass.
|
|
USER_ADMIN_STATUSES = frozenset(
|
|
{
|
|
User.STATUS_ACCOUNT_ADMIN,
|
|
}
|
|
)
|
|
|
|
|
|
def _status(user) -> str | None:
|
|
return getattr(user, "status", None) if user else None
|
|
|
|
|
|
def executive_user(user) -> bool:
|
|
return bool(
|
|
user
|
|
and getattr(user, "is_authenticated", False)
|
|
and _status(user) in User.EXECUTIVE_STATUSES
|
|
)
|
|
|
|
|
|
def can_sync(user) -> bool:
|
|
return bool(
|
|
user
|
|
and getattr(user, "is_authenticated", False)
|
|
and _status(user) in SYNC_STATUSES
|
|
)
|
|
|
|
|
|
def can_register_active_site(user) -> bool:
|
|
"""GM / GM Admin may add site dashboard URLs to the HQ registry."""
|
|
return bool(
|
|
user
|
|
and getattr(user, "is_authenticated", False)
|
|
and _status(user) in SITE_REGISTRY_STATUSES
|
|
)
|
|
|
|
|
|
def can_approve_cycle_close(user) -> bool:
|
|
"""Only GM (not gm_admin) may approve closing a cycle."""
|
|
return bool(
|
|
user
|
|
and getattr(user, "is_authenticated", False)
|
|
and _status(user) in CYCLE_CLOSE_APPROVER_STATUSES
|
|
)
|
|
|
|
|
|
def can_write_operations(user) -> bool:
|
|
"""No manual ops create/edit/delete for executive roles."""
|
|
return False
|
|
|
|
|
|
def can_manage_users(user) -> bool:
|
|
"""Account Admin (IT Central) or Django superuser break-glass."""
|
|
if not user or not getattr(user, "is_authenticated", False):
|
|
return False
|
|
if getattr(user, "is_superuser", False):
|
|
return True
|
|
return _status(user) in USER_ADMIN_STATUSES
|
|
|
|
|
|
def can_manage_active_site_registry(user) -> bool:
|
|
"""Alias used by active-site write endpoints — same as register permission."""
|
|
return can_register_active_site(user) or bool(getattr(user, "is_superuser", False))
|
|
|
|
|
|
class IsExecutive(permissions.BasePermission):
|
|
def has_permission(self, request, view):
|
|
return executive_user(request.user)
|
|
|
|
|
|
class IsExecutiveAdmin(permissions.BasePermission):
|
|
def has_permission(self, request, view):
|
|
return can_manage_users(request.user)
|
|
|
|
|
|
class CanRegisterActiveSite(permissions.BasePermission):
|
|
def has_permission(self, request, view):
|
|
return can_register_active_site(request.user) or bool(
|
|
getattr(request.user, "is_superuser", False)
|
|
)
|
|
|
|
|
|
class CanApproveCycleClose(permissions.BasePermission):
|
|
def has_permission(self, request, view):
|
|
return can_approve_cycle_close(request.user)
|
|
|
|
|
|
class ReadOrSyncOnly(permissions.BasePermission):
|
|
"""Safe methods for all executives; sync actions for GM / GM Admin only."""
|
|
|
|
def has_permission(self, request, view):
|
|
if not executive_user(request.user):
|
|
return False
|
|
if request.method in permissions.SAFE_METHODS:
|
|
return True
|
|
if getattr(view, "executive_sync_action", False):
|
|
return can_sync(request.user)
|
|
return can_write_operations(request.user)
|