schon/core/viewsets.py

686 lines
26 KiB
Python

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 BrandFilter, CategoryFilter, 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")
filter_backends = [DjangoFilterBackend]
filterset_class = CategoryFilter
serializer_class = CategoryDetailSerializer
action_serializer_classes = {
"list": CategorySimpleSerializer,
}
def get_queryset(self):
qs = super().get_queryset()
if self.action == "list":
qs = qs.filter(parent=None)
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"
lookup_url_kwarg = "lookup"
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: {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()
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_fields = ["order_product", "rating", "is_active"]
serializer_class = FeedbackDetailSerializer
action_serializer_classes = {
"list": FeedbackSimpleSerializer,
}
@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 "<class 'payments.models.Transaction'>":
return Response(status=status.HTTP_202_ACCEPTED, data=TransactionProcessSerializer(instance).data)
case "<class 'core.models.Order'>":
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
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 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,
}