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,101 @@
from django.db import models
from django.conf import settings
from django.utils import timezone
class Role(models.Model):
"""
Роль пользователя.
ВАЖНО: Модель находится в TENANT_APPS, поэтому автоматически изолируется
по тенантам через django-tenants. Каждый тенант имеет свой набор ролей
в своей PostgreSQL schema.
Не нужно явно связывать с тенантом через FK - изоляция происходит автоматически!
"""
OWNER = 'owner'
MANAGER = 'manager'
FLORIST = 'florist'
COURIER = 'courier'
ROLE_CHOICES = [
(OWNER, 'Владелец'),
(MANAGER, 'Менеджер'),
(FLORIST, 'Флорист'),
(COURIER, 'Курьер'),
]
code = models.CharField(
max_length=20,
choices=ROLE_CHOICES,
unique=True,
verbose_name="Код роли"
)
name = models.CharField(
max_length=100,
verbose_name="Название"
)
description = models.TextField(
blank=True,
verbose_name="Описание"
)
is_system = models.BooleanField(
default=True,
verbose_name="Системная роль",
help_text="Системные роли нельзя удалить"
)
created_at = models.DateTimeField(auto_now_add=True)
class Meta:
verbose_name = "Роль"
verbose_name_plural = "Роли"
ordering = ['code']
def __str__(self):
return self.name
class UserRole(models.Model):
"""
Роль пользователя в текущем тенанте.
ВАЖНО: Эта модель НЕ связывает пользователя с тенантом!
Связь с тенантом обеспечивается автоматически через django-tenants
(модель в TENANT_APPS = находится в schema тенанта).
UserRole просто говорит: "этот пользователь имеет эту роль" (в рамках текущего тенанта).
Один пользователь = одна роль в рамках одного тенанта.
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
on_delete=models.CASCADE,
related_name='tenant_role',
verbose_name="Пользователь",
help_text="Пользователь из public schema (SHARED_APPS)"
)
role = models.ForeignKey(
Role,
on_delete=models.PROTECT,
related_name='users',
verbose_name="Роль"
)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
null=True,
blank=True,
on_delete=models.SET_NULL,
related_name='created_user_roles',
verbose_name="Создал"
)
is_active = models.BooleanField(
default=True,
verbose_name="Активен"
)
class Meta:
verbose_name = "Роль пользователя"
verbose_name_plural = "Роли пользователей"
def __str__(self):
return f"{self.user.email} - {self.role.name}"