schon/core/viewsets.py

601 lines
23 KiB
Python

from uuid import UUID
from django.http import Http404
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,
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,
FeedbackSimpleSerializer,
OrderDetailSerializer,
OrderProductSimpleSerializer,
OrderSimpleSerializer,
ProductDetailSerializer,
ProductImageDetailSerializer,
ProductImageSimpleSerializer,
ProductSimpleSerializer,
ProductTagDetailSerializer,
ProductTagSimpleSerializer,
PromoCodeDetailSerializer,
PromoCodeSimpleSerializer,
PromotionDetailSerializer,
PromotionSimpleSerializer,
RemoveOrderProductSerializer,
RemoveWishlistProductSerializer,
StockDetailSerializer,
StockSimpleSerializer,
VendorSimpleSerializer,
WishlistDetailSerializer,
WishlistSimpleSerializer,
)
from core.serializers.utility import (
AddressAutocompleteInputSerializer,
AddressCreateSerializer,
AddressSuggestionSerializer,
)
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
class EvibesViewSet(ModelViewSet):
action_serializer_classes = {}
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_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:
raise Http404(f"No Product found matching uuid or slug '{lookup_value}'")
self.check_object_permissions(self.request, obj)
return obj
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,
}
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):
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,
}
def get_queryset(self):
qs = super().get_queryset()
user = self.request.user
if user.has_perm("core.view_order"):
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)
order = Order.objects.get(user=request.user, status="PENDING")
if not request.user == order.user:
raise PermissionDenied(permission_denied_message)
return Response(
status=status.HTTP_200_OK,
data=OrderDetailSerializer(order).data,
)
@action(detail=True, methods=["post"], url_path="buy")
def buy(self, request, *_args, **kwargs):
serializer = BuyOrderSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
order_uuid = kwargs.get("pk")
try:
order = Order.objects.get(user=request.user, uuid=order_uuid)
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:
return Response(status=status.HTTP_404_NOT_FOUND, data={"detail": _(f"order {order_uuid} not found")})
@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 = [product.get("product_uuid") for product in serializer.validated_data.get("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)
try:
order = Order.objects.get(uuid=kwargs.get("pk"))
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)
try:
order = Order.objects.get(uuid=kwargs.get("pk"))
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, *_args, **kwargs):
serializer = BulkAddOrderProductsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
order = Order.objects.get(uuid=kwargs.get("pk"))
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, *_args, **kwargs):
serializer = BulkRemoveOrderProductsSerializer(data=request.data)
serializer.is_valid(raise_exception=True)
try:
order = Order.objects.get(uuid=kwargs.get("pk"))
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)
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,
}
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)
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,
}