first commit
This commit is contained in:
commit
b54624be96
226 files changed
+108840
No files matched your search
@@ -0,0 +1,145 @@
|
||||
"""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 city sites (URL) + sync from cities
|
||||
- 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 city dashboard URLs into the HQ mirror registry.
|
||||
SITE_REGISTRY_STATUSES = frozenset(
|
||||
{
|
||||
User.STATUS_GM,
|
||||
User.STATUS_GM_ADMIN,
|
||||
}
|
||||
)
|
||||
|
||||
# Can trigger city → 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_city_site(user) -> bool:
|
||||
"""GM / GM Admin may add city 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_city_registry(user) -> bool:
|
||||
"""Alias used by city-site write endpoints — same as register permission."""
|
||||
return can_register_city_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 CanRegisterCitySite(permissions.BasePermission):
|
||||
def has_permission(self, request, view):
|
||||
return can_register_city_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)
|
||||
Reference in new issue
Block a user