from .models import ProductModel from rest_framework import serializers 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, Count, Prefetch from home.models import ShowCaseSlider from home.serializers import ShowCaseSliderSerialzier from order.models import Cart, CartItem # 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: # Optimize query with prefetch_related to avoid N+1 queries categories = MainCategoryModel.objects.prefetch_related( Prefetch( 'subcategorys', queryset=SubCategoryModel.objects.annotate( product_count=Count('products') ) ) ).all() categories_ser = self.serializer_class( instance=categories, many=True, context={'request': request}) return Response(categories_ser.data, status=status.HTTP_200_OK) class UnitCategorySerializerV2(serializers.ModelSerializer): maincategorys = serializers.SerializerMethodField() class Meta: model = UnitCategoryModel fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image', 'maincategorys'] def get_maincategorys(self, obj): main_categories = obj.maincategorys.all() return MainCategorySerializer(main_categories, many=True, context=self.context).data class AllCategoriesV2(APIView): serializer_class = UnitCategorySerializerV2 authentication_classes = [] @extend_schema( responses={ 200: UnitCategorySerializerV2(many=True), 404: OpenApiTypes.OBJECT, }, ) def get(self, request): # Optimize query with prefetch_related to avoid N+1 queries unit_categories = UnitCategoryModel.objects.prefetch_related( Prefetch( 'maincategorys', queryset=MainCategoryModel.objects.prefetch_related( Prefetch( 'subcategorys', queryset=SubCategoryModel.objects.annotate( product_count=Count('products') ) ) ) ) ).all() categories_ser = self.serializer_class( instance=unit_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): # Optimize query with select_related and prefetch_related to avoid N+1 queries product = get_object_or_404( ProductModel.objects.select_related( 'category', 'category__parent', 'shop') .prefetch_related( 'variants__product_attributes__attribute_type', 'variants__in_pack_items', 'variants__images', 'variants__details__details', 'variants__details__detail_category', 'related_products__variants__product_attributes', 'related_products__category', ), slug=slug ) if request.user.is_authenticated: cart_obj, _ = Cart.objects.get_or_create(user=request.user) # Optimize cart items query - prefetch all related data cart_items = cart_obj.items.select_related( 'product_variant__product' ).prefetch_related( 'product_variant__images', 'product_variant__product_attributes__attribute_type' ) 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') # Start with optimized base query products = ProductModel.objects.select_related( 'category', 'category__parent' ).prefetch_related( 'variants__product_attributes__attribute_type', 'variants__images', ) if category_slug: if 'category' not in category_slug: sub_category = get_object_or_404( SubCategoryModel, slug=category_slug) products = products.filter(category=sub_category) else: main_category = get_object_or_404( MainCategoryModel, slug=category_slug) sub_categories = main_category.subcategorys.all() products = products.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).distinct() 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).distinct() 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 products.order_by('category') 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) # Start with optimized base query products = ProductModel.objects.select_related( 'category', 'category__parent' ).prefetch_related( 'variants__product_attributes__attribute_type', 'variants__images', ) 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 = products.filter( variants__slider_category=slider_category).distinct() else: products = products.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).distinct() # 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).distinct() # 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) # Optimize comments query to prefetch user data comments = product.comments.filter( review_status__in=['not_reviwed', 'reviewed_and_confirmed'] ).select_related('user') 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) class BotProductSerializer(serializers.ModelSerializer): class Meta: model = ProductModel fields = ['pk', 'name'] class BotProductsView(APIView): serializer_class = BotProductSerializer def get(self, request): bot_products = ProductModel.objects.filter(show_in_bot=True) if bot_products.exists(): serialized = self.serializer_class(bot_products, many=True) return Response({ "success": True, "products": serialized.data }) else: return Response({ "success": False, "products": [] }) class BotProductDetailView(APIView): def get(self, request, pk): product = get_object_or_404(ProductModel, pk=pk, show_in_bot=True) return Response({ 'name': product.name, 'banner': product.bot_banner, 'link': f'https://heymlz.com/product/{product.slug}' }) class BotCategorySerializer(serializers.ModelSerializer): link = serializers.SerializerMethodField() class Meta: model = MainCategoryModel fields = ['pk', 'name', 'link'] def get_link(self, obj): return f'https://heymlz.com/products/category/{obj.slug}' class BotCategoryView(APIView): serializer_class = BotCategorySerializer def get(self, request): categories = MainCategoryModel.objects.all() categories_ser = self.serializer_class(categories, many=True) if categories.exists(): return Response({ "success": True, "categories": categories_ser.data }) else: return Response({ "success": False, "categories": [] })