Обновления в user_roles: модели, сервисы, представления и миграции

This commit is contained in:
2026-01-08 22:15:42 +03:00
parent 76acf419fc
commit 75384999ee
4 changed files with 70 additions and 18 deletions

View File

@@ -1,7 +1,6 @@
# Generated by Django 5.0.10 on 2026-01-03 23:23
# Generated by Django 5.0.10 on 2026-01-08 15:58
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
@@ -10,7 +9,7 @@ class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('accounts', '0001_initial'),
]
operations = [
@@ -36,9 +35,9 @@ class Migration(migrations.Migration):
('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='Создал')),
('created_by', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='created_user_roles', to='accounts.customuser', 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='Пользователь')),
('user', models.OneToOneField(help_text='Пользователь тенанта (из tenant schema)', on_delete=django.db.models.deletion.CASCADE, related_name='tenant_role', to='accounts.customuser', verbose_name='Пользователь')),
],
options={
'verbose_name': 'Роль пользователя',

View File

@@ -13,18 +13,23 @@ class Role(models.Model):
Не нужно явно связывать с тенантом через FK - изоляция происходит автоматически!
"""
PLATFORM_SUPPORT = 'platform_support'
OWNER = 'owner'
MANAGER = 'manager'
FLORIST = 'florist'
COURIER = 'courier'
ROLE_CHOICES = [
(PLATFORM_SUPPORT, 'Техподдержка платформы'),
(OWNER, 'Владелец'),
(MANAGER, 'Менеджер'),
(FLORIST, 'Флорист'),
(COURIER, 'Курьер'),
]
# Роли, скрытые от пользователей тенанта (не отображаются в списках)
HIDDEN_ROLES = [PLATFORM_SUPPORT]
code = models.CharField(
max_length=20,
choices=ROLE_CHOICES,
@@ -67,11 +72,11 @@ class UserRole(models.Model):
Один пользователь = одна роль в рамках одного тенанта.
"""
user = models.OneToOneField(
settings.AUTH_USER_MODEL,
'accounts.CustomUser',
on_delete=models.CASCADE,
related_name='tenant_role',
verbose_name="Пользователь",
help_text="Пользователь из public schema (SHARED_APPS)"
help_text="Пользователь тенанта (из tenant schema)"
)
role = models.ForeignKey(
Role,
@@ -81,7 +86,7 @@ class UserRole(models.Model):
)
created_at = models.DateTimeField(auto_now_add=True)
created_by = models.ForeignKey(
settings.AUTH_USER_MODEL,
'accounts.CustomUser',
null=True,
blank=True,
on_delete=models.SET_NULL,

View File

@@ -18,6 +18,12 @@ class RoleService:
поэтому роли создаются в schema конкретного тенанта автоматически!
"""
default_roles = [
{
'code': Role.PLATFORM_SUPPORT,
'name': 'Техподдержка платформы',
'description': 'Служебный аккаунт техподдержки платформы. Полный доступ для помощи владельцу.',
'is_system': True,
},
{
'code': Role.OWNER,
'name': 'Владелец',
@@ -50,6 +56,26 @@ class RoleService:
defaults=role_data
)
@staticmethod
def get_visible_roles():
"""
Возвращает роли, видимые для пользователей тенанта.
Исключает служебные роли (platform_support).
"""
return Role.objects.exclude(code__in=Role.HIDDEN_ROLES)
@staticmethod
def get_visible_users():
"""
Возвращает пользователей, видимых для владельца тенанта.
Исключает пользователей с ролью platform_support.
"""
from accounts.models import CustomUser
hidden_user_ids = UserRole.objects.filter(
role__code__in=Role.HIDDEN_ROLES
).values_list('user_id', flat=True)
return CustomUser.objects.exclude(id__in=hidden_user_ids)
@staticmethod
def get_role_by_code(code):
"""Получить роль по коду"""

View File

@@ -16,16 +16,22 @@ def user_role_list(request):
# Фильтр по активности
show_inactive = request.GET.get('show_inactive', 'false') == 'true'
# Базовый queryset - исключаем скрытые роли (platform_support)
base_qs = UserRole.objects.select_related('user', 'role', 'created_by').exclude(
role__code__in=Role.HIDDEN_ROLES
)
if show_inactive:
user_roles = UserRole.objects.select_related('user', 'role', 'created_by').all()
user_roles = base_qs.all()
else:
# По умолчанию показываем только активных
user_roles = UserRole.objects.select_related('user', 'role', 'created_by').filter(
user_roles = base_qs.filter(
is_active=True,
user__is_active=True
)
roles = Role.objects.all()
# Показываем только видимые роли (без platform_support)
roles = RoleService.get_visible_roles()
context = {
'user_roles': user_roles,
@@ -69,7 +75,7 @@ def user_role_create(request):
f'Пользователь с email {email} уже существует в системе.'
)
# ВАЖНО: Возвращаемся к форме, чтобы пользователь мог исправить email
roles = Role.objects.all()
roles = RoleService.get_visible_roles()
context = {'roles': roles}
return render(request, 'user_roles/user_role_create.html', context)
else:
@@ -100,7 +106,8 @@ def user_role_create(request):
except Exception as e:
messages.error(request, f'Ошибка при создании пользователя: {str(e)}')
roles = Role.objects.all()
# Показываем только видимые роли (без platform_support)
roles = RoleService.get_visible_roles()
context = {
'roles': roles,
}
@@ -113,6 +120,11 @@ def user_role_edit(request, pk):
"""Изменение роли пользователя"""
user_role = get_object_or_404(UserRole, pk=pk)
# Защита от редактирования скрытых ролей (platform_support)
if user_role.role.code in Role.HIDDEN_ROLES:
messages.error(request, 'Недостаточно прав для редактирования этого пользователя')
return redirect('user_roles:list')
# Защита от самоблокировки
can_modify, error_message = RoleService.can_modify_user_role(request.user, user_role)
if not can_modify:
@@ -132,7 +144,7 @@ def user_role_edit(request, pk):
messages.success(request, f'Пароль для пользователя {user_role.user.email} успешно обновлен')
# Показываем новый пароль в модальном окне
roles = Role.objects.all()
roles = RoleService.get_visible_roles()
context = {
'user_role': user_role,
'roles': roles,
@@ -170,7 +182,7 @@ def user_role_edit(request, pk):
except Exception as e:
messages.error(request, f'Ошибка при обновлении данных: {str(e)}')
roles = Role.objects.all()
roles = RoleService.get_visible_roles()
context = {
'user_role': user_role,
'roles': roles,
@@ -184,6 +196,11 @@ def user_role_delete(request, pk):
"""Деактивация пользователя (soft delete)"""
user_role = get_object_or_404(UserRole, pk=pk)
# Защита от удаления скрытых ролей (platform_support)
if user_role.role.code in Role.HIDDEN_ROLES:
messages.error(request, 'Недостаточно прав для удаления этого пользователя')
return redirect('user_roles:list')
# Защита от самоблокировки
can_modify, error_message = RoleService.can_modify_user_role(request.user, user_role)
if not can_modify:
@@ -215,6 +232,11 @@ def user_role_reactivate(request, pk):
"""Реактивация пользователя"""
user_role = get_object_or_404(UserRole, pk=pk)
# Защита от реактивации скрытых ролей (platform_support)
if user_role.role.code in Role.HIDDEN_ROLES:
messages.error(request, 'Недостаточно прав для изменения этого пользователя')
return redirect('user_roles:list')
if request.method == 'POST':
email = user_role.user.email