feat: implement user roles system with tenant isolation

Добавлена система ролей пользователей для управления доступом в multi-tenant приложении.

Новые роли:
- Владелец (Owner): полный доступ, управление пользователями
- Менеджер (Manager): управление заказами, клиентами, товарами, складом
- Флорист (Florist): работа с заказами и складскими операциями
- Курьер (Courier): роль создана, права будут определены позже

Архитектура:
- Роли автоматически изолируются по тенантам через django-tenants (TENANT_APPS)
- Не требуется FK на Client/Tenant - изоляция через PostgreSQL schemas
- Роли автоматически создаются при создании нового тенанта

Компоненты:
- user_roles/models.py: модели Role и UserRole
- user_roles/services.py: RoleService для управления ролями
- user_roles/decorators.py: @role_required, @owner_required
- user_roles/mixins.py: RoleBasedAdminMixin, OwnerOnlyAdminMixin
- user_roles/admin.py: админка для управления ролями
- user_roles/management/commands/init_roles.py: команда для инициализации

Изменения:
- accounts/models.py: добавлены helper методы (is_owner, has_role, etc)
- settings.py: добавлен user_roles в TENANT_APPS
- tenants/admin.py: автосоздание ролей при создании тенанта

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2025-12-01 18:06:47 +03:00
parent eef2cb820f
commit f4e7ad0aac
17 changed files with 471 additions and 0 deletions

View File

@@ -0,0 +1,100 @@
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