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:
@@ -79,3 +79,33 @@ class CustomUser(AbstractUser):
|
|||||||
self.is_email_confirmed = True
|
self.is_email_confirmed = True
|
||||||
self.email_confirmed_at = timezone.now()
|
self.email_confirmed_at = timezone.now()
|
||||||
self.save()
|
self.save()
|
||||||
|
|
||||||
|
def get_tenant_role(self):
|
||||||
|
"""Получить роль пользователя в текущем тенанте"""
|
||||||
|
from user_roles.services import RoleService
|
||||||
|
return RoleService.get_user_role(self)
|
||||||
|
|
||||||
|
def has_role(self, *role_codes):
|
||||||
|
"""Проверить, имеет ли пользователь одну из указанных ролей"""
|
||||||
|
from user_roles.services import RoleService
|
||||||
|
return RoleService.user_has_role(self, *role_codes)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_owner(self):
|
||||||
|
"""Является ли пользователь владельцем"""
|
||||||
|
return self.has_role('owner')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_manager(self):
|
||||||
|
"""Является ли пользователь менеджером"""
|
||||||
|
return self.has_role('manager')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_florist(self):
|
||||||
|
"""Является ли пользователь флористом"""
|
||||||
|
return self.has_role('florist')
|
||||||
|
|
||||||
|
@property
|
||||||
|
def is_courier(self):
|
||||||
|
"""Является ли пользователь курьером"""
|
||||||
|
return self.has_role('courier')
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ TENANT_APPS = [
|
|||||||
'simple_history', # История изменений для каждого тенанта
|
'simple_history', # История изменений для каждого тенанта
|
||||||
'nested_admin',
|
'nested_admin',
|
||||||
'django_filters', # Фильтрация данных
|
'django_filters', # Фильтрация данных
|
||||||
|
'user_roles', # Роли пользователей
|
||||||
'customers', # Клиенты магазина
|
'customers', # Клиенты магазина
|
||||||
'products', # Товары и категории
|
'products', # Товары и категории
|
||||||
'orders', # Заказы
|
'orders', # Заказы
|
||||||
|
|||||||
@@ -310,6 +310,17 @@ class TenantRegistrationAdmin(admin.ModelAdmin):
|
|||||||
logger.error(f"Ошибка при создании статусов заказов: {e}", exc_info=True)
|
logger.error(f"Ошибка при создании статусов заказов: {e}", exc_info=True)
|
||||||
# Не прерываем процесс, т.к. это не критично
|
# Не прерываем процесс, т.к. это не критично
|
||||||
|
|
||||||
|
# Создаем системные роли пользователей
|
||||||
|
logger.info(f"Создание системных ролей для тенанта: {client.id}")
|
||||||
|
from user_roles.services import RoleService
|
||||||
|
|
||||||
|
try:
|
||||||
|
RoleService.create_default_roles()
|
||||||
|
logger.info("Системные роли успешно созданы")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"Ошибка при создании ролей: {e}", exc_info=True)
|
||||||
|
# Не прерываем процесс, т.к. это не критично
|
||||||
|
|
||||||
# Создаем системные способы оплаты
|
# Создаем системные способы оплаты
|
||||||
logger.info(f"Создание системных способов оплаты для тенанта: {client.id}")
|
logger.info(f"Создание системных способов оплаты для тенанта: {client.id}")
|
||||||
from django.core.management import call_command
|
from django.core.management import call_command
|
||||||
|
|||||||
0
myproject/user_roles/__init__.py
Normal file
0
myproject/user_roles/__init__.py
Normal file
39
myproject/user_roles/admin.py
Normal file
39
myproject/user_roles/admin.py
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from user_roles.models import Role, UserRole
|
||||||
|
from user_roles.mixins import OwnerOnlyAdminMixin
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(Role)
|
||||||
|
class RoleAdmin(admin.ModelAdmin):
|
||||||
|
list_display = ['code', 'name', 'is_system']
|
||||||
|
list_filter = ['is_system']
|
||||||
|
search_fields = ['code', 'name']
|
||||||
|
readonly_fields = ['created_at']
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
"""Запрет удаления системных ролей"""
|
||||||
|
if obj and obj.is_system:
|
||||||
|
return False
|
||||||
|
return super().has_delete_permission(request, obj)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.register(UserRole)
|
||||||
|
class UserRoleAdmin(OwnerOnlyAdminMixin, admin.ModelAdmin):
|
||||||
|
"""
|
||||||
|
Админка ролей пользователей.
|
||||||
|
Доступна только владельцу.
|
||||||
|
|
||||||
|
ВАЖНО: UserRole изолирован по тенантам автоматически через django-tenants,
|
||||||
|
поэтому владелец видит только пользователей своего магазина!
|
||||||
|
"""
|
||||||
|
list_display = ['user', 'role', 'is_active', 'created_at']
|
||||||
|
list_filter = ['role', 'is_active']
|
||||||
|
search_fields = ['user__email', 'user__name']
|
||||||
|
readonly_fields = ['created_at', 'created_by']
|
||||||
|
autocomplete_fields = ['user']
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
"""Автоматически устанавливаем created_by"""
|
||||||
|
if not change:
|
||||||
|
obj.created_by = request.user
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
6
myproject/user_roles/apps.py
Normal file
6
myproject/user_roles/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class UserRolesConfig(AppConfig):
|
||||||
|
default_auto_field = 'django.db.models.BigAutoField'
|
||||||
|
name = 'user_roles'
|
||||||
40
myproject/user_roles/decorators.py
Normal file
40
myproject/user_roles/decorators.py
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from functools import wraps
|
||||||
|
from django.http import HttpResponseForbidden
|
||||||
|
from django.shortcuts import redirect
|
||||||
|
from django.contrib import messages
|
||||||
|
from user_roles.services import RoleService
|
||||||
|
|
||||||
|
|
||||||
|
def role_required(*role_codes):
|
||||||
|
"""
|
||||||
|
Декоратор для проверки роли пользователя.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
@role_required('owner', 'manager')
|
||||||
|
def my_view(request):
|
||||||
|
...
|
||||||
|
"""
|
||||||
|
def decorator(view_func):
|
||||||
|
@wraps(view_func)
|
||||||
|
def wrapper(request, *args, **kwargs):
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
return redirect('login')
|
||||||
|
|
||||||
|
if RoleService.user_has_role(request.user, *role_codes):
|
||||||
|
return view_func(request, *args, **kwargs)
|
||||||
|
|
||||||
|
messages.error(request, 'У вас нет прав для выполнения этого действия.')
|
||||||
|
return HttpResponseForbidden('Access denied')
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
return decorator
|
||||||
|
|
||||||
|
|
||||||
|
def owner_required(view_func):
|
||||||
|
"""Декоратор для проверки роли Владелец"""
|
||||||
|
return role_required('owner')(view_func)
|
||||||
|
|
||||||
|
|
||||||
|
def manager_or_owner_required(view_func):
|
||||||
|
"""Декоратор для проверки роли Менеджер или Владелец"""
|
||||||
|
return role_required('owner', 'manager')(view_func)
|
||||||
0
myproject/user_roles/management/__init__.py
Normal file
0
myproject/user_roles/management/__init__.py
Normal file
15
myproject/user_roles/management/commands/init_roles.py
Normal file
15
myproject/user_roles/management/commands/init_roles.py
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
from django.core.management.base import BaseCommand
|
||||||
|
from user_roles.services import RoleService
|
||||||
|
|
||||||
|
|
||||||
|
class Command(BaseCommand):
|
||||||
|
help = 'Создает дефолтные роли для текущего тенанта'
|
||||||
|
|
||||||
|
def handle(self, *args, **options):
|
||||||
|
self.stdout.write('Создание дефолтных ролей...')
|
||||||
|
|
||||||
|
RoleService.create_default_roles()
|
||||||
|
|
||||||
|
self.stdout.write(
|
||||||
|
self.style.SUCCESS('✓ Роли успешно созданы')
|
||||||
|
)
|
||||||
48
myproject/user_roles/migrations/0001_initial.py
Normal file
48
myproject/user_roles/migrations/0001_initial.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
# Generated by Django 5.0.10 on 2025-12-01 15:03
|
||||||
|
|
||||||
|
import django.db.models.deletion
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
initial = True
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='Role',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('code', models.CharField(choices=[('owner', 'Владелец'), ('manager', 'Менеджер'), ('florist', 'Флорист'), ('courier', 'Курьер')], max_length=20, 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, help_text='Системные роли нельзя удалить', verbose_name='Системная роль')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Роль',
|
||||||
|
'verbose_name_plural': 'Роли',
|
||||||
|
'ordering': ['code'],
|
||||||
|
},
|
||||||
|
),
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='UserRole',
|
||||||
|
fields=[
|
||||||
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
|
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
|
||||||
|
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_user_roles', to=settings.AUTH_USER_MODEL, verbose_name='Создал')),
|
||||||
|
('role', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='users', to='user_roles.role', verbose_name='Роль')),
|
||||||
|
('user', models.OneToOneField(help_text='Пользователь из public schema (SHARED_APPS)', on_delete=django.db.models.deletion.CASCADE, related_name='tenant_role', to=settings.AUTH_USER_MODEL, verbose_name='Пользователь')),
|
||||||
|
],
|
||||||
|
options={
|
||||||
|
'verbose_name': 'Роль пользователя',
|
||||||
|
'verbose_name_plural': 'Роли пользователей',
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]
|
||||||
0
myproject/user_roles/migrations/__init__.py
Normal file
0
myproject/user_roles/migrations/__init__.py
Normal file
74
myproject/user_roles/mixins.py
Normal file
74
myproject/user_roles/mixins.py
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.core.exceptions import PermissionDenied
|
||||||
|
from user_roles.services import RoleService
|
||||||
|
|
||||||
|
|
||||||
|
class RoleBasedAdminMixin:
|
||||||
|
"""
|
||||||
|
Миксин для ModelAdmin с проверкой ролей.
|
||||||
|
|
||||||
|
Использование:
|
||||||
|
class MyAdmin(RoleBasedAdminMixin, admin.ModelAdmin):
|
||||||
|
required_roles = ['owner', 'manager']
|
||||||
|
"""
|
||||||
|
required_roles = [] # Роли, которые имеют доступ
|
||||||
|
|
||||||
|
def has_module_permission(self, request):
|
||||||
|
"""Проверка доступа к модулю"""
|
||||||
|
if not super().has_module_permission(request):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.required_roles:
|
||||||
|
return True # Нет ограничений
|
||||||
|
|
||||||
|
return RoleService.user_has_role(request.user, *self.required_roles)
|
||||||
|
|
||||||
|
def has_view_permission(self, request, obj=None):
|
||||||
|
"""Проверка доступа на просмотр"""
|
||||||
|
if not super().has_view_permission(request, obj):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.required_roles:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return RoleService.user_has_role(request.user, *self.required_roles)
|
||||||
|
|
||||||
|
def has_add_permission(self, request):
|
||||||
|
"""Проверка доступа на добавление"""
|
||||||
|
if not super().has_add_permission(request):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.required_roles:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return RoleService.user_has_role(request.user, *self.required_roles)
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
"""Проверка доступа на изменение"""
|
||||||
|
if not super().has_change_permission(request, obj):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.required_roles:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return RoleService.user_has_role(request.user, *self.required_roles)
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
"""Проверка доступа на удаление"""
|
||||||
|
if not super().has_delete_permission(request, obj):
|
||||||
|
return False
|
||||||
|
|
||||||
|
if not self.required_roles:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return RoleService.user_has_role(request.user, *self.required_roles)
|
||||||
|
|
||||||
|
|
||||||
|
class OwnerOnlyAdminMixin(RoleBasedAdminMixin):
|
||||||
|
"""Миксин для админки, доступной только владельцу"""
|
||||||
|
required_roles = ['owner']
|
||||||
|
|
||||||
|
|
||||||
|
class ManagerOwnerAdminMixin(RoleBasedAdminMixin):
|
||||||
|
"""Миксин для админки, доступной менеджеру и владельцу"""
|
||||||
|
required_roles = ['owner', 'manager']
|
||||||
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}"
|
||||||
100
myproject/user_roles/services.py
Normal file
100
myproject/user_roles/services.py
Normal 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
|
||||||
3
myproject/user_roles/tests.py
Normal file
3
myproject/user_roles/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
3
myproject/user_roles/views.py
Normal file
3
myproject/user_roles/views.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.shortcuts import render
|
||||||
|
|
||||||
|
# Create your views here.
|
||||||
Reference in New Issue
Block a user