From 4148d9e02c88aabfb928a91c975336865d50b793 Mon Sep 17 00:00:00 2001 From: Egor fureunoir Gorbunov Date: Sun, 8 Jun 2025 14:18:26 +0300 Subject: [PATCH] Features: Added feedbacks support for OrderProducts --- core/docs/drf/viewsets.py | 195 +++++++++++++++++++++++++----------- core/models.py | 10 ++ core/serializers/utility.py | 11 ++ core/viewsets.py | 46 ++++++++- 4 files changed, 199 insertions(+), 63 deletions(-) diff --git a/core/docs/drf/viewsets.py b/core/docs/drf/viewsets.py index 7bf75f96..b2e99959 100644 --- a/core/docs/drf/viewsets.py +++ b/core/docs/drf/viewsets.py @@ -24,6 +24,8 @@ from core.serializers import ( FeedbackDetailSerializer, FeedbackSimpleSerializer, OrderDetailSerializer, + OrderProductDetailSerializer, + OrderProductSimpleSerializer, OrderSimpleSerializer, ProductDetailSerializer, ProductSimpleSerializer, @@ -32,7 +34,7 @@ from core.serializers import ( WishlistDetailSerializer, WishlistSimpleSerializer, ) -from core.serializers.utility import AddressCreateSerializer, AddressSuggestionSerializer +from core.serializers.utility import AddressCreateSerializer, AddressSuggestionSerializer, DoFeedbackSerializer from payments.serializers import TransactionProcessSerializer ATTRIBUTE_GROUP_SCHEMA = { @@ -42,11 +44,11 @@ ATTRIBUTE_GROUP_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single attribute group (detailed view)"), - responses={status.HTTP_200_OK: AttributeGroupDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeGroupDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create an attribute group"), - responses={status.HTTP_201_CREATED: AttributeGroupDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: AttributeGroupDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete an attribute group"), @@ -54,11 +56,11 @@ ATTRIBUTE_GROUP_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing attribute group saving non-editables"), - responses={status.HTTP_200_OK: AttributeGroupDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeGroupDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing attribute group saving non-editables"), - responses={status.HTTP_200_OK: AttributeGroupDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeGroupDetailSerializer(), **BASE_ERRORS}, ), } @@ -69,11 +71,11 @@ ATTRIBUTE_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single attribute (detailed view)"), - responses={status.HTTP_200_OK: AttributeDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create an attribute"), - responses={status.HTTP_201_CREATED: AttributeDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: AttributeDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete an attribute"), @@ -81,11 +83,11 @@ ATTRIBUTE_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing attribute saving non-editables"), - responses={status.HTTP_200_OK: AttributeDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing attribute saving non-editables"), - responses={status.HTTP_200_OK: AttributeDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeDetailSerializer(), **BASE_ERRORS}, ), } @@ -96,11 +98,11 @@ ATTRIBUTE_VALUE_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single attribute value (detailed view)"), - responses={status.HTTP_200_OK: AttributeValueDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeValueDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create an attribute value"), - responses={status.HTTP_201_CREATED: AttributeValueDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: AttributeValueDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete an attribute value"), @@ -108,11 +110,11 @@ ATTRIBUTE_VALUE_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing attribute value saving non-editables"), - responses={status.HTTP_200_OK: AttributeValueDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeValueDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing attribute value saving non-editables"), - responses={status.HTTP_200_OK: AttributeValueDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: AttributeValueDetailSerializer(), **BASE_ERRORS}, ), } @@ -123,11 +125,11 @@ CATEGORY_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single category (detailed view)"), - responses={status.HTTP_200_OK: CategoryDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: CategoryDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create a category"), - responses={status.HTTP_201_CREATED: CategoryDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: CategoryDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete a category"), @@ -135,11 +137,11 @@ CATEGORY_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing category saving non-editables"), - responses={status.HTTP_200_OK: CategoryDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: CategoryDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing category saving non-editables"), - responses={status.HTTP_200_OK: CategoryDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: CategoryDetailSerializer(), **BASE_ERRORS}, ), } @@ -151,12 +153,12 @@ ORDER_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single order (detailed view)"), - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create an order"), description=_("doesn't work for non-staff users."), - responses={status.HTTP_201_CREATED: OrderDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: OrderDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete an order"), @@ -164,11 +166,11 @@ ORDER_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing order saving non-editables"), - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing order saving non-editables"), - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), "buy": extend_schema( summary=_("purchase an order"), @@ -177,45 +179,45 @@ ORDER_SCHEMA = { " the purchase is completed using the user's balance;" " if `force_payment` is used, a transaction is initiated." ), - request=BuyOrderSerializer, + request=BuyOrderSerializer(), responses={ - status.HTTP_200_OK: OrderDetailSerializer, - status.HTTP_202_ACCEPTED: TransactionProcessSerializer, + status.HTTP_200_OK: OrderDetailSerializer(), + status.HTTP_202_ACCEPTED: TransactionProcessSerializer(), **BASE_ERRORS, }, ), "buy_unregistered": extend_schema( summary=_("purchase an order without account creation"), description=_("finalizes the order purchase for a non-registered user."), - request=BuyUnregisteredOrderSerializer, + request=BuyUnregisteredOrderSerializer(), responses={ - status.HTTP_202_ACCEPTED: TransactionProcessSerializer, + status.HTTP_202_ACCEPTED: TransactionProcessSerializer(), **BASE_ERRORS, }, ), "add_order_product": extend_schema( summary=_("add product to order"), description=_("adds a product to an order using the provided `product_uuid` and `attributes`."), - request=AddOrderProductSerializer, - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + request=AddOrderProductSerializer(), + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), "bulk_add_order_products": extend_schema( summary=_("add a list of products to order, quantities will not count"), description=_("adds a list of products to an order using the provided `product_uuid` and `attributes`."), - request=BulkAddOrderProductsSerializer, - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + request=BulkAddOrderProductsSerializer(), + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), "remove_order_product": extend_schema( summary=_("remove product from order"), description=_("removes a product from an order using the provided `product_uuid` and `attributes`."), - request=RemoveOrderProductSerializer, - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + request=RemoveOrderProductSerializer(), + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), "bulk_remove_order_products": extend_schema( summary=_("remove product from order, quantities will not count"), description=_("removes a list of products from an order using the provided `product_uuid` and `attributes`"), - request=BulkRemoveOrderProductsSerializer, - responses={status.HTTP_200_OK: OrderDetailSerializer, **BASE_ERRORS}, + request=BulkRemoveOrderProductsSerializer(), + responses={status.HTTP_200_OK: OrderDetailSerializer(), **BASE_ERRORS}, ), } @@ -227,12 +229,12 @@ WISHLIST_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single wishlist (detailed view)"), - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create an wishlist"), description=_("Doesn't work for non-staff users."), - responses={status.HTTP_201_CREATED: WishlistDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: WishlistDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete an wishlist"), @@ -240,35 +242,35 @@ WISHLIST_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing wishlist saving non-editables"), - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing wishlist saving non-editables"), - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), "add_wishlist_product": extend_schema( summary=_("add product to wishlist"), description=_("adds a product to an wishlist using the provided `product_uuid`"), - request=AddWishlistProductSerializer, - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + request=AddWishlistProductSerializer(), + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), "remove_wishlist_product": extend_schema( summary=_("remove product from wishlist"), description=_("removes a product from an wishlist using the provided `product_uuid`"), - request=RemoveWishlistProductSerializer, - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + request=RemoveWishlistProductSerializer(), + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), "bulk_add_wishlist_products": extend_schema( summary=_("add many products to wishlist"), description=_("adds many products to an wishlist using the provided `product_uuids`"), - request=BulkAddWishlistProductSerializer, - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + request=BulkAddWishlistProductSerializer(), + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), "bulk_remove_wishlist_products": extend_schema( summary=_("remove many products from wishlist"), description=_("removes many products from an wishlist using the provided `product_uuids`"), - request=BulkRemoveWishlistProductSerializer, - responses={status.HTTP_200_OK: WishlistDetailSerializer, **BASE_ERRORS}, + request=BulkRemoveWishlistProductSerializer(), + responses={status.HTTP_200_OK: WishlistDetailSerializer(), **BASE_ERRORS}, ), } @@ -397,14 +399,14 @@ PRODUCT_SCHEMA = { ), ], responses={ - status.HTTP_200_OK: ProductDetailSerializer, + status.HTTP_200_OK: ProductDetailSerializer(), **BASE_ERRORS, }, ), "create": extend_schema( summary=_("create a product"), responses={ - status.HTTP_201_CREATED: ProductDetailSerializer, + status.HTTP_201_CREATED: ProductDetailSerializer(), **BASE_ERRORS, }, ), @@ -419,7 +421,7 @@ PRODUCT_SCHEMA = { ), ], responses={ - status.HTTP_200_OK: ProductDetailSerializer, + status.HTTP_200_OK: ProductDetailSerializer(), **BASE_ERRORS, }, ), @@ -434,7 +436,7 @@ PRODUCT_SCHEMA = { ), ], responses={ - status.HTTP_200_OK: ProductDetailSerializer, + status.HTTP_200_OK: ProductDetailSerializer(), **BASE_ERRORS, }, ), @@ -453,6 +455,21 @@ PRODUCT_SCHEMA = { **BASE_ERRORS, }, ), + "feedbacks": extend_schema( + summary=_("lists all permitted feedbacks for a product"), + parameters=[ + OpenApiParameter( + name="lookup", + location="path", + description=_("Product UUID or slug"), + type=str, + ), + ], + responses={ + status.HTTP_200_OK: FeedbackDetailSerializer(many=True), + **BASE_ERRORS, + }, + ), } ADDRESS_SCHEMA = { @@ -466,15 +483,15 @@ ADDRESS_SCHEMA = { "retrieve": extend_schema( summary=_("retrieve a single address"), responses={ - status.HTTP_200_OK: AddressSerializer, + status.HTTP_200_OK: AddressSerializer(), **BASE_ERRORS, }, ), "create": extend_schema( summary=_("create a new address"), - request=AddressCreateSerializer, + request=AddressCreateSerializer(), responses={ - status.HTTP_201_CREATED: AddressSerializer, + status.HTTP_201_CREATED: AddressSerializer(), **BASE_ERRORS, }, ), @@ -487,17 +504,17 @@ ADDRESS_SCHEMA = { ), "update": extend_schema( summary=_("update an entire address"), - request=AddressSerializer, + request=AddressSerializer(), responses={ - status.HTTP_200_OK: AddressSerializer, + status.HTTP_200_OK: AddressSerializer(), **BASE_ERRORS, }, ), "partial_update": extend_schema( summary=_("partially update an address"), - request=AddressSerializer, + request=AddressSerializer(), responses={ - status.HTTP_200_OK: AddressSerializer, + status.HTTP_200_OK: AddressSerializer(), **BASE_ERRORS, }, ), @@ -531,11 +548,11 @@ FEEDBACK_SCHEMA = { ), "retrieve": extend_schema( summary=_("retrieve a single feedback (detailed view)"), - responses={status.HTTP_200_OK: FeedbackDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: FeedbackDetailSerializer(), **BASE_ERRORS}, ), "create": extend_schema( summary=_("create a feedback"), - responses={status.HTTP_201_CREATED: FeedbackDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_201_CREATED: FeedbackDetailSerializer(), **BASE_ERRORS}, ), "destroy": extend_schema( summary=_("delete a feedback"), @@ -543,10 +560,66 @@ FEEDBACK_SCHEMA = { ), "update": extend_schema( summary=_("rewrite an existing feedback saving non-editables"), - responses={status.HTTP_200_OK: FeedbackDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: FeedbackDetailSerializer(), **BASE_ERRORS}, ), "partial_update": extend_schema( summary=_("rewrite some fields of an existing feedback saving non-editables"), - responses={status.HTTP_200_OK: FeedbackDetailSerializer, **BASE_ERRORS}, + responses={status.HTTP_200_OK: FeedbackDetailSerializer(), **BASE_ERRORS}, + ), +} + +ORDER_PRODUCT_SCHEMA = { + "list": extend_schema( + summary=_("list all order–product relations (simple view)"), + responses={ + status.HTTP_200_OK: OrderProductSimpleSerializer(many=True), + **BASE_ERRORS, + }, + ), + "retrieve": extend_schema( + summary=_("retrieve a single order–product relation (detailed view)"), + responses={ + status.HTTP_200_OK: OrderProductDetailSerializer(), + **BASE_ERRORS, + }, + ), + "create": extend_schema( + summary=_("create a new order–product relation"), + responses={ + status.HTTP_201_CREATED: OrderProductDetailSerializer(), + **BASE_ERRORS, + }, + ), + "update": extend_schema( + summary=_("replace an existing order–product relation"), + responses={ + status.HTTP_200_OK: OrderProductDetailSerializer(), + **BASE_ERRORS, + }, + ), + "partial_update": extend_schema( + summary=_("partially update an existing order–product relation"), + responses={ + status.HTTP_200_OK: OrderProductDetailSerializer(), + **BASE_ERRORS, + }, + ), + "destroy": extend_schema( + summary=_("delete an order–product relation"), + responses={ + status.HTTP_204_NO_CONTENT: {}, + **BASE_ERRORS, + }, + ), + "do_feedback": extend_schema( + summary=_("add or remove feedback on an order–product relation"), + request=DoFeedbackSerializer, + responses={ + status.HTTP_201_CREATED: FeedbackDetailSerializer(), + status.HTTP_204_NO_CONTENT: {}, + status.HTTP_400_BAD_REQUEST: {}, + status.HTTP_404_NOT_FOUND: {}, + **BASE_ERRORS, + }, ), } diff --git a/core/models.py b/core/models.py index 69bff2c8..e7ff3ade 100644 --- a/core/models.py +++ b/core/models.py @@ -913,6 +913,16 @@ class OrderProduct(ExportModelOperationsMixin("order_product"), NiceModel): return self.download.url return "" + def do_feedback(self, rating: int = 10, comment: str = "", action: str = "add"): + if action not in ["add", "remove"]: + raise ValueError(_(f"wrong action specified for feedback: {action}")) + if action == "remove" and self.feedback: + self.feedback.delete() + return None + if action == "add" and not self.feedback: + return Feedback.objects.create(rating=rating, comment=comment, order_product=self) + return None + class ProductTag(ExportModelOperationsMixin("product_tag"), NiceModel): is_publicly_visible = True diff --git a/core/serializers/utility.py b/core/serializers/utility.py index a07df7a9..87784ba8 100644 --- a/core/serializers/utility.py +++ b/core/serializers/utility.py @@ -1,3 +1,4 @@ +from rest_framework.exceptions import ValidationError from rest_framework.fields import CharField, DictField, FloatField, IntegerField from rest_framework.serializers import ModelSerializer, Serializer @@ -62,3 +63,13 @@ class AddressCreateSerializer(ModelSerializer): if self.context["request"].user.is_authenticated: user = self.context["request"].user return Address.objects.create(raw_data=raw, user=user, **validated_data) + + +class DoFeedbackSerializer(Serializer): + comment = CharField(required=True) + rating = IntegerField(required=False, min_value=1, max_value=10, default=10) + action = CharField(choices=["add", "remove"], required=True, default="add") + + def validate(self, data): + if data["action"] == "add" and not all([data["comment"], data["rating"]]): + raise ValidationError(_("you must provide a comment, rating, and order product uuid to add feedback.")) diff --git a/core/viewsets.py b/core/viewsets.py index e53c3a61..aa316409 100644 --- a/core/viewsets.py +++ b/core/viewsets.py @@ -26,6 +26,7 @@ from core.docs.drf.viewsets import ( ATTRIBUTE_VALUE_SCHEMA, CATEGORY_SCHEMA, FEEDBACK_SCHEMA, + ORDER_PRODUCT_SCHEMA, ORDER_SCHEMA, PRODUCT_SCHEMA, WISHLIST_SCHEMA, @@ -99,6 +100,7 @@ from core.serializers.utility import ( AddressAutocompleteInputSerializer, AddressCreateSerializer, AddressSuggestionSerializer, + DoFeedbackSerializer, ) from core.utils import format_attributes from core.utils.messages import permission_denied_message @@ -213,11 +215,25 @@ class ProductViewSet(EvibesViewSet): obj = queryset.filter(slug=lookup_value).first() if not obj: - raise Http404(f"No Product found matching uuid or slug '{lookup_value}'") + name = "Product" + raise Http404(f"{name} does not exist: {uuid}") self.check_object_permissions(self.request, obj) return obj + @action(detail=True, methods=["get"], url_path="feedbacks") + def feedbacks(self, request, **kwargs): + lookup_val = kwargs.get(self.lookup_field) + try: + product = Product.objects.get(uuid=lookup_val) + feedbacks = Feedback.objects.filter(order_product__product=product) if request.user.has_perm( + "core.view_feedback") else Feedback.objects.filter(order_product__product=product, is_active=True) + return Response( + data=FeedbackDetailSerializer(feedbacks, many=True).data) + except Product.DoesNotExist: + name = "Product" + return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"{name} does not exist: {uuid}")}) + class VendorViewSet(EvibesViewSet): queryset = Vendor.objects.all() @@ -322,7 +338,8 @@ class OrderViewSet(EvibesViewSet): case _: raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}")) except Order.DoesNotExist: - return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"order {lookup_val} not found")}) + name = "Order" + return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"{name} does not exist: {uuid}")}) @action(detail=False, methods=["post"], url_path="buy_unregistered") @method_decorator(ratelimit(key="ip", rate="5/h" if not DEBUG else "888/h")) @@ -414,6 +431,7 @@ class OrderViewSet(EvibesViewSet): return Response(status=status.HTTP_404_NOT_FOUND) +@extend_schema_view(**ORDER_PRODUCT_SCHEMA) class OrderProductViewSet(EvibesViewSet): queryset = OrderProduct.objects.all() filter_backends = [DjangoFilterBackend] @@ -421,6 +439,7 @@ class OrderProductViewSet(EvibesViewSet): serializer_class = AttributeGroupDetailSerializer action_serializer_classes = { "list": OrderProductSimpleSerializer, + "do_feedback": DoFeedbackSerializer, } def get_queryset(self): @@ -432,6 +451,29 @@ class OrderProductViewSet(EvibesViewSet): return qs.filter(user=user) + @action(detail=True, methods=["post"], url_path="do_feedback") + def do_feedback(self, request, **kwargs): + serializer = self.get_serializer(request.data) + serializer.is_valid(raise_exception=True) + try: + order_product = OrderProduct.objects.get(uuid=kwargs.get("pk")) + if not (request.user.has_perm("core.change_orderproduct") or request.user == order_product.order.user): + raise PermissionDenied(permission_denied_message) + feedback = order_product.do_feedback( + rating=serializer.validated_data.get("rating"), + comment=serializer.validated_data.get("comment"), + action=serializer.validated_data.get("action"), + ) + match serializer.validated_data.get("action"): + case "add": + return Response(data=FeedbackDetailSerializer(feedback).data, status=status.HTTP_201_CREATED) + case "remove": + return Response(status=status.HTTP_204_NO_CONTENT) + case _: + return Response(status=status.HTTP_400_BAD_REQUEST) + except OrderProduct.DoesNotExist: + return Response(status=status.HTTP_404_NOT_FOUND) + class ProductImageViewSet(EvibesViewSet): queryset = ProductImage.objects.all()