Обновления в user_roles: модели, сервисы, представления и миграции
This commit is contained in:
@@ -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
|
import django.db.models.deletion
|
||||||
from django.conf import settings
|
|
||||||
from django.db import migrations, models
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
@@ -10,7 +9,7 @@ class Migration(migrations.Migration):
|
|||||||
initial = True
|
initial = True
|
||||||
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
('accounts', '0001_initial'),
|
||||||
]
|
]
|
||||||
|
|
||||||
operations = [
|
operations = [
|
||||||
@@ -36,9 +35,9 @@ class Migration(migrations.Migration):
|
|||||||
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
('created_at', models.DateTimeField(auto_now_add=True)),
|
('created_at', models.DateTimeField(auto_now_add=True)),
|
||||||
('is_active', models.BooleanField(default=True, verbose_name='Активен')),
|
('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='Роль')),
|
('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={
|
options={
|
||||||
'verbose_name': 'Роль пользователя',
|
'verbose_name': 'Роль пользователя',
|
||||||
|
|||||||
@@ -13,18 +13,23 @@ class Role(models.Model):
|
|||||||
|
|
||||||
Не нужно явно связывать с тенантом через FK - изоляция происходит автоматически!
|
Не нужно явно связывать с тенантом через FK - изоляция происходит автоматически!
|
||||||
"""
|
"""
|
||||||
|
PLATFORM_SUPPORT = 'platform_support'
|
||||||
OWNER = 'owner'
|
OWNER = 'owner'
|
||||||
MANAGER = 'manager'
|
MANAGER = 'manager'
|
||||||
FLORIST = 'florist'
|
FLORIST = 'florist'
|
||||||
COURIER = 'courier'
|
COURIER = 'courier'
|
||||||
|
|
||||||
ROLE_CHOICES = [
|
ROLE_CHOICES = [
|
||||||
|
(PLATFORM_SUPPORT, 'Техподдержка платформы'),
|
||||||
(OWNER, 'Владелец'),
|
(OWNER, 'Владелец'),
|
||||||
(MANAGER, 'Менеджер'),
|
(MANAGER, 'Менеджер'),
|
||||||
(FLORIST, 'Флорист'),
|
(FLORIST, 'Флорист'),
|
||||||
(COURIER, 'Курьер'),
|
(COURIER, 'Курьер'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
# Роли, скрытые от пользователей тенанта (не отображаются в списках)
|
||||||
|
HIDDEN_ROLES = [PLATFORM_SUPPORT]
|
||||||
|
|
||||||
code = models.CharField(
|
code = models.CharField(
|
||||||
max_length=20,
|
max_length=20,
|
||||||
choices=ROLE_CHOICES,
|
choices=ROLE_CHOICES,
|
||||||
@@ -67,11 +72,11 @@ class UserRole(models.Model):
|
|||||||
Один пользователь = одна роль в рамках одного тенанта.
|
Один пользователь = одна роль в рамках одного тенанта.
|
||||||
"""
|
"""
|
||||||
user = models.OneToOneField(
|
user = models.OneToOneField(
|
||||||
settings.AUTH_USER_MODEL,
|
'accounts.CustomUser',
|
||||||
on_delete=models.CASCADE,
|
on_delete=models.CASCADE,
|
||||||
related_name='tenant_role',
|
related_name='tenant_role',
|
||||||
verbose_name="Пользователь",
|
verbose_name="Пользователь",
|
||||||
help_text="Пользователь из public schema (SHARED_APPS)"
|
help_text="Пользователь тенанта (из tenant schema)"
|
||||||
)
|
)
|
||||||
role = models.ForeignKey(
|
role = models.ForeignKey(
|
||||||
Role,
|
Role,
|
||||||
@@ -81,7 +86,7 @@ class UserRole(models.Model):
|
|||||||
)
|
)
|
||||||
created_at = models.DateTimeField(auto_now_add=True)
|
created_at = models.DateTimeField(auto_now_add=True)
|
||||||
created_by = models.ForeignKey(
|
created_by = models.ForeignKey(
|
||||||
settings.AUTH_USER_MODEL,
|
'accounts.CustomUser',
|
||||||
null=True,
|
null=True,
|
||||||
blank=True,
|
blank=True,
|
||||||
on_delete=models.SET_NULL,
|
on_delete=models.SET_NULL,
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ class RoleService:
|
|||||||
поэтому роли создаются в schema конкретного тенанта автоматически!
|
поэтому роли создаются в schema конкретного тенанта автоматически!
|
||||||
"""
|
"""
|
||||||
default_roles = [
|
default_roles = [
|
||||||
|
{
|
||||||
|
'code': Role.PLATFORM_SUPPORT,
|
||||||
|
'name': 'Техподдержка платформы',
|
||||||
|
'description': 'Служебный аккаунт техподдержки платформы. Полный доступ для помощи владельцу.',
|
||||||
|
'is_system': True,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
'code': Role.OWNER,
|
'code': Role.OWNER,
|
||||||
'name': 'Владелец',
|
'name': 'Владелец',
|
||||||
@@ -50,6 +56,26 @@ class RoleService:
|
|||||||
defaults=role_data
|
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
|
@staticmethod
|
||||||
def get_role_by_code(code):
|
def get_role_by_code(code):
|
||||||
"""Получить роль по коду"""
|
"""Получить роль по коду"""
|
||||||
|
|||||||
@@ -16,16 +16,22 @@ def user_role_list(request):
|
|||||||
# Фильтр по активности
|
# Фильтр по активности
|
||||||
show_inactive = request.GET.get('show_inactive', 'false') == 'true'
|
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:
|
if show_inactive:
|
||||||
user_roles = UserRole.objects.select_related('user', 'role', 'created_by').all()
|
user_roles = base_qs.all()
|
||||||
else:
|
else:
|
||||||
# По умолчанию показываем только активных
|
# По умолчанию показываем только активных
|
||||||
user_roles = UserRole.objects.select_related('user', 'role', 'created_by').filter(
|
user_roles = base_qs.filter(
|
||||||
is_active=True,
|
is_active=True,
|
||||||
user__is_active=True
|
user__is_active=True
|
||||||
)
|
)
|
||||||
|
|
||||||
roles = Role.objects.all()
|
# Показываем только видимые роли (без platform_support)
|
||||||
|
roles = RoleService.get_visible_roles()
|
||||||
|
|
||||||
context = {
|
context = {
|
||||||
'user_roles': user_roles,
|
'user_roles': user_roles,
|
||||||
@@ -69,7 +75,7 @@ def user_role_create(request):
|
|||||||
f'Пользователь с email {email} уже существует в системе.'
|
f'Пользователь с email {email} уже существует в системе.'
|
||||||
)
|
)
|
||||||
# ВАЖНО: Возвращаемся к форме, чтобы пользователь мог исправить email
|
# ВАЖНО: Возвращаемся к форме, чтобы пользователь мог исправить email
|
||||||
roles = Role.objects.all()
|
roles = RoleService.get_visible_roles()
|
||||||
context = {'roles': roles}
|
context = {'roles': roles}
|
||||||
return render(request, 'user_roles/user_role_create.html', context)
|
return render(request, 'user_roles/user_role_create.html', context)
|
||||||
else:
|
else:
|
||||||
@@ -100,7 +106,8 @@ def user_role_create(request):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
messages.error(request, f'Ошибка при создании пользователя: {str(e)}')
|
messages.error(request, f'Ошибка при создании пользователя: {str(e)}')
|
||||||
|
|
||||||
roles = Role.objects.all()
|
# Показываем только видимые роли (без platform_support)
|
||||||
|
roles = RoleService.get_visible_roles()
|
||||||
context = {
|
context = {
|
||||||
'roles': roles,
|
'roles': roles,
|
||||||
}
|
}
|
||||||
@@ -113,6 +120,11 @@ def user_role_edit(request, pk):
|
|||||||
"""Изменение роли пользователя"""
|
"""Изменение роли пользователя"""
|
||||||
user_role = get_object_or_404(UserRole, pk=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)
|
can_modify, error_message = RoleService.can_modify_user_role(request.user, user_role)
|
||||||
if not can_modify:
|
if not can_modify:
|
||||||
@@ -132,7 +144,7 @@ def user_role_edit(request, pk):
|
|||||||
messages.success(request, f'Пароль для пользователя {user_role.user.email} успешно обновлен')
|
messages.success(request, f'Пароль для пользователя {user_role.user.email} успешно обновлен')
|
||||||
|
|
||||||
# Показываем новый пароль в модальном окне
|
# Показываем новый пароль в модальном окне
|
||||||
roles = Role.objects.all()
|
roles = RoleService.get_visible_roles()
|
||||||
context = {
|
context = {
|
||||||
'user_role': user_role,
|
'user_role': user_role,
|
||||||
'roles': roles,
|
'roles': roles,
|
||||||
@@ -170,7 +182,7 @@ def user_role_edit(request, pk):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
messages.error(request, f'Ошибка при обновлении данных: {str(e)}')
|
messages.error(request, f'Ошибка при обновлении данных: {str(e)}')
|
||||||
|
|
||||||
roles = Role.objects.all()
|
roles = RoleService.get_visible_roles()
|
||||||
context = {
|
context = {
|
||||||
'user_role': user_role,
|
'user_role': user_role,
|
||||||
'roles': roles,
|
'roles': roles,
|
||||||
@@ -184,6 +196,11 @@ def user_role_delete(request, pk):
|
|||||||
"""Деактивация пользователя (soft delete)"""
|
"""Деактивация пользователя (soft delete)"""
|
||||||
user_role = get_object_or_404(UserRole, pk=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)
|
can_modify, error_message = RoleService.can_modify_user_role(request.user, user_role)
|
||||||
if not can_modify:
|
if not can_modify:
|
||||||
@@ -215,6 +232,11 @@ def user_role_reactivate(request, pk):
|
|||||||
"""Реактивация пользователя"""
|
"""Реактивация пользователя"""
|
||||||
user_role = get_object_or_404(UserRole, pk=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':
|
if request.method == 'POST':
|
||||||
email = user_role.user.email
|
email = user_role.user.email
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user