from django.core.paginator import Paginator from rest_framework.views import APIView from .models import * from .serializers import * from rest_framework import status from rest_framework.response import Response from django.db.models import Q from django.shortcuts import get_object_or_404 from rest_framework.permissions import IsAuthenticatedOrReadOnly from utils.pagination import StructurePagination from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiTypes from rest_framework.permissions import AllowAny from order.serializers import OrderItemSerailzier from order.models import OrderModel from django.db.models import Min, Max from home.models import ShowCaseSlider from home.serializers import ShowCaseSliderSerialzier # class APIView(APIView): # def __init__(self, *args, **kwargs): # super().__init__(*args, **kwargs) # print('here') # print(self.permission_classes) # if AllowAny in self.permission_classes or not self.permission_classes: # print('asdf') # self.authentication_classes = [] class AllCategories(APIView): serializer_class = MainCategorySerializer authentication_classes = [] @extend_schema( # parameters=[ # OpenApiParameter( # name="search", # description="Search by category name or description.", # required=False, # type=OpenApiTypes.STR, # ) # ], responses={ 200: MainCategorySerializer(many=True), 404: OpenApiTypes.OBJECT, }, ) def get(self, request): # search_query = request.query_params.get('search', None) # if search_query: # categories = MainCategoryModel.objects.filter(Q(name__icontains=search_query) | Q(slug__icontains=search_query)) # else: categories = MainCategoryModel.objects.all() categories_ser = self.serializer_class(instance=categories, many=True, context={'request': request}) return Response(categories_ser.data, status=status.HTTP_200_OK) class ProductView(APIView): serializer_class = DynamicProductSerializer permission_classes = [AllowAny] # authentication_classes = [] def get(self, request, slug): product = get_object_or_404(ProductModel, slug=slug) if request.user.is_authenticated: cart_obj, _ = OrderModel.objects.get_or_create(user=request.user, status='CART') cart_items = cart_obj.items.all() cart_items_ser = OrderItemSerailzier(cart_items, many=True, context={'request': request}) product_ser_context = {'request': request, 'view_type': 'instance', 'cart_items': cart_items_ser.data} else: product_ser_context = {'request': request, 'view_type': 'instance'} product_ser = self.serializer_class(instance=product, many=False, context=product_ser_context) return Response(product_ser.data, status=status.HTTP_200_OK) class AllProductsView(APIView): serializer_class = DynamicProductSerializer pagination_class = StructurePagination authentication_classes = [] @extend_schema( parameters=[ OpenApiParameter( name="search", description="Search by product name or description.", required=False, type=OpenApiTypes.STR, ), # OpenApiParameter( # name="category", # type={'type': 'array', 'items': {'type': 'number'}}, # location=OpenApiParameter.QUERY, # required=False, # style='form', # explode=False, # ), OpenApiParameter( name="category", type=OpenApiTypes.STR, description="slug category (send it with category type)", required=False, ), OpenApiParameter( name="category_type", type=OpenApiTypes.STR, required=False, enum=['sub', 'main'], ), OpenApiParameter( name="price_gte", description="Filter products with price greater than or equal to this value.", required=False, type=OpenApiTypes.FLOAT, ), OpenApiParameter( name="price_lte", description="Filter products with price less than or equal to this value.", required=False, type=OpenApiTypes.FLOAT, ), OpenApiParameter( name="sort", description=( "Sort results by one of the following fields:\n" "`name`, `-name`, `price`, `-price`, `created_at`, `-created_at`." "\nPrefix with `-` for descending order." "remove the price form sorting templory " ), required=False, type=OpenApiTypes.STR, ), OpenApiParameter( name="limit", description="Number of results to return per page (pagination).", required=False, type=OpenApiTypes.INT, ), OpenApiParameter( name="offset", description="The starting position of the results (pagination).", required=False, type=OpenApiTypes.INT, ), OpenApiParameter( name="in_stock", description="Filter products that are in stock (positive stock).", required=False, type=OpenApiTypes.BOOL, ), OpenApiParameter( name="has_discount", description="Filter products that have a discount.", required=False, type=OpenApiTypes.BOOL, ) ], description=( "Retrieve products with optional filters and sorting. " "Provide a list of category IDs to filter products by those categories and their subcategories." ), responses={ 200: DynamicProductSerializer(many=True, context={'view_type': 'list'}), 404: OpenApiTypes.OBJECT, }, ) def get(self, request): try: category_slug = request.query_params.get('category') category_type = request.query_params.get('category_type') VALID_CATEGORY_TYPES = {'sub', 'main'} products = ProductModel.objects.all() if category_slug: if category_type not in VALID_CATEGORY_TYPES: return Response( {"detail": "category_type must be one of ['sub', 'main']"}, status=status.HTTP_400_BAD_REQUEST ) if category_type == 'sub': sub_category = get_object_or_404(SubCategoryModel, slug=category_slug) products = ProductModel.objects.filter(category=sub_category) else: # category_type == 'main' main_category = get_object_or_404(MainCategoryModel, slug=category_slug) sub_categories = main_category.subcategorys.all() products = ProductModel.objects.filter(category__in=sub_categories) in_stock = request.query_params.get('in_stock') if in_stock is not None: if in_stock.lower() == 'true': products = products.filter(variants__in_stock__gt=0) elif in_stock.lower() != 'false': return Response({'detail': 'in_stock must be "true" or "false".'}, status=status.HTTP_400_BAD_REQUEST) # Filter by discount has_discount = request.query_params.get('has_discount') if has_discount is not None: if has_discount.lower() == 'true': products = products.filter(variants__discount__gt=0) elif has_discount.lower() != 'false': return Response({'detail': 'has_discount must be "true" or "false".'}, status=status.HTTP_400_BAD_REQUEST) # Search filter search_query = request.query_params.get('search') if search_query: products = products.filter(Q(name__icontains=search_query) | Q(description__icontains=search_query)) # Price filters price_gte = request.query_params.get('price_gte') price_lte = request.query_params.get('price_lte') products = products.annotate(min_price=Min('variants__price'), max_price=Max('variants__price')) if price_gte: try: price_gte = float(price_gte) products = products.filter(max_price__gte=price_gte) except ValueError: return Response({'detail': 'price_gte must be a number.'}, status=status.HTTP_400_BAD_REQUEST) if price_lte: try: price_lte = float(price_lte) products = products.filter(min_price__lte=price_lte) except ValueError: return Response({'detail': 'price_lte must be a number.'}, status=status.HTTP_400_BAD_REQUEST) # Sorting sort_by = request.query_params.get('sort') if sort_by in ['name', '-name', 'created_at', '-created_at']: products = products.order_by(sort_by) else: products = products.order_by('name') # Pagination paginator = self.pagination_class() paginated_products = paginator.paginate_queryset(products, request) serializer = self.serializer_class( paginated_products, many=True, context={'request': request, 'view_type': 'list'} ) return paginator.get_paginated_response(serializer.data) except MainCategoryModel.DoesNotExist: return Response({"detail": "Main Category not found."}, status=status.HTTP_404_NOT_FOUND) except SubCategoryModel.DoesNotExist: return Response({"detail": "Sub Category not found."}, status=status.HTTP_404_NOT_FOUND) class ShowCaseCategoryListView(APIView): serializer_class = ShowCaseSliderSerialzier permission_classes = [AllowAny] def get(self, request): categoryes = ShowCaseSlider.objects.all() categoryes_ser = self.serializer_class(instance=categoryes, many=True, context={'request': request}) return Response(categoryes_ser.data, status=status.HTTP_200_OK) class ShowCaseProductsView(APIView): serializer_class = DynamicProductSerializer pagination_class = StructurePagination authentication_classes = [] @extend_schema( parameters=[ OpenApiParameter( name="search", description="Search by product name or description.", required=False, type=OpenApiTypes.STR, ), OpenApiParameter( name="slider_category", type=OpenApiTypes.INT, required=False, ), OpenApiParameter( name="price_gte", description="Filter products with price greater than or equal to this value.", required=False, type=OpenApiTypes.FLOAT, ), OpenApiParameter( name="price_lte", description="Filter products with price less than or equal to this value.", required=False, type=OpenApiTypes.FLOAT, ), OpenApiParameter( name="sort", description=( "Sort results by one of the following fields:\n" "`name`, `-name`, `price`, `-price`, `created_at`, `-created_at`." "\nPrefix with `-` for descending order." "remove the price form sorting templory " ), required=False, type=OpenApiTypes.STR, ), OpenApiParameter( name="limit", description="Number of results to return per page (pagination).", required=False, type=OpenApiTypes.INT, ), OpenApiParameter( name="offset", description="The starting position of the results (pagination).", required=False, type=OpenApiTypes.INT, ), OpenApiParameter( name="in_stock", description="Filter products that are in stock (positive stock).", required=False, type=OpenApiTypes.BOOL, ), OpenApiParameter( name="has_discount", description="Filter products that have a discount.", required=False, type=OpenApiTypes.BOOL, ) ], description=( "Retrieve products with optional filters and sorting. " "Provide a list of category IDs to filter products by those categories and their subcategories." ), responses={ 200: DynamicProductSerializer(many=True, context={'view_type': 'list'}), 404: OpenApiTypes.OBJECT, }, ) def get(self, request): try: category_id = request.query_params.get('slider_category', None) if category_id: try: category_id = int(category_id) except ValueError: return Response({'detail': 'value error category id should be a number'}, status=status.HTTP_400_BAD_REQUEST) slider_category = get_object_or_404(ShowCaseSlider, pk=category_id) products = ProductModel.objects.filter(variants__slider_category=slider_category).distinct() else: products = ProductModel.objects.filter(variants__slider_category__isnull=False).distinct() # Filter by stock status if `in_stock` is specified in_stock = request.query_params.get('in_stock', "false") == 'true' if in_stock: products = products.filter(variants__in_stock__gt=0) # Filter by discount if `has_discount` is specified has_discount = request.query_params.get('has_discount', "false") == 'true' if has_discount: products = products.filter(variants__discount__gt=0) # Search filter search_query = request.query_params.get('search', None) if search_query: products = products.filter(Q(name__icontains=search_query) | Q(description__icontains=search_query)) # Price filters price_gte = request.query_params.get('price_gte', None) price_lte = request.query_params.get('price_lte', None) products = products.annotate(min_price=Min('variants__price'), max_price=Max('variants__price')) if price_gte: products = products.filter(max_price__gte=price_gte) if price_lte: products = products.filter(min_price__lte=price_lte) # Sorting sort_by = request.query_params.get('sort', None) if sort_by in ['name', '-name', 'created_at', '-created_at']: products = products.order_by(sort_by) else: products = products.order_by('name') # Pagination paginator = self.pagination_class() paginated_products = paginator.paginate_queryset(products, request) serializer = self.serializer_class(paginated_products, many=True, context={'request': request, 'view_type': 'slider'}) return paginator.get_paginated_response(serializer.data) except MainCategoryModel.DoesNotExist: return Response({"detail": "Category not found."}, status=status.HTTP_404_NOT_FOUND) class CommentView(APIView): serializer_class = CommentSerializer permission_classes = [IsAuthenticatedOrReadOnly] pagination_class = StructurePagination @extend_schema( parameters=[ OpenApiParameter( name="limit", description="Number of results to return per page (pagination).", required=False, type=OpenApiTypes.INT, ), OpenApiParameter( name="offset", description="The starting position of the results (pagination).", required=False, type=OpenApiTypes.INT, ) ], responses={ 200: CommentSerializer(many=True), 404: OpenApiTypes.OBJECT, }, ) def get(self, request, slug): product = get_object_or_404(ProductModel, slug=slug) comments = product.comments.filter(review_status__in=['not_reviwed', 'reviewed_and_confirmed']) paginator = self.pagination_class() paginated_comments = paginator.paginate_queryset(comments, request) comments_ser = self.serializer_class(instance=paginated_comments, many=True) return paginator.get_paginated_response(comments_ser.data) def post(self, request, slug): comment_ser = CommentSerializer(data=request.data) product = get_object_or_404(ProductModel, slug=slug) if comment_ser.is_valid(): comment_ser.save(product=product, user=request.user) return Response(comment_ser.data, status=status.HTTP_201_CREATED) return Response(comment_ser.errors, status=status.HTTP_400_BAD_REQUEST) def delete(self, request, pk): comment = get_object_or_404(CommentModel, pk=pk) if comment.user == request.user: comment.delete() return Response(status=status.HTTP_204_NO_CONTENT) else: return Response({"detail": "شما اجازه ی پاک کردن این کامنت را ندارید"}, status=status.HTTP_403_FORBIDDEN)