Конкретно пересмотрел логику работы. Легаси вынесена в архив
All checks were successful
Deploy MES Core / deploy (push) Successful in 13s

This commit is contained in:
2026-04-13 07:36:57 +03:00
parent 86215c9fa8
commit 28537447f8
80 changed files with 10246 additions and 684 deletions

106
shiftflow/authz.py Normal file
View File

@@ -0,0 +1,106 @@
from __future__ import annotations
from typing import Iterable
ROLE_PRIORITY = [
'admin',
'prod_head',
'technologist',
'master',
'clerk',
'supply',
'manager',
'operator',
'observer',
'director',
]
def get_user_group_roles(user) -> set[str]:
"""Возвращает роли пользователя только из Django Groups.
Используется для экранов, где включён строгий доступ "только по группам".
EmployeeProfile.role здесь намеренно не учитывается.
Правило: superuser получает роль admin.
"""
roles: set[str] = set()
if not user or not getattr(user, 'is_authenticated', False):
return roles
if getattr(user, 'is_superuser', False):
roles.add('admin')
try:
roles |= set(user.groups.values_list('name', flat=True))
except Exception:
pass
return roles
def get_user_roles(user) -> set[str]:
"""Возвращает множество ролей пользователя.
Источник ролей (вариант A, плавная миграция):
- Django Groups: позволяет назначать несколько ролей одному пользователю.
- Fallback на EmployeeProfile.role: чтобы при деплое и до раздачи групп система
продолжала работать по старой модели (одна роль).
Правило: superuser всегда получает роль admin независимо от групп/профиля.
"""
roles: set[str] = set() # Изначально множество пустое
# 1. Проверяем, что пользователь авторизован
if not user or not getattr(user, 'is_authenticated', False):
return roles
# 2. Проверяем, что пользователь не superuser
if getattr(user, 'is_superuser', False):
roles.add('admin')
# 3. Проверяем, что у пользователя есть хотя бы одна группа
try:
roles |= set(user.groups.values_list('name', flat=True))
except Exception:
pass
# 4. Проверяем, что у пользователя есть роль в EmployeeProfile
profile = getattr(user, 'profile', None)
if profile and getattr(profile, 'role', None):
roles.add(str(profile.role))
return roles
def primary_role(roles: Iterable[str]) -> str:
"""Выбирает "основную" роль для отображения в UI.
Примечание: права доступа должны проверяться по всем ролям (has_any_role).
primary_role используется только для:
- подписи/лейбла "роль пользователя" в шаблонах
- дефолтного поведения, где требуется один статус (например, оформление UI)
"""
s = set(roles or [])
for r in ROLE_PRIORITY:
if r in s:
return r
return 'operator'
def has_any_role(roles: Iterable[str], required: Iterable[str]) -> bool:
"""Проверяет, что у пользователя есть хотя бы одна роль из required.
Используется во вьюхах для разрешений вида:
roles = get_user_roles(request.user)
if not has_any_role(roles, ['admin', 'master']):
return redirect('registry')
"""
s = set(roles or [])
for r in required or []:
if r in s:
return True
return False