Files
octopus/myproject/user_roles/services.py
Andrey Smakotin f2c1f7e02d feat: add self-modification protection for user roles
Protect owners from accidentally locking themselves out by:
- Adding RoleService.can_modify_user_role() to centralize validation logic
- Blocking edit/delete operations on own role in views
- Hiding edit/delete buttons for own role in template

This prevents owners from:
- Changing their own role to a lower privilege level
- Deactivating themselves
- Deleting their own access

Standard admin pattern used by GitHub, WordPress, Django Admin.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-01 23:06:54 +03:00

123 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from django.db import transaction
from user_roles.models import Role, UserRole
from django.contrib.auth import get_user_model
User = get_user_model()
class RoleService:
"""Сервис для управления ролями"""
@staticmethod
def create_default_roles():
"""
Создает системные роли для тенанта.
Вызывается при создании тенанта (автоматически через signals или admin).
ВАЖНО: Эта функция вызывается в контексте schema_context(tenant.schema_name),
поэтому роли создаются в schema конкретного тенанта автоматически!
"""
default_roles = [
{
'code': Role.OWNER,
'name': 'Владелец',
'description': 'Полный доступ ко всем функциям системы. Управление пользователями и настройками.',
'is_system': True,
},
{
'code': Role.MANAGER,
'name': 'Менеджер',
'description': 'Управление заказами, клиентами, товарами и складом. Без доступа к настройкам.',
'is_system': True,
},
{
'code': Role.FLORIST,
'name': 'Флорист',
'description': 'Работа с заказами и складскими операциями для заказов.',
'is_system': True,
},
{
'code': Role.COURIER,
'name': 'Курьер',
'description': 'Доставка заказов (права будут определены позже).',
'is_system': True,
},
]
for role_data in default_roles:
Role.objects.get_or_create(
code=role_data['code'],
defaults=role_data
)
@staticmethod
def get_role_by_code(code):
"""Получить роль по коду"""
try:
return Role.objects.get(code=code, is_system=True)
except Role.DoesNotExist:
return None
@staticmethod
@transaction.atomic
def assign_role_to_user(user, role_code, created_by=None):
"""
Назначить роль пользователю в текущем тенанте.
Если у пользователя уже есть роль - обновляет её.
ВАЖНО: Вызывается в контексте текущего тенанта (через middleware),
поэтому UserRole создается в schema текущего тенанта автоматически!
"""
role = RoleService.get_role_by_code(role_code)
if not role:
raise ValueError(f"Роль с кодом '{role_code}' не найдена")
user_role, created = UserRole.objects.update_or_create(
user=user,
defaults={
'role': role,
'created_by': created_by,
'is_active': True,
}
)
return user_role
@staticmethod
def get_user_role(user):
"""Получить роль пользователя в текущем тенанте"""
try:
user_role = UserRole.objects.get(user=user, is_active=True)
return user_role.role
except UserRole.DoesNotExist:
return None
@staticmethod
def user_has_role(user, *role_codes):
"""Проверить, имеет ли пользователь одну из указанных ролей"""
user_role = RoleService.get_user_role(user)
if not user_role:
return False
return user_role.code in role_codes
@staticmethod
def can_modify_user_role(modifier_user, target_user_role):
"""
Проверяет, может ли пользователь изменить указанную роль.
Args:
modifier_user: Пользователь, который хочет изменить роль
target_user_role: UserRole объект, который нужно изменить
Returns:
tuple: (bool, str) - (можно ли изменить, причина отказа)
"""
# Защита от самоблокировки
if target_user_role.user == modifier_user:
return False, "Вы не можете изменить свою собственную роль"
# Можно добавить другие проверки в будущем:
# - Запрет удаления последнего owner'а
# - Проверка иерархии ролей и т.д.
return True, ""