diff --git a/myproject/products/views/configurablekit_views.py b/myproject/products/views/configurablekit_views.py index 15e8d11..b9b9519 100644 --- a/myproject/products/views/configurablekit_views.py +++ b/myproject/products/views/configurablekit_views.py @@ -2,7 +2,7 @@ CRUD представления для вариативных товаров (ConfigurableKitProduct). """ from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.urls import reverse_lazy from django.db.models import Q, Prefetch @@ -12,6 +12,7 @@ from django.views.decorators.http import require_POST from django.contrib.auth.decorators import login_required from django.db import transaction +from user_roles.mixins import ManagerOwnerRequiredMixin from ..models import ConfigurableKitProduct, ConfigurableKitOption, ProductKit, ConfigurableKitProductAttribute from ..forms import ( ConfigurableKitProductForm, @@ -22,7 +23,7 @@ from ..forms import ( ) -class ConfigurableKitProductListView(LoginRequiredMixin, ListView): +class ConfigurableKitProductListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, ListView): model = ConfigurableKitProduct template_name = 'products/configurablekit_list.html' context_object_name = 'configurable_kits' @@ -79,7 +80,7 @@ class ConfigurableKitProductListView(LoginRequiredMixin, ListView): return context -class ConfigurableKitProductDetailView(LoginRequiredMixin, DetailView): +class ConfigurableKitProductDetailView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DetailView): model = ConfigurableKitProduct template_name = 'products/configurablekit_detail.html' context_object_name = 'configurable_kit' @@ -103,7 +104,7 @@ class ConfigurableKitProductDetailView(LoginRequiredMixin, DetailView): return context -class ConfigurableKitProductCreateView(LoginRequiredMixin, CreateView): +class ConfigurableKitProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, CreateView): model = ConfigurableKitProduct form_class = ConfigurableKitProductForm template_name = 'products/configurablekit_form.html' @@ -375,7 +376,7 @@ class ConfigurableKitProductCreateView(LoginRequiredMixin, CreateView): return reverse_lazy('products:configurablekit-detail', kwargs={'pk': self.object.pk}) -class ConfigurableKitProductUpdateView(LoginRequiredMixin, UpdateView): +class ConfigurableKitProductUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, UpdateView): model = ConfigurableKitProduct form_class = ConfigurableKitProductForm template_name = 'products/configurablekit_form.html' @@ -652,7 +653,7 @@ class ConfigurableKitProductUpdateView(LoginRequiredMixin, UpdateView): return reverse_lazy('products:configurablekit-detail', kwargs={'pk': self.object.pk}) -class ConfigurableKitProductDeleteView(LoginRequiredMixin, DeleteView): +class ConfigurableKitProductDeleteView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DeleteView): model = ConfigurableKitProduct template_name = 'products/configurablekit_confirm_delete.html' success_url = reverse_lazy('products:configurablekit-list') diff --git a/myproject/products/views/photo_management.py b/myproject/products/views/photo_management.py index 1b88e5c..68da6c4 100644 --- a/myproject/products/views/photo_management.py +++ b/myproject/products/views/photo_management.py @@ -8,7 +8,6 @@ from django.contrib import messages from django.http import JsonResponse from django.views.decorators.http import require_http_methods from django.contrib.auth.decorators import login_required -from django.contrib.auth.mixins import PermissionRequiredMixin from ..models import ProductPhoto, ProductKitPhoto, ProductCategoryPhoto diff --git a/myproject/products/views/product_views.py b/myproject/products/views/product_views.py index ae32108..3d065ef 100644 --- a/myproject/products/views/product_views.py +++ b/myproject/products/views/product_views.py @@ -2,7 +2,7 @@ CRUD представления для товаров (Product). """ from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.urls import reverse_lazy from django.db.models import Q, Sum, Value, DecimalField @@ -13,13 +13,13 @@ from ..models import Product, ProductCategory, ProductTag, ProductKit from ..forms import ProductForm from .utils import handle_photos from ..models import ProductPhoto +from user_roles.mixins import ManagerOwnerRequiredMixin -class ProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class ProductListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, ListView): model = Product template_name = 'products/product_list.html' context_object_name = 'products' - permission_required = 'products.view_product' paginate_by = 10 def get_queryset(self): @@ -109,11 +109,10 @@ class ProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return context -class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): +class ProductCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, CreateView): model = Product form_class = ProductForm template_name = 'products/product_form.html' - permission_required = 'products.add_product' def get_success_url(self): return reverse_lazy('products:products-list') @@ -156,11 +155,10 @@ class ProductCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView) return self.form_invalid(form) -class ProductDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class ProductDetailView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DetailView): model = Product template_name = 'products/product_detail.html' context_object_name = 'product' - permission_required = 'products.view_product' def get_queryset(self): # Предзагрузка фотографий и аннотация остатков @@ -180,11 +178,10 @@ class ProductDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView) return context -class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): +class ProductUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, UpdateView): model = Product form_class = ProductForm template_name = 'products/product_form.html' - permission_required = 'products.change_product' def get_success_url(self): return reverse_lazy('products:products-list') @@ -234,25 +231,23 @@ class ProductUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView) return self.form_invalid(form) -class ProductDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): +class ProductDeleteView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DeleteView): model = Product template_name = 'products/product_confirm_delete.html' context_object_name = 'product' - permission_required = 'products.delete_product' def get_success_url(self): messages.success(self.request, f'Товар "{self.object.name}" успешно удален!') return reverse_lazy('products:products-list') -class CombinedProductListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class CombinedProductListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, ListView): """ Объединенное представление для товаров и комплектов. Показывает оба типа продуктов в одном списке с возможностью фильтрации по типу. """ template_name = 'products/products_list.html' context_object_name = 'items' - permission_required = 'products.view_product' paginate_by = 20 def get_queryset(self): diff --git a/myproject/products/views/productkit_views.py b/myproject/products/views/productkit_views.py index 13e4c5d..11e425a 100644 --- a/myproject/products/views/productkit_views.py +++ b/myproject/products/views/productkit_views.py @@ -2,22 +2,22 @@ CRUD представления для комплектов товаров (ProductKit). """ from django.contrib import messages -from django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin +from django.contrib.auth.mixins import LoginRequiredMixin from django.views.generic import ListView, CreateView, DetailView, UpdateView, DeleteView from django.urls import reverse_lazy from django.shortcuts import redirect from django.db import transaction, IntegrityError +from user_roles.mixins import ManagerOwnerRequiredMixin from ..models import ProductKit, ProductCategory, ProductTag, ProductKitPhoto, Product, ProductVariantGroup from ..forms import ProductKitForm, KitItemFormSetCreate, KitItemFormSetUpdate from .utils import handle_photos -class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): +class ProductKitListView(LoginRequiredMixin, ManagerOwnerRequiredMixin, ListView): model = ProductKit template_name = 'products/productkit_list.html' context_object_name = 'kits' - permission_required = 'products.view_productkit' paginate_by = 10 def get_queryset(self): @@ -89,14 +89,13 @@ class ProductKitListView(LoginRequiredMixin, PermissionRequiredMixin, ListView): return context -class ProductKitCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateView): +class ProductKitCreateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, CreateView): """ View для создания нового комплекта с добавлением компонентов на одной странице. """ model = ProductKit form_class = ProductKitForm template_name = 'products/productkit_create.html' - permission_required = 'products.add_productkit' def post(self, request, *args, **kwargs): """ @@ -248,14 +247,13 @@ class ProductKitCreateView(LoginRequiredMixin, PermissionRequiredMixin, CreateVi return self.render_to_response(context) -class ProductKitUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): +class ProductKitUpdateView(LoginRequiredMixin, ManagerOwnerRequiredMixin, UpdateView): """ View для редактирования существующего комплекта и добавления товаров. """ model = ProductKit form_class = ProductKitForm template_name = 'products/productkit_edit.html' - permission_required = 'products.change_productkit' def post(self, request, *args, **kwargs): """ @@ -453,7 +451,7 @@ class ProductKitUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateVi return reverse_lazy('products:products-list') -class ProductKitDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailView): +class ProductKitDetailView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DetailView): """ View для просмотра деталей комплекта. Показывает все компоненты, цены, фотографии. @@ -461,7 +459,6 @@ class ProductKitDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVi model = ProductKit template_name = 'products/productkit_detail.html' context_object_name = 'kit' - permission_required = 'products.view_productkit' def get_queryset(self): # Prefetch для оптимизации запросов @@ -482,21 +479,20 @@ class ProductKitDetailView(LoginRequiredMixin, PermissionRequiredMixin, DetailVi return context -class ProductKitDeleteView(LoginRequiredMixin, PermissionRequiredMixin, DeleteView): +class ProductKitDeleteView(LoginRequiredMixin, ManagerOwnerRequiredMixin, DeleteView): """ View для удаления комплекта. """ model = ProductKit template_name = 'products/productkit_confirm_delete.html' context_object_name = 'kit' - permission_required = 'products.delete_productkit' def get_success_url(self): messages.success(self.request, f'Комплект "{self.object.name}" успешно удален!') return reverse_lazy('products:productkit-list') -class ProductKitMakePermanentView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView): +class ProductKitMakePermanentView(LoginRequiredMixin, ManagerOwnerRequiredMixin, UpdateView): """ View для преобразования временного комплекта в постоянный. Позволяет отредактировать название, добавить категории, теги перед сохранением. @@ -504,7 +500,6 @@ class ProductKitMakePermanentView(LoginRequiredMixin, PermissionRequiredMixin, U model = ProductKit template_name = 'products/productkit_make_permanent.html' context_object_name = 'kit' - permission_required = 'products.change_productkit' fields = ['name', 'description', 'categories', 'tags', 'sale_price'] def get_queryset(self): diff --git a/myproject/user_roles/mixins.py b/myproject/user_roles/mixins.py index 5e53895..347f403 100644 --- a/myproject/user_roles/mixins.py +++ b/myproject/user_roles/mixins.py @@ -1,4 +1,5 @@ from django.contrib import admin +from django.contrib.auth.mixins import AccessMixin from django.core.exceptions import PermissionDenied from user_roles.services import RoleService @@ -92,3 +93,44 @@ class OwnerOnlyAdminMixin(RoleBasedAdminMixin): class ManagerOwnerAdminMixin(RoleBasedAdminMixin): """Миксин для админки, доступной менеджеру и владельцу""" required_roles = ['owner', 'manager'] + + +# Миксины для обычных views (не админки) + +class RoleRequiredMixin(AccessMixin): + """ + Миксин для проверки ролей в обычных CBV. + + Использование: + class MyView(RoleRequiredMixin, ListView): + required_roles = ['owner', 'manager'] + """ + required_roles = [] # Список ролей, которым разрешен доступ + + def dispatch(self, request, *args, **kwargs): + if not request.user.is_authenticated: + return self.handle_no_permission() + + # Superuser имеет полный доступ + if request.user.is_superuser: + return super().dispatch(request, *args, **kwargs) + + # Если роли не указаны, доступ разрешен всем аутентифицированным + if not self.required_roles: + return super().dispatch(request, *args, **kwargs) + + # Проверяем роль пользователя + if not RoleService.user_has_role(request.user, *self.required_roles): + raise PermissionDenied("У вас нет прав для доступа к этой странице") + + return super().dispatch(request, *args, **kwargs) + + +class OwnerRequiredMixin(RoleRequiredMixin): + """Миксин для view, доступного только владельцу""" + required_roles = ['owner'] + + +class ManagerOwnerRequiredMixin(RoleRequiredMixin): + """Миксин для view, доступного менеджеру и владельцу""" + required_roles = ['owner', 'manager']