From 14cc73722f4f3c6a2d72bf58fa3ab02bed08db7b Mon Sep 17 00:00:00 2001 From: Andrey Smakotin Date: Mon, 1 Dec 2025 21:24:27 +0300 Subject: [PATCH] feat: add user roles management UI with owner access control MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added role management views (list, create, edit, delete) - Created user_roles URL routing - Added role management templates with Bootstrap styling - Updated navbar with Roles link for owners and superusers - Enhanced decorators and mixins with superuser bypass - Added assign_owner_role.py utility script 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- assign_owner_role.py | 48 ++++++++ myproject/myproject/urls.py | 1 + myproject/templates/navbar.html | 5 + myproject/user_roles/decorators.py | 4 + myproject/user_roles/mixins.py | 20 ++++ .../user_roles/user_role_create.html | 64 ++++++++++ .../user_roles/user_role_delete.html | 61 ++++++++++ .../templates/user_roles/user_role_edit.html | 63 ++++++++++ .../templates/user_roles/user_role_list.html | 93 +++++++++++++++ myproject/user_roles/urls.py | 11 ++ myproject/user_roles/views.py | 111 +++++++++++++++++- 11 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 assign_owner_role.py create mode 100644 myproject/user_roles/templates/user_roles/user_role_create.html create mode 100644 myproject/user_roles/templates/user_roles/user_role_delete.html create mode 100644 myproject/user_roles/templates/user_roles/user_role_edit.html create mode 100644 myproject/user_roles/templates/user_roles/user_role_list.html create mode 100644 myproject/user_roles/urls.py diff --git a/assign_owner_role.py b/assign_owner_role.py new file mode 100644 index 0000000..8144571 --- /dev/null +++ b/assign_owner_role.py @@ -0,0 +1,48 @@ +import os +import sys +import django + +# Добавляем путь к проекту +sys.path.insert(0, os.path.join(os.path.dirname(__file__), 'myproject')) + +# Настраиваем Django +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'myproject.settings') +django.setup() + +from django.contrib.auth import get_user_model +from django_tenants.utils import schema_context +from user_roles.services import RoleService + +User = get_user_model() + +# Схема тенанта +schema_name = 'buba' + +# Email пользователя +user_email = 'admin@localhost' + +print(f"Назначение роли Owner для пользователя {user_email} в тенанте {schema_name}...") + +# Переключаемся на схему тенанта +with schema_context(schema_name): + try: + # Находим пользователя + user = User.objects.get(email=user_email) + print(f"Пользователь найден: {user.email} (ID: {user.id})") + + # Назначаем роль Owner + user_role = RoleService.assign_role_to_user(user, 'owner') + print(f"✓ Роль '{user_role.role.name}' успешно назначена!") + print(f" - Email: {user_role.user.email}") + print(f" - Имя: {user_role.user.name}") + print(f" - Роль: {user_role.role.name} ({user_role.role.code})") + print(f" - Активен: {user_role.is_active}") + + except User.DoesNotExist: + print(f"✗ Ошибка: Пользователь с email '{user_email}' не найден") + except Exception as e: + print(f"✗ Ошибка при назначении роли: {e}") + import traceback + traceback.print_exc() + +print("\nГотово!") diff --git a/myproject/myproject/urls.py b/myproject/myproject/urls.py index f0ca567..2660ffe 100644 --- a/myproject/myproject/urls.py +++ b/myproject/myproject/urls.py @@ -17,6 +17,7 @@ urlpatterns = [ # Web interface for shop owners path('', views.index, name='index'), # Главная страница path('accounts/', include('accounts.urls')), # Управление аккаунтом + path('roles/', include('user_roles.urls')), # Управление ролями пользователей path('products/', include('products.urls')), # Управление товарами path('customers/', include('customers.urls')), # Управление клиентами path('inventory/', include('inventory.urls')), # Управление складом diff --git a/myproject/templates/navbar.html b/myproject/templates/navbar.html index fa48118..c02eb74 100644 --- a/myproject/templates/navbar.html +++ b/myproject/templates/navbar.html @@ -44,6 +44,11 @@ + {% if request.user.is_owner or request.user.is_superuser %} + + {% endif %} diff --git a/myproject/user_roles/decorators.py b/myproject/user_roles/decorators.py index e0c73a9..eee4d64 100644 --- a/myproject/user_roles/decorators.py +++ b/myproject/user_roles/decorators.py @@ -20,6 +20,10 @@ def role_required(*role_codes): if not request.user.is_authenticated: return redirect('login') + # Superuser имеет полный доступ + if request.user.is_superuser: + return view_func(request, *args, **kwargs) + if RoleService.user_has_role(request.user, *role_codes): return view_func(request, *args, **kwargs) diff --git a/myproject/user_roles/mixins.py b/myproject/user_roles/mixins.py index f75e649..5e53895 100644 --- a/myproject/user_roles/mixins.py +++ b/myproject/user_roles/mixins.py @@ -18,6 +18,10 @@ class RoleBasedAdminMixin: if not super().has_module_permission(request): return False + # Superuser имеет полный доступ + if request.user.is_superuser: + return True + if not self.required_roles: return True # Нет ограничений @@ -28,6 +32,10 @@ class RoleBasedAdminMixin: if not super().has_view_permission(request, obj): return False + # Superuser имеет полный доступ + if request.user.is_superuser: + return True + if not self.required_roles: return True @@ -38,6 +46,10 @@ class RoleBasedAdminMixin: if not super().has_add_permission(request): return False + # Superuser имеет полный доступ + if request.user.is_superuser: + return True + if not self.required_roles: return True @@ -48,6 +60,10 @@ class RoleBasedAdminMixin: if not super().has_change_permission(request, obj): return False + # Superuser имеет полный доступ + if request.user.is_superuser: + return True + if not self.required_roles: return True @@ -58,6 +74,10 @@ class RoleBasedAdminMixin: if not super().has_delete_permission(request, obj): return False + # Superuser имеет полный доступ + if request.user.is_superuser: + return True + if not self.required_roles: return True diff --git a/myproject/user_roles/templates/user_roles/user_role_create.html b/myproject/user_roles/templates/user_roles/user_role_create.html new file mode 100644 index 0000000..fd0dc21 --- /dev/null +++ b/myproject/user_roles/templates/user_roles/user_role_create.html @@ -0,0 +1,64 @@ +{% extends "base.html" %} + +{% block title %}Добавить пользователя{% endblock %} + +{% block content %} +
+
+
+
+
+

Добавить пользователя

+
+
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +
+ {% csrf_token %} + +
+ + +
Email пользователя для входа
+
+ +
+ + +
Полное имя пользователя
+
+ +
+ + +
+ +
+ + +
Оставьте пустым для автогенерации. Пароль будет показан после создания.
+
+ +
+ Отмена + +
+
+
+
+
+
+
+{% endblock %} diff --git a/myproject/user_roles/templates/user_roles/user_role_delete.html b/myproject/user_roles/templates/user_roles/user_role_delete.html new file mode 100644 index 0000000..9b750c8 --- /dev/null +++ b/myproject/user_roles/templates/user_roles/user_role_delete.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} + +{% block title %}Удалить пользователя{% endblock %} + +{% block content %} +
+
+
+
+
+

Удалить доступ пользователя

+
+
+
+ + Внимание! Вы собираетесь удалить доступ пользователя к системе. +
+ +
+
Email:
+
{{ user_role.user.email }}
+ +
Имя:
+
{{ user_role.user.name }}
+ +
Роль:
+
+ + {{ user_role.role.name }} + +
+ +
Создан:
+
{{ user_role.created_at|date:"d.m.Y H:i" }}
+
+ +

+ Пользователь больше не сможет войти в систему через этот магазин. + Учетная запись пользователя не будет удалена. +

+ +
+ {% csrf_token %} +
+ Отмена + +
+
+
+
+
+
+
+{% endblock %} diff --git a/myproject/user_roles/templates/user_roles/user_role_edit.html b/myproject/user_roles/templates/user_roles/user_role_edit.html new file mode 100644 index 0000000..b41ca75 --- /dev/null +++ b/myproject/user_roles/templates/user_roles/user_role_edit.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} + +{% block title %}Изменить роль пользователя{% endblock %} + +{% block content %} +
+
+
+
+
+

Изменить роль пользователя

+
+
+ {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +
+ +

{{ user_role.user.email }} ({{ user_role.user.name }})

+
+ +
+ {% csrf_token %} + +
+ + +
+ +
+
+ + +
+
Снимите галочку, чтобы временно отключить доступ
+
+ +
+ Отмена + +
+
+
+
+
+
+
+{% endblock %} diff --git a/myproject/user_roles/templates/user_roles/user_role_list.html b/myproject/user_roles/templates/user_roles/user_role_list.html new file mode 100644 index 0000000..710f8c6 --- /dev/null +++ b/myproject/user_roles/templates/user_roles/user_role_list.html @@ -0,0 +1,93 @@ +{% extends "base.html" %} + +{% block title %}Управление ролями пользователей{% endblock %} + +{% block content %} +
+
+

Управление ролями пользователей

+ + Добавить пользователя + +
+ + {% if messages %} + {% for message in messages %} + + {% endfor %} + {% endif %} + +
+
+ {% if user_roles %} +
+ + + + + + + + + + + + + + {% for user_role in user_roles %} + + + + + + + + + + {% endfor %} + +
EmailИмяРольСтатусСозданСоздалДействия
{{ user_role.user.email }}{{ user_role.user.name }} + + {{ user_role.role.name }} + + + {% if user_role.is_active %} + Активен + {% else %} + Неактивен + {% endif %} + {{ user_role.created_at|date:"d.m.Y H:i" }} + {% if user_role.created_by %} + {{ user_role.created_by.name }} + {% else %} + — + {% endif %} + + + Изменить + + + Удалить + +
+
+ {% else %} +
+

Нет пользователей с ролями

+ + Добавить первого пользователя + +
+ {% endif %} +
+
+
+{% endblock %} diff --git a/myproject/user_roles/urls.py b/myproject/user_roles/urls.py new file mode 100644 index 0000000..6d39bcc --- /dev/null +++ b/myproject/user_roles/urls.py @@ -0,0 +1,11 @@ +from django.urls import path +from . import views + +app_name = 'user_roles' + +urlpatterns = [ + path('', views.user_role_list, name='list'), + path('create/', views.user_role_create, name='create'), + path('/edit/', views.user_role_edit, name='edit'), + path('/delete/', views.user_role_delete, name='delete'), +] diff --git a/myproject/user_roles/views.py b/myproject/user_roles/views.py index 91ea44a..28148fc 100644 --- a/myproject/user_roles/views.py +++ b/myproject/user_roles/views.py @@ -1,3 +1,110 @@ -from django.shortcuts import render +from django.shortcuts import render, redirect, get_object_or_404 +from django.contrib.auth.decorators import login_required +from django.contrib import messages +from django.contrib.auth import get_user_model +from user_roles.models import Role, UserRole +from user_roles.services import RoleService +from user_roles.decorators import owner_required -# Create your views here. +User = get_user_model() + + +@login_required +@owner_required +def user_role_list(request): + """Список пользователей с их ролями""" + user_roles = UserRole.objects.select_related('user', 'role', 'created_by').all() + roles = Role.objects.all() + + context = { + 'user_roles': user_roles, + 'roles': roles, + } + return render(request, 'user_roles/user_role_list.html', context) + + +@login_required +@owner_required +def user_role_create(request): + """Создание нового пользователя с ролью""" + if request.method == 'POST': + email = request.POST.get('email') + name = request.POST.get('name') + role_code = request.POST.get('role') + password = request.POST.get('password', User.objects.make_random_password(12)) + + try: + # Создаем пользователя + user = User.objects.create_user( + email=email, + name=name, + password=password, + is_email_confirmed=True + ) + + # Назначаем роль + RoleService.assign_role_to_user(user, role_code, created_by=request.user) + + messages.success(request, f'Пользователь {email} создан с ролью {role_code}. Пароль: {password}') + return redirect('user_roles:list') + + except Exception as e: + messages.error(request, f'Ошибка при создании пользователя: {str(e)}') + + roles = Role.objects.all() + context = { + 'roles': roles, + } + return render(request, 'user_roles/user_role_create.html', context) + + +@login_required +@owner_required +def user_role_edit(request, pk): + """Изменение роли пользователя""" + user_role = get_object_or_404(UserRole, pk=pk) + + if request.method == 'POST': + role_code = request.POST.get('role') + is_active = request.POST.get('is_active') == 'on' + + try: + # Обновляем роль + role = RoleService.get_role_by_code(role_code) + if not role: + raise ValueError(f"Роль '{role_code}' не найдена") + + user_role.role = role + user_role.is_active = is_active + user_role.save() + + messages.success(request, f'Роль пользователя {user_role.user.email} обновлена') + return redirect('user_roles:list') + + except Exception as e: + messages.error(request, f'Ошибка при обновлении роли: {str(e)}') + + roles = Role.objects.all() + context = { + 'user_role': user_role, + 'roles': roles, + } + return render(request, 'user_roles/user_role_edit.html', context) + + +@login_required +@owner_required +def user_role_delete(request, pk): + """Удаление роли пользователя (отключение доступа)""" + user_role = get_object_or_404(UserRole, pk=pk) + + if request.method == 'POST': + email = user_role.user.email + user_role.delete() + messages.success(request, f'Доступ пользователя {email} удален') + return redirect('user_roles:list') + + context = { + 'user_role': user_role, + } + return render(request, 'user_roles/user_role_delete.html', context)