From 978ed9a8190406c9bac626e14011c9f065a209cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=98=D0=B3=D0=BE=D1=80=D1=8C=20=D0=9D=D0=B0=D0=B4=D0=B5?= =?UTF-8?q?=D0=B8=D0=BD?= Date: Wed, 10 Dec 2025 17:02:40 +0300 Subject: [PATCH] fix: aligned ProcurementItem permissions with ProcurementRequest logic - Added comprehensive permission checks in perform_create() - Created _check_item_edit_permission() helper method - Updated perform_update() and perform_destroy() to use helper - Permission logic now matches CanManageProcurementRequest: * Admins/Staff can edit items in DRAFT requests * Users with change_procurementrequest permission * Request author can edit their own requests * Department head can edit requests from their department - Fixes 403 Forbidden error when creating/editing/deleting items --- backend/api/v1/procurement/views.py | 119 ++++++++++++++++++-------- backend/procurement/views_frontend.py | 29 +++++-- 2 files changed, 105 insertions(+), 43 deletions(-) diff --git a/backend/api/v1/procurement/views.py b/backend/api/v1/procurement/views.py index 4d9de8bf..5766cb66 100644 --- a/backend/api/v1/procurement/views.py +++ b/backend/api/v1/procurement/views.py @@ -688,57 +688,100 @@ class ProcurementItemViewSet(viewsets.ModelViewSet): return [permission() for permission in permission_classes] def perform_create(self, serializer): - """Проверяем права при создании позиции.""" + """Проверяем права при создании позиции. + + Логика соответствует CanManageProcurementRequest: + - Админы/Staff могут добавлять в любые заявки в DRAFT + - Модельные права (change_procurementrequest) → любые заявки в DRAFT + - Автор заявки → свои заявки в DRAFT + - Начальник отдела → заявки своего отдела в DRAFT + """ + from rest_framework.exceptions import PermissionDenied + procurement_request = serializer.validated_data.get('request') + user = self.request.user - # Проверяем, что пользователь - создатель заявки - if procurement_request.requestor != self.request.user: - from rest_framework.exceptions import PermissionDenied - raise PermissionDenied( - "Вы можете добавлять позиции только в свои заявки" - ) - - # Проверяем, что заявка редактируема + # Проверяем, что заявка в DRAFT if not procurement_request.is_editable: - from rest_framework.exceptions import ValidationError - raise ValidationError( - "Нельзя добавлять позиции в заявку не в статусе черновика" + raise PermissionDenied( + "Нельзя добавлять позиции в заявку со статусом '{}'".format( + procurement_request.get_status_display() + ) ) - serializer.save() + # Админы могут редактировать + if user.is_superuser or user.is_staff: + serializer.save() + return + + # Модельные права + if user.has_perm('procurement.change_procurementrequest'): + serializer.save() + return + + # Автор заявки + if procurement_request.requestor == user: + serializer.save() + return + + # Начальник отдела + if procurement_request.department.head == user: + serializer.save() + return + + # Нет прав + raise PermissionDenied( + "Вы не можете добавлять позиции в эту заявку" + ) + + def _check_item_edit_permission(self, item): + """Проверка прав на редактирование позиции. + + Вынесена в отдельный метод для переиспользования. + """ + from rest_framework.exceptions import PermissionDenied + + user = self.request.user + procurement_request = item.request + + # Проверяем, что заявка в DRAFT + if not procurement_request.is_editable: + raise PermissionDenied( + "Нельзя изменять позиции в заявке со статусом '{}'".format( + procurement_request.get_status_display() + ) + ) + + # Админы могут редактировать + if user.is_superuser or user.is_staff: + return True + + # Модельные права + if user.has_perm('procurement.change_procurementrequest'): + return True + + # Автор заявки + if procurement_request.requestor == user: + return True + + # Начальник отдела + if procurement_request.department.head == user: + return True + + # Нет прав + raise PermissionDenied( + "Вы не можете изменять позиции в этой заявке" + ) def perform_update(self, serializer): """Проверяем права при обновлении позиции.""" item = self.get_object() - - if item.request.requestor != self.request.user: - from rest_framework.exceptions import PermissionDenied - raise PermissionDenied( - "Вы можете изменять только позиции своих заявок" - ) - - if not item.request.is_editable: - from rest_framework.exceptions import ValidationError - raise ValidationError( - "Нельзя изменять позиции в заявке не в статусе черновика" - ) - + self._check_item_edit_permission(item) serializer.save() def perform_destroy(self, instance): """Проверяем права при удалении позиции.""" - if instance.request.requestor != self.request.user: - from rest_framework.exceptions import PermissionDenied - raise PermissionDenied( - "Вы можете удалять только позиции своих заявок" - ) - - if not instance.request.is_editable: - from rest_framework.exceptions import ValidationError - raise ValidationError( - "Нельзя удалять позиции из заявки не в статусе черновика" - ) - + self._check_item_edit_permission(instance) instance.delete() @action(detail=True, methods=['post']) diff --git a/backend/procurement/views_frontend.py b/backend/procurement/views_frontend.py index 5dbb2c1b..d90b006f 100644 --- a/backend/procurement/views_frontend.py +++ b/backend/procurement/views_frontend.py @@ -93,6 +93,15 @@ def request_detail(request, pk): """Детальная страница заявки. Загружает данные из БД и передаёт в шаблон. + + Права на редактирование (can_edit): + - Админы/Staff → любая заявка в DRAFT + - Модельные права (change_procurementrequest) → любая заявка в DRAFT + - Автор заявки → своя заявка в DRAFT + - Начальник отдела → заявки своего отдела в DRAFT + + Права на согласование (can_approve): + - Пользователи с активным approval для этой заявки """ from django.shortcuts import get_object_or_404 from procurement.models import ProcurementRequest @@ -106,11 +115,21 @@ def request_detail(request, pk): user = request.user - # Проверяем права на редактирование - can_edit = ( - request_obj.requestor == user and - request_obj.status == 'draft' - ) + # Проверяем права на редактирование (синхронизировано с API) + can_edit = False + if request_obj.is_editable: # Только DRAFT + # Админы могут редактировать + if user.is_superuser or user.is_staff: + can_edit = True + # Модельные права + elif user.has_perm('procurement.change_procurementrequest'): + can_edit = True + # Автор заявки + elif request_obj.requestor == user: + can_edit = True + # Начальник отдела + elif request_obj.department.head == user: + can_edit = True # Проверяем права на согласование can_approve = (