diff --git a/myproject/user_roles/migrations/0001_initial.py b/myproject/user_roles/migrations/0001_initial.py index e914cfb..01350fd 100644 --- a/myproject/user_roles/migrations/0001_initial.py +++ b/myproject/user_roles/migrations/0001_initial.py @@ -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': 'Роль пользователя', diff --git a/myproject/user_roles/models.py b/myproject/user_roles/models.py index a8ee458..d79052b 100644 --- a/myproject/user_roles/models.py +++ b/myproject/user_roles/models.py @@ -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, diff --git a/myproject/user_roles/services.py b/myproject/user_roles/services.py index 014a6ec..262cf41 100644 --- a/myproject/user_roles/services.py +++ b/myproject/user_roles/services.py @@ -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): """Получить роль по коду""" diff --git a/myproject/user_roles/views.py b/myproject/user_roles/views.py index ea9c625..1836cd6 100644 --- a/myproject/user_roles/views.py +++ b/myproject/user_roles/views.py @@ -15,17 +15,23 @@ 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: @@ -130,9 +142,9 @@ def user_role_edit(request, pk): user_role.user.save() 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