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:
101
myproject/user_roles/models.py
Normal file
101
myproject/user_roles/models.py
Normal 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}"
|
||||
Reference in New Issue
Block a user