import logging import uuid from uuid import UUID from django.db.models import Q from django.http import Http404 from django.shortcuts import get_object_or_404 from django.utils.decorators import method_decorator from django.utils.translation import gettext_lazy as _ from django_filters.rest_framework import DjangoFilterBackend from django_ratelimit.decorators import ratelimit from djangorestframework_camel_case.render import CamelCaseJSONRenderer from drf_spectacular.utils import extend_schema_view from rest_framework import status from rest_framework.decorators import action from rest_framework.exceptions import PermissionDenied from rest_framework.renderers import MultiPartRenderer from rest_framework.response import Response from rest_framework.viewsets import ModelViewSet from rest_framework_xml.renderers import XMLRenderer from rest_framework_yaml.renderers import YAMLRenderer from core.docs.drf.viewsets import ( ADDRESS_SCHEMA, ATTRIBUTE_GROUP_SCHEMA, ATTRIBUTE_SCHEMA, ATTRIBUTE_VALUE_SCHEMA, CATEGORY_SCHEMA, FEEDBACK_SCHEMA, ORDER_PRODUCT_SCHEMA, ORDER_SCHEMA, PRODUCT_SCHEMA, WISHLIST_SCHEMA, ) from core.filters import AddressFilter, BrandFilter, CategoryFilter, FeedbackFilter, OrderFilter, ProductFilter from core.models import ( Address, Attribute, AttributeGroup, AttributeValue, Brand, Category, Feedback, Order, OrderProduct, Product, ProductImage, ProductTag, PromoCode, Promotion, Stock, Vendor, Wishlist, ) from core.permissions import EvibesPermission from core.serializers import ( AddOrderProductSerializer, AddressSerializer, AddWishlistProductSerializer, AttributeDetailSerializer, AttributeGroupDetailSerializer, AttributeGroupSimpleSerializer, AttributeSimpleSerializer, AttributeValueDetailSerializer, AttributeValueSimpleSerializer, BrandDetailSerializer, BrandSimpleSerializer, BulkAddOrderProductsSerializer, BulkAddWishlistProductSerializer, BulkRemoveOrderProductsSerializer, BulkRemoveWishlistProductSerializer, BuyOrderSerializer, BuyUnregisteredOrderSerializer, CategoryDetailSerializer, CategorySimpleSerializer, FeedbackDetailSerializer, FeedbackSimpleSerializer, OrderDetailSerializer, OrderProductSimpleSerializer, OrderSimpleSerializer, ProductDetailSerializer, ProductImageDetailSerializer, ProductImageSimpleSerializer, ProductSimpleSerializer, ProductTagDetailSerializer, ProductTagSimpleSerializer, PromoCodeDetailSerializer, PromoCodeSimpleSerializer, PromotionDetailSerializer, PromotionSimpleSerializer, RemoveOrderProductSerializer, RemoveWishlistProductSerializer, StockDetailSerializer, StockSimpleSerializer, VendorDetailSerializer, VendorSimpleSerializer, WishlistDetailSerializer, WishlistSimpleSerializer, ) from core.serializers.utility import ( AddressAutocompleteInputSerializer, AddressCreateSerializer, AddressSuggestionSerializer, DoFeedbackSerializer, ) from core.utils import format_attributes from core.utils.messages import permission_denied_message from core.utils.nominatim import fetch_address_suggestions from evibes.settings import DEBUG from payments.serializers import TransactionProcessSerializer logger = logging.getLogger("django.request") class EvibesViewSet(ModelViewSet): action_serializer_classes = {} additional = {} permission_classes = [EvibesPermission] renderer_classes = [CamelCaseJSONRenderer, MultiPartRenderer, XMLRenderer, YAMLRenderer] def get_serializer_class(self): return self.action_serializer_classes.get(self.action, super().get_serializer_class()) @extend_schema_view(**ATTRIBUTE_GROUP_SCHEMA) class AttributeGroupViewSet(EvibesViewSet): queryset = AttributeGroup.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["is_active"] serializer_class = AttributeGroupDetailSerializer action_serializer_classes = { "list": AttributeGroupSimpleSerializer, } @extend_schema_view(**ATTRIBUTE_SCHEMA) class AttributeViewSet(EvibesViewSet): queryset = Attribute.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["group", "value_type", "is_active"] serializer_class = AttributeDetailSerializer action_serializer_classes = { "list": AttributeSimpleSerializer, } @extend_schema_view(**ATTRIBUTE_VALUE_SCHEMA) class AttributeValueViewSet(EvibesViewSet): queryset = AttributeValue.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["attribute", "is_active"] serializer_class = AttributeValueDetailSerializer action_serializer_classes = { "list": AttributeValueSimpleSerializer, } @extend_schema_view(**CATEGORY_SCHEMA) class CategoryViewSet(EvibesViewSet): queryset = Category.objects.all().prefetch_related("parent", "children", "attributes", "tags") filter_backends = [DjangoFilterBackend] filterset_class = CategoryFilter serializer_class = CategoryDetailSerializer action_serializer_classes = { "list": CategorySimpleSerializer, } def get_queryset(self): qs = super().get_queryset() if self.request.user.has_perm("core.view_category"): return qs return qs.filter(is_active=True) class BrandViewSet(EvibesViewSet): queryset = Brand.objects.all() filter_backends = [DjangoFilterBackend] filterset_class = BrandFilter serializer_class = BrandDetailSerializer action_serializer_classes = { "list": BrandSimpleSerializer, } @extend_schema_view(**PRODUCT_SCHEMA) class ProductViewSet(EvibesViewSet): queryset = Product.objects.prefetch_related("tags", "attributes", "stocks", "images").all() filter_backends = [DjangoFilterBackend] filterset_class = ProductFilter serializer_class = ProductDetailSerializer action_serializer_classes = { "list": ProductSimpleSerializer, } lookup_field = "lookup_value" lookup_url_kwarg = "lookup_value" def get_queryset(self): qs = super().get_queryset() if self.request.user.has_perm("core.view_product"): return qs return qs.filter(is_active=True) def get_object(self): queryset = self.filter_queryset(self.get_queryset()) lookup_value = self.kwargs[self.lookup_url_kwarg] obj = None try: uuid_obj = UUID(lookup_value) obj = queryset.filter(uuid=uuid_obj).first() except (ValueError, TypeError): pass if not obj: obj = queryset.filter(slug=lookup_value).first() if not obj: name = "Product" raise Http404(f"{name} does not exist: {lookup_value}") 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() filter_backends = [DjangoFilterBackend] filterset_fields = ["name", "markup_percent", "is_active"] serializer_class = VendorDetailSerializer action_serializer_classes = { "list": VendorSimpleSerializer, } @extend_schema_view(**FEEDBACK_SCHEMA) class FeedbackViewSet(EvibesViewSet): queryset = Feedback.objects.all() filter_backends = [DjangoFilterBackend] filterset_class = FeedbackFilter serializer_class = FeedbackDetailSerializer action_serializer_classes = { "list": FeedbackSimpleSerializer, } def get_queryset(self): qs = super().get_queryset() if self.request.user.has_perm("core.view_feedback"): return qs return qs.filter(is_active=True) @extend_schema_view(**ORDER_SCHEMA) class OrderViewSet(EvibesViewSet): lookup_field = "lookup_value" lookup_url_kwarg = "lookup_value" queryset = Order.objects.prefetch_related("order_products").all() filter_backends = [DjangoFilterBackend] filterset_class = OrderFilter serializer_class = OrderDetailSerializer action_serializer_classes = { "list": OrderSimpleSerializer, "buy": OrderDetailSerializer, "add_order_product": AddOrderProductSerializer, "remove_order_product": RemoveOrderProductSerializer, } additional = {"retrieve": "ALLOW"} def get_serializer_class(self): return self.action_serializer_classes.get(self.action, super().get_serializer_class()) def get_queryset(self): qs = super().get_queryset() user = self.request.user if not user.is_authenticated: return qs.filter(user__isnull=True) if user.has_perm("core.view_order"): return qs return qs.filter(user=user) def get_object(self): lookup_val = self.kwargs[self.lookup_field] qs = self.get_queryset() try: uuid.UUID(lookup_val) uuid_q = Q(uuid=lookup_val) except ValueError: uuid_q = Q() obj = get_object_or_404(qs, uuid_q | Q(human_readable_id=lookup_val)) self.check_object_permissions(self.request, obj) return obj @action(detail=False, methods=["get"], url_path="current") def current(self, request): if not request.user.is_authenticated: raise PermissionDenied(permission_denied_message) order = Order.objects.get(user=request.user, status="PENDING") return Response( status=status.HTTP_200_OK, data=OrderDetailSerializer(order).data, ) @action(detail=True, methods=["post"], url_path="buy") def buy(self, request, **kwargs): serializer = BuyOrderSerializer(data=request.data) serializer.is_valid(raise_exception=True) lookup_val = kwargs.get(self.lookup_field) try: order = Order.objects.get(user=request.user, uuid=lookup_val) instance = order.buy( force_balance=serializer.validated_data.get("force_balance"), force_payment=serializer.validated_data.get("force_payment"), promocode_uuid=serializer.validated_data.get("promocode_uuid"), shipping_address=serializer.validated_data.get("shipping_address_uuid"), billing_address=serializer.validated_data.get("billing_address_uuid"), ) match str(type(instance)): case "": return Response(status=status.HTTP_202_ACCEPTED, data=TransactionProcessSerializer(instance).data) case "": return Response(status=status.HTTP_200_OK, data=OrderDetailSerializer(instance).data) case _: raise TypeError(_(f"wrong type came from order.buy() method: {type(instance)!s}")) except Order.DoesNotExist: 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")) def buy_unregistered(self, request): serializer = BuyUnregisteredOrderSerializer(data=request.data) serializer.is_valid(raise_exception=True) order = Order.objects.create(status="MOMENTAL") products = [p["product_uuid"] for p in serializer.validated_data["products"]] transaction = order.buy_without_registration( products=products, promocode_uuid=serializer.validated_data.get("promocode_uuid"), customer_name=serializer.validated_data.get("customer_name"), customer_email=serializer.validated_data.get("customer_email"), customer_phone_number=serializer.validated_data.get("customer_phone_number"), billing_customer_address=serializer.validated_data.get("billing_customer_address_uuid"), shipping_customer_address=serializer.validated_data.get("shipping_customer_address_uuid"), payment_method=serializer.validated_data.get("payment_method"), ) return Response(status=status.HTTP_202_ACCEPTED, data=TransactionProcessSerializer(transaction).data) @action(detail=True, methods=["post"], url_path="add_order_product") def add_order_product(self, request, **kwargs): serializer = AddOrderProductSerializer(data=request.data) serializer.is_valid(raise_exception=True) lookup_val = kwargs.get(self.lookup_field) try: order = Order.objects.get(uuid=lookup_val) if not (request.user.has_perm("core.add_orderproduct") or request.user == order.user): raise PermissionDenied(permission_denied_message) order = order.add_product( product_uuid=serializer.validated_data.get("product_uuid"), attributes=format_attributes(serializer.validated_data.get("attributes")), ) return Response(status=status.HTTP_200_OK, data=OrderDetailSerializer(order).data) except Order.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @action(detail=True, methods=["post"], url_path="remove_order_product") def remove_order_product(self, request, **kwargs): serializer = RemoveOrderProductSerializer(data=request.data) serializer.is_valid(raise_exception=True) lookup_val = kwargs.get(self.lookup_field) try: order = Order.objects.get(uuid=lookup_val) if not (request.user.has_perm("core.delete_orderproduct") or request.user == order.user): raise PermissionDenied(permission_denied_message) order = order.remove_product( product_uuid=serializer.validated_data.get("product_uuid"), attributes=format_attributes(serializer.validated_data.get("attributes")), ) return Response(status=status.HTTP_200_OK, data=OrderDetailSerializer(order).data) except Order.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @action(detail=True, methods=["post"], url_path="bulk_add_order_products") def bulk_add_order_products(self, request, **kwargs): serializer = BulkAddOrderProductsSerializer(data=request.data) serializer.is_valid(raise_exception=True) lookup_val = kwargs.get(self.lookup_field) try: order = Order.objects.get(uuid=lookup_val) if not (request.user.has_perm("core.add_orderproduct") or request.user == order.user): raise PermissionDenied(permission_denied_message) order = order.bulk_add_products( products=serializer.validated_data.get("products"), ) return Response(status=status.HTTP_200_OK, data=OrderDetailSerializer(order).data) except Order.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @action(detail=True, methods=["post"], url_path="bulk_remove_order_products") def bulk_remove_order_products(self, request, **kwargs): serializer = BulkRemoveOrderProductsSerializer(data=request.data) serializer.is_valid(raise_exception=True) lookup_val = kwargs.get(self.lookup_field) try: order = Order.objects.get(uuid=lookup_val) if not (request.user.has_perm("core.delete_orderproduct") or request.user == order.user): raise PermissionDenied(permission_denied_message) order = order.bulk_remove_products( products=serializer.validated_data.get("products"), ) return Response(status=status.HTTP_200_OK, data=OrderDetailSerializer(order).data) except Order.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @extend_schema_view(**ORDER_PRODUCT_SCHEMA) class OrderProductViewSet(EvibesViewSet): queryset = OrderProduct.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["order", "product", "status", "is_active"] serializer_class = AttributeGroupDetailSerializer action_serializer_classes = { "list": OrderProductSimpleSerializer, "do_feedback": DoFeedbackSerializer, } def get_queryset(self): qs = super().get_queryset() user = self.request.user if user.has_perm("core.view_orderproduct"): return qs 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() filter_backends = [DjangoFilterBackend] filterset_fields = ["product", "priority", "is_active"] serializer_class = ProductImageDetailSerializer action_serializer_classes = { "list": ProductImageSimpleSerializer, } class PromoCodeViewSet(EvibesViewSet): queryset = PromoCode.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["code", "discount_amount", "discount_percent", "start_time", "end_time", "used_on", "is_active"] serializer_class = PromoCodeDetailSerializer action_serializer_classes = { "list": PromoCodeSimpleSerializer, } def get_queryset(self): qs = super().get_queryset() user = self.request.user if user.has_perm("core.view_promocode"): return qs return qs.filter(user=user) class PromotionViewSet(EvibesViewSet): queryset = Promotion.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["name", "discount_percent", "is_active"] serializer_class = PromotionDetailSerializer action_serializer_classes = { "list": PromotionSimpleSerializer, } class StockViewSet(EvibesViewSet): queryset = Stock.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["vendor", "product", "sku", "is_active"] serializer_class = StockDetailSerializer action_serializer_classes = { "list": StockSimpleSerializer, } @extend_schema_view(**WISHLIST_SCHEMA) class WishlistViewSet(EvibesViewSet): queryset = Wishlist.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["user", "is_active"] serializer_class = WishlistDetailSerializer action_serializer_classes = { "list": WishlistSimpleSerializer, } def get_queryset(self): qs = super().get_queryset() user = self.request.user if user.has_perm("core.view_wishlist"): return qs return qs.filter(user=user) @action(detail=False, methods=["get"], url_path="current") def current(self, request): if not request.user.is_authenticated: raise PermissionDenied(permission_denied_message) wishlist = Wishlist.objects.get(user=request.user) if not request.user == wishlist.user: raise PermissionDenied(permission_denied_message) return Response( status=status.HTTP_200_OK, data=WishlistDetailSerializer(wishlist).data, ) @action(detail=True, methods=["post"], url_path="add_wishlist_product") def add_wishlist_product(self, request, **kwargs): serializer = AddWishlistProductSerializer(data=request.data) serializer.is_valid(raise_exception=True) try: wishlist = Wishlist.objects.get(uuid=kwargs.get("pk")) if not (request.user.has_perm("core.change_wishlist") or request.user == wishlist.user): raise PermissionDenied(permission_denied_message) wishlist = wishlist.add_product( product_uuid=serializer.validated_data.get("product_uuid"), ) return Response(status=status.HTTP_200_OK, data=WishlistDetailSerializer(wishlist).data) except Wishlist.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @action(detail=True, methods=["post"], url_path="remove_wishlist_product") def remove_wishlist_product(self, request, **kwargs): serializer = RemoveWishlistProductSerializer(data=request.data) serializer.is_valid(raise_exception=True) try: wishlist = Wishlist.objects.get(uuid=kwargs.get("pk")) if not (request.user.has_perm("core.change_wishlist") or request.user == wishlist.user): raise PermissionDenied(permission_denied_message) wishlist = wishlist.remove_product( product_uuid=serializer.validated_data.get("product_uuid"), ) return Response(status=status.HTTP_200_OK, data=WishlistDetailSerializer(wishlist).data) except Wishlist.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @action(detail=True, methods=["post"], url_path="bulk_add_wishlist_product") def bulk_add_wishlist_products(self, request, **kwargs): serializer = BulkAddWishlistProductSerializer(data=request.data) serializer.is_valid(raise_exception=True) try: wishlist = Wishlist.objects.get(uuid=kwargs.get("pk")) if not (request.user.has_perm("core.change_wishlist") or request.user == wishlist.user): raise PermissionDenied(permission_denied_message) wishlist = wishlist.bulk_add_products( product_uuids=serializer.validated_data.get("product_uuids"), ) return Response(status=status.HTTP_200_OK, data=WishlistDetailSerializer(wishlist).data) except Order.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @action(detail=True, methods=["post"], url_path="bulk_remove_wishlist_product") def bulk_remove_wishlist_products(self, request, **kwargs): serializer = BulkRemoveWishlistProductSerializer(data=request.data) serializer.is_valid(raise_exception=True) try: wishlist = Wishlist.objects.get(uuid=kwargs.get("pk")) if not (request.user.has_perm("core.change_wishlist") or request.user == wishlist.user): raise PermissionDenied(permission_denied_message) wishlist = wishlist.bulk_remove_products( product_uuids=serializer.validated_data.get("product_uuids"), ) return Response(status=status.HTTP_200_OK, data=WishlistDetailSerializer(wishlist).data) except Order.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) @extend_schema_view(**ADDRESS_SCHEMA) class AddressViewSet(EvibesViewSet): pagination_class = None filter_backends = [DjangoFilterBackend] filterset_class = AddressFilter queryset = Address.objects.all() serializer_class = AddressSerializer additional = {"create": "ALLOW"} def get_serializer_class(self): if self.action == "create": return AddressCreateSerializer if self.action == "autocomplete": return AddressAutocompleteInputSerializer return AddressSerializer def get_queryset(self): if self.request.user.has_perm("core.view_address"): return super().get_queryset() if self.request.user.is_authenticated: return super().get_queryset().filter(user=self.request.user) return Address.objects.none() def create(self, request, **kwargs): create_serializer = AddressCreateSerializer(data=request.data, context={"request": request}) create_serializer.is_valid(raise_exception=True) address_obj = create_serializer.create(create_serializer.validated_data) output_serializer = AddressSerializer(address_obj, context={"request": request}) return Response( status=status.HTTP_201_CREATED, data=output_serializer.data, ) @action(detail=False, methods=["get"], url_path="autocomplete") def autocomplete(self, request): serializer = AddressAutocompleteInputSerializer(data=request.query_params) serializer.is_valid(raise_exception=True) q = serializer.validated_data["q"] limit = serializer.validated_data["limit"] try: suggestions = fetch_address_suggestions(query=q, limit=limit) serializer = AddressSuggestionSerializer(suggestions, many=True) return Response( serializer.data, status=status.HTTP_200_OK, ) except Exception as e: return Response( {"detail": _(f"Geocoding error: {e}")}, status=status.HTTP_502_BAD_GATEWAY, ) class ProductTagViewSet(EvibesViewSet): queryset = ProductTag.objects.all() filter_backends = [DjangoFilterBackend] filterset_fields = ["tag_name", "is_active"] serializer_class = ProductTagDetailSerializer action_serializer_classes = { "list": ProductTagSimpleSerializer, }