Features: Added feedbacks support for OrderProducts

This commit is contained in:
Egor Pavlovich Gorbunov 2025-06-08 14:18:26 +03:00
parent b876983ef3
commit 4148d9e02c
4 changed files with 199 additions and 63 deletions

View file

@ -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 orderproduct relations (simple view)"),
responses={
status.HTTP_200_OK: OrderProductSimpleSerializer(many=True),
**BASE_ERRORS,
},
),
"retrieve": extend_schema(
summary=_("retrieve a single orderproduct relation (detailed view)"),
responses={
status.HTTP_200_OK: OrderProductDetailSerializer(),
**BASE_ERRORS,
},
),
"create": extend_schema(
summary=_("create a new orderproduct relation"),
responses={
status.HTTP_201_CREATED: OrderProductDetailSerializer(),
**BASE_ERRORS,
},
),
"update": extend_schema(
summary=_("replace an existing orderproduct relation"),
responses={
status.HTTP_200_OK: OrderProductDetailSerializer(),
**BASE_ERRORS,
},
),
"partial_update": extend_schema(
summary=_("partially update an existing orderproduct relation"),
responses={
status.HTTP_200_OK: OrderProductDetailSerializer(),
**BASE_ERRORS,
},
),
"destroy": extend_schema(
summary=_("delete an orderproduct relation"),
responses={
status.HTTP_204_NO_CONTENT: {},
**BASE_ERRORS,
},
),
"do_feedback": extend_schema(
summary=_("add or remove feedback on an orderproduct 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,
},
),
}

View file

@ -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

View file

@ -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."))

View file

@ -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()