optimze home page and product page
This commit is contained in:
@@ -11,6 +11,10 @@ from django.utils.html import format_html
|
|||||||
from unfold.decorators import display
|
from unfold.decorators import display
|
||||||
from utils.admin import ModelAdmin
|
from utils.admin import ModelAdmin
|
||||||
from unfold.contrib.filters.admin import ChoicesDropdownFilter
|
from unfold.contrib.filters.admin import ChoicesDropdownFilter
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
# Cache key for home page
|
||||||
|
HOME_CACHE_KEY = 'home_view_data_anonymous'
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ShowCaseSlider)
|
@admin.register(ShowCaseSlider)
|
||||||
@@ -28,6 +32,10 @@ class ShowCaseSliderAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
@admin.register(LearnVideoModel)
|
@admin.register(LearnVideoModel)
|
||||||
class LearnVideoAdmin(UnfoldModelAdmin):
|
class LearnVideoAdmin(UnfoldModelAdmin):
|
||||||
list_display = ['title', 'section', 'display_viewd']
|
list_display = ['title', 'section', 'display_viewd']
|
||||||
@@ -49,6 +57,10 @@ class LearnVideoAdmin(UnfoldModelAdmin):
|
|||||||
def has_add_permission(self, request, obj=None):
|
def has_add_permission(self, request, obj=None):
|
||||||
return request.user.video_uploader
|
return request.user.video_uploader
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
|
|
||||||
@display(description='دیده شده')
|
@display(description='دیده شده')
|
||||||
def display_viewd(self, instance):
|
def display_viewd(self, instance):
|
||||||
@@ -79,6 +91,10 @@ class SliderAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
@admin.register(HomeImageModel)
|
@admin.register(HomeImageModel)
|
||||||
class HomeImageAdmin(ModelAdmin, ImportExportModelAdmin):
|
class HomeImageAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||||
import_form_class = ImportForm
|
import_form_class = ImportForm
|
||||||
@@ -94,6 +110,10 @@ class HomeImageAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
|
|
||||||
# admin.py
|
# admin.py
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
# Generated by Django 5.2 on 2026-05-04 06:29
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('home', '0018_remove_showcaseslider_image_and_more'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='slidermodel',
|
||||||
|
name='id',
|
||||||
|
field=models.BigAutoField(auto_created=True, db_index=True, primary_key=True, serialize=False, verbose_name='ID'),
|
||||||
|
),
|
||||||
|
]
|
||||||
@@ -2,6 +2,7 @@ from django.db import models
|
|||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
|
||||||
class SliderModel(models.Model):
|
class SliderModel(models.Model):
|
||||||
|
id = models.BigAutoField(auto_created=True, db_index=True, primary_key=True, serialize=False, verbose_name='ID')
|
||||||
link = models.URLField(verbose_name='لینک')
|
link = models.URLField(verbose_name='لینک')
|
||||||
title = models.CharField(max_length=50, verbose_name='عنوان')
|
title = models.CharField(max_length=50, verbose_name='عنوان')
|
||||||
description = models.TextField(verbose_name='توضیحات')
|
description = models.TextField(verbose_name='توضیحات')
|
||||||
|
|||||||
+76
-14
@@ -1,11 +1,13 @@
|
|||||||
from django.shortcuts import render, get_object_or_404, redirect
|
from django.shortcuts import render, get_object_or_404, redirect
|
||||||
from rest_framework.views import APIView, Response
|
from rest_framework.views import APIView, Response
|
||||||
from product.models import ProductModel, SubCategoryModel, MainCategoryModel
|
from product.models import ProductModel, SubCategoryModel, MainCategoryModel, ProductVariant
|
||||||
from product.serializers import SubCategorySerializer, DynamicProductSerializer, MainCategorySerializer
|
from product.serializers import SubCategorySerializer, DynamicProductSerializer, MainCategorySerializer
|
||||||
from .serializers import *
|
from .serializers import *
|
||||||
from .models import *
|
from .models import *
|
||||||
from rest_framework import status
|
from rest_framework import status
|
||||||
from django.views import View
|
from django.views import View
|
||||||
|
from django.db.models import Prefetch
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
|
||||||
class ChangeViewVideo(View):
|
class ChangeViewVideo(View):
|
||||||
@@ -18,31 +20,87 @@ class ChangeViewVideo(View):
|
|||||||
|
|
||||||
class HomeView(APIView):
|
class HomeView(APIView):
|
||||||
authentication_classes = []
|
authentication_classes = []
|
||||||
|
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
# Check cache first for anonymous users
|
||||||
|
cache_key = 'home_view_data_anonymous'
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
cached_data = cache.get(cache_key)
|
||||||
|
if cached_data:
|
||||||
|
return Response(cached_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
sliders = SliderModel.objects.only('id', 'link', 'title', 'description', 'image', 'video')[:10]
|
||||||
sliders = SliderModel.objects.all()
|
|
||||||
slider_ser = SliderSerializer(instance=sliders, many=True, context={'request': request})
|
slider_ser = SliderSerializer(instance=sliders, many=True, context={'request': request})
|
||||||
|
|
||||||
main_categories = MainCategoryModel.objects.filter(show_in_home=True)
|
main_categories = MainCategoryModel.objects.filter(
|
||||||
|
show_in_home=True
|
||||||
|
).prefetch_related('subcategorys')
|
||||||
main_category_ser = MainCategorySerializer(instance=main_categories, many=True, context={'request': request})
|
main_category_ser = MainCategorySerializer(instance=main_categories, many=True, context={'request': request})
|
||||||
|
|
||||||
top_seller_products = ProductModel.objects.filter(show_in_top_seller=True)
|
# Optimize variant prefetching - prefetch all variants, limit applied in serializer
|
||||||
top_seller_products_ser = DynamicProductSerializer(instance=top_seller_products, many=True, context={'request': request, 'view_type': 'list'})
|
variant_prefetch = Prefetch(
|
||||||
|
'variants',
|
||||||
|
ProductVariant.objects.prefetch_related('images').order_by('-discount', 'price')
|
||||||
|
)
|
||||||
|
|
||||||
lot_of_discount_products = ProductModel.objects.filter(show_in_lot_of_discount=True)
|
# Top seller products
|
||||||
lot_of_discount_products_ser = DynamicProductSerializer(instance=lot_of_discount_products, many=True, context={'request': request, 'view_type': 'list'})
|
top_seller_products = ProductModel.objects.filter(
|
||||||
|
show_in_top_seller=True
|
||||||
|
).select_related('category', 'shop').prefetch_related(
|
||||||
|
variant_prefetch
|
||||||
|
).only('id', 'name', 'image', 'rating', 'slug', 'category', 'shop')[:20]
|
||||||
|
top_seller_products_ser = DynamicProductSerializer(
|
||||||
|
instance=top_seller_products,
|
||||||
|
many=True,
|
||||||
|
context={'request': request, 'view_type': 'list'}
|
||||||
|
)
|
||||||
|
|
||||||
most_viewed_products = ProductModel.objects.filter(show_in_most_viewed=True)
|
# Lot of discount products
|
||||||
most_viewed_products_ser = DynamicProductSerializer(instance=most_viewed_products, many=True, context={'request': request, 'view_type': 'list'})
|
lot_of_discount_products = ProductModel.objects.filter(
|
||||||
|
show_in_lot_of_discount=True
|
||||||
|
).select_related('category', 'shop').prefetch_related(
|
||||||
|
variant_prefetch
|
||||||
|
).only('id', 'name', 'image', 'rating', 'slug', 'category', 'shop')[:20]
|
||||||
|
lot_of_discount_products_ser = DynamicProductSerializer(
|
||||||
|
instance=lot_of_discount_products,
|
||||||
|
many=True,
|
||||||
|
context={'request': request, 'view_type': 'list'}
|
||||||
|
)
|
||||||
|
|
||||||
trends_products = ProductModel.objects.filter(show_in_trends=True)
|
# Most viewed products
|
||||||
trends_products_ser = DynamicProductSerializer(instance=trends_products, many=True, context={'request': request, 'view_type': 'list'})
|
most_viewed_products = ProductModel.objects.filter(
|
||||||
|
show_in_most_viewed=True
|
||||||
|
).select_related('category', 'shop').prefetch_related(
|
||||||
|
variant_prefetch
|
||||||
|
).only('id', 'name', 'image', 'rating', 'slug', 'category', 'shop')[:20]
|
||||||
|
most_viewed_products_ser = DynamicProductSerializer(
|
||||||
|
instance=most_viewed_products,
|
||||||
|
many=True,
|
||||||
|
context={'request': request, 'view_type': 'list'}
|
||||||
|
)
|
||||||
|
|
||||||
home_image = HomeImageModel.objects.all().first()
|
# Trends products
|
||||||
|
trends_products = ProductModel.objects.filter(
|
||||||
|
show_in_trends=True
|
||||||
|
).select_related('category', 'shop').prefetch_related(
|
||||||
|
variant_prefetch
|
||||||
|
).only('id', 'name', 'image', 'rating', 'slug', 'category', 'shop')[:20]
|
||||||
|
trends_products_ser = DynamicProductSerializer(
|
||||||
|
instance=trends_products,
|
||||||
|
many=True,
|
||||||
|
context={'request': request, 'view_type': 'list'}
|
||||||
|
)
|
||||||
|
|
||||||
|
home_image = HomeImageModel.objects.only(
|
||||||
|
'id', 'image1', 'image2', 'title1', 'title2',
|
||||||
|
'description1', 'description2', 'link1', 'link2',
|
||||||
|
'video1', 'video2'
|
||||||
|
).first()
|
||||||
home_image_ser = HomeImageSerializer(instance=home_image, context={'request': request})
|
home_image_ser = HomeImageSerializer(instance=home_image, context={'request': request})
|
||||||
|
|
||||||
show_cases = ShowCaseSlider.objects.all()
|
show_cases = ShowCaseSlider.objects.only(
|
||||||
|
'id', 'title', 'description', 'image1', 'image2', 'image3', 'background_image'
|
||||||
|
)[:10]
|
||||||
show_cases_ser = ShowCaseSliderSerialzier(instance=show_cases, many=True, context={'request': request})
|
show_cases_ser = ShowCaseSliderSerialzier(instance=show_cases, many=True, context={'request': request})
|
||||||
|
|
||||||
response = {
|
response = {
|
||||||
@@ -57,4 +115,8 @@ class HomeView(APIView):
|
|||||||
'show_case_slider': show_cases_ser.data
|
'show_case_slider': show_cases_ser.data
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Cache for anonymous users only (avoid issues with user-specific data)
|
||||||
|
if not request.user.is_authenticated:
|
||||||
|
cache.set(cache_key, response, 60 * 5) # 5 minutes cache
|
||||||
|
|
||||||
return Response(response, status=status.HTTP_200_OK)
|
return Response(response, status=status.HTTP_200_OK)
|
||||||
@@ -15,6 +15,10 @@ from django.shortcuts import redirect, render
|
|||||||
from django.utils.html import format_html
|
from django.utils.html import format_html
|
||||||
from .permissions import ProductDetailCategoryPermission, ProductAdminPermission, ProductVariantAdminPermission, ProductVariantInlineAdminPermission, InPackItemsAdminPermission, AttributeTypeAdminPermission, AttributeValueAdminPermission
|
from .permissions import ProductDetailCategoryPermission, ProductAdminPermission, ProductVariantAdminPermission, ProductVariantInlineAdminPermission, InPackItemsAdminPermission, AttributeTypeAdminPermission, AttributeValueAdminPermission
|
||||||
from django import forms
|
from django import forms
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
# Cache key for home page
|
||||||
|
HOME_CACHE_KEY = 'home_view_data_anonymous'
|
||||||
|
|
||||||
@admin.register(ProductDetailCategory)
|
@admin.register(ProductDetailCategory)
|
||||||
class ProductDetailCategoryAdmin(ProductDetailCategoryPermission, ModelAdmin, ImportExportModelAdmin):
|
class ProductDetailCategoryAdmin(ProductDetailCategoryPermission, ModelAdmin, ImportExportModelAdmin):
|
||||||
@@ -300,6 +304,10 @@ class ProductVariantInLine(ProductVariantInlineAdminPermission, StackedInline):
|
|||||||
else:
|
else:
|
||||||
return ['price', 'sell', 'price_in_dollor', 'slider_category']
|
return ['price', 'sell', 'price_in_dollor', 'slider_category']
|
||||||
|
|
||||||
|
def save_formset(self, request, form, formset, change):
|
||||||
|
super().save_formset(request, form, formset, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
from unfold.contrib.filters.admin import RelatedDropdownFilter
|
from unfold.contrib.filters.admin import RelatedDropdownFilter
|
||||||
|
|
||||||
|
|
||||||
@@ -330,6 +338,10 @@ class ProductVariantAdmin(ProductVariantAdminPermission, ModelAdmin, ImportExpor
|
|||||||
list_display = ('product', 'created_at')
|
list_display = ('product', 'created_at')
|
||||||
# inlines = [DetailModelInLine]
|
# inlines = [DetailModelInLine]
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
|
|
||||||
@admin.register(ProductModel)
|
@admin.register(ProductModel)
|
||||||
class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdmin):
|
class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdmin):
|
||||||
@@ -369,6 +381,10 @@ class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdm
|
|||||||
else:
|
else:
|
||||||
return ['show_in_bot', 'bot_banner', 'created_at', 'show_in_top_seller','show_in_trends', 'show_in_most_viewed', 'show_in_lot_of_discount','meta_description', 'meta_keywords', 'meta_rating', 'rating', 'view', 'slug']
|
return ['show_in_bot', 'bot_banner', 'created_at', 'show_in_top_seller','show_in_trends', 'show_in_most_viewed', 'show_in_lot_of_discount','meta_description', 'meta_keywords', 'meta_rating', 'rating', 'view', 'slug']
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
|
||||||
def display_price(self, obj):
|
def display_price(self, obj):
|
||||||
if obj.variants.all().first():
|
if obj.variants.all().first():
|
||||||
return obj.variants.all().first().price
|
return obj.variants.all().first().price
|
||||||
@@ -464,6 +480,11 @@ class MainCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
cache.delete('all_categories_v2')
|
||||||
|
|
||||||
@admin.register(SubCategoryModel)
|
@admin.register(SubCategoryModel)
|
||||||
class SubCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
class SubCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||||
list_display = ['name', 'parent']
|
list_display = ['name', 'parent']
|
||||||
@@ -488,6 +509,20 @@ class SubCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
|
|||||||
def has_view_permission(self, request, obj = ...):
|
def has_view_permission(self, request, obj = ...):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def has_add_permission(self, request):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def has_change_permission(self, request, obj=None):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def has_delete_permission(self, request, obj=None):
|
||||||
|
return False
|
||||||
|
|
||||||
|
def save_model(self, request, obj, form, change):
|
||||||
|
super().save_model(request, obj, form, change)
|
||||||
|
cache.delete(HOME_CACHE_KEY)
|
||||||
|
cache.delete('all_categories_v2')
|
||||||
|
|
||||||
@admin.register(CommentModel)
|
@admin.register(CommentModel)
|
||||||
class CommentAdmin(ModelAdmin, ImportExportModelAdmin):
|
class CommentAdmin(ModelAdmin, ImportExportModelAdmin):
|
||||||
import_form_class = ImportForm
|
import_form_class = ImportForm
|
||||||
|
|||||||
@@ -122,12 +122,11 @@ class SubCategorySerializer(serializers.ModelSerializer):
|
|||||||
'meta_description', 'product_count', 'parent', 'image']
|
'meta_description', 'product_count', 'parent', 'image']
|
||||||
|
|
||||||
def get_product_count(self, obj):
|
def get_product_count(self, obj):
|
||||||
# Use annotated product_count if available (from optimized query)
|
# Use annotated product_count from database annotation
|
||||||
# Otherwise fall back to counting (for backward compatibility)
|
return getattr(obj, 'product_count', 0)
|
||||||
return getattr(obj, 'product_count', obj.products.count())
|
|
||||||
|
|
||||||
def get_parent(self, obj):
|
def get_parent(self, obj):
|
||||||
return obj.parent.name
|
return obj.parent.name if obj.parent else None
|
||||||
|
|
||||||
|
|
||||||
class UnitCategorySerializer(serializers.ModelSerializer):
|
class UnitCategorySerializer(serializers.ModelSerializer):
|
||||||
@@ -159,6 +158,7 @@ class DynamicProductSerializer(serializers.ModelSerializer):
|
|||||||
main_image = serializers.SerializerMethodField()
|
main_image = serializers.SerializerMethodField()
|
||||||
customer_pickup_title = serializers.SerializerMethodField()
|
customer_pickup_title = serializers.SerializerMethodField()
|
||||||
customer_pickup_description = serializers.SerializerMethodField()
|
customer_pickup_description = serializers.SerializerMethodField()
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs):
|
def __init__(self, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
view_type = self.context.get('view_type', 'all')
|
view_type = self.context.get('view_type', 'all')
|
||||||
@@ -180,31 +180,48 @@ class DynamicProductSerializer(serializers.ModelSerializer):
|
|||||||
'chat': ['id', 'name', 'description', 'variants', 'image']
|
'chat': ['id', 'name', 'description', 'variants', 'image']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
def _get_best_deal_variant(self, obj):
|
||||||
|
"""Get best deal variant from prefetched variants (pre-ordered by discount/price)"""
|
||||||
|
if not hasattr(self, '_best_deal_cache'):
|
||||||
|
self._best_deal_cache = {}
|
||||||
|
|
||||||
|
if obj.id not in self._best_deal_cache:
|
||||||
|
# Get first from prefetched variants (already ordered by discount desc, price asc)
|
||||||
|
variants = list(obj.variants.all())
|
||||||
|
self._best_deal_cache[obj.id] = variants[0] if variants else None
|
||||||
|
|
||||||
|
return self._best_deal_cache[obj.id]
|
||||||
|
|
||||||
def get_main_image(self, obj):
|
def get_main_image(self, obj):
|
||||||
if obj.image:
|
if obj.image:
|
||||||
return obj.image.url
|
return obj.image.url
|
||||||
return obj.variants.first().images.first().image.url if obj.variants.exists() and obj.variants.first().images.exists() else None
|
# Use prefetched variants
|
||||||
|
variants = list(obj.variants.all())
|
||||||
|
if variants:
|
||||||
|
images = list(variants[0].images.all())
|
||||||
|
if images:
|
||||||
|
return images[0].image.url
|
||||||
|
return None
|
||||||
|
|
||||||
def get_best_deal_price_before_discount(self, obj):
|
def get_best_deal_price_before_discount(self, obj):
|
||||||
best_deal = obj.get_best_deal_variant()
|
best_deal = self._get_best_deal_variant(obj)
|
||||||
if best_deal:
|
if best_deal:
|
||||||
return f'{best_deal.price:,.0f} تومانءءء'
|
return f'{best_deal.price:,.0f} تومانءءء'
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_best_deal_price_after_discount(self, obj):
|
def get_best_deal_price_after_discount(self, obj):
|
||||||
best_deal = obj.get_best_deal_variant()
|
best_deal = self._get_best_deal_variant(obj)
|
||||||
if best_deal:
|
if best_deal:
|
||||||
price_after_discount = best_deal.price_after_discount
|
price_after_discount = best_deal.price_after_discount
|
||||||
return f'{price_after_discount:,.0f} تومانءءء'
|
return f'{price_after_discount:,.0f} تومانءءء'
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
def get_best_deal_discount(self, obj):
|
def get_best_deal_discount(self, obj):
|
||||||
best_deal = obj.get_best_deal_variant()
|
best_deal = self._get_best_deal_variant(obj)
|
||||||
if best_deal:
|
if best_deal:
|
||||||
return best_deal.discount
|
return best_deal.discount
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
|
||||||
def get_customer_pickup_title(self, obj):
|
def get_customer_pickup_title(self, obj):
|
||||||
if obj.shop:
|
if obj.shop:
|
||||||
return obj.shop.customer_pickup_title
|
return obj.shop.customer_pickup_title
|
||||||
@@ -219,7 +236,7 @@ class DynamicProductSerializer(serializers.ModelSerializer):
|
|||||||
from account.models import UserFavorites
|
from account.models import UserFavorites
|
||||||
request = self.context.get('request')
|
request = self.context.get('request')
|
||||||
if not request or not request.user.is_authenticated:
|
if not request or not request.user.is_authenticated:
|
||||||
return False # not logged in users haven't added anything
|
return False
|
||||||
|
|
||||||
# Use exists() with filter instead of fetching all products
|
# Use exists() with filter instead of fetching all products
|
||||||
return UserFavorites.objects.filter(user=request.user, products=obj).exists()
|
return UserFavorites.objects.filter(user=request.user, products=obj).exists()
|
||||||
@@ -230,23 +247,24 @@ class DynamicProductSerializer(serializers.ModelSerializer):
|
|||||||
varients = obj.variants.filter(slider_category__isnull=False)
|
varients = obj.variants.filter(slider_category__isnull=False)
|
||||||
else:
|
else:
|
||||||
varients = obj.variants.all()
|
varients = obj.variants.all()
|
||||||
colors = set(varient.color for varient in varients)
|
|
||||||
return ProductVariantSerialzier(instance=varients, many=True, context=self.context).data
|
return ProductVariantSerialzier(instance=varients, many=True, context=self.context).data
|
||||||
|
|
||||||
def get_colors(self, obj):
|
def get_colors(self, obj):
|
||||||
# Use values_list to get only color field, reducing data transfer
|
# Get colors from prefetched variants efficiently
|
||||||
colors = obj.variants.values_list('color', flat=True).distinct()
|
variants = list(obj.variants.all())
|
||||||
return list(filter(None, colors)) # Filter out None values
|
colors = set()
|
||||||
|
for variant in variants:
|
||||||
|
if variant.color:
|
||||||
|
colors.add(variant.color)
|
||||||
|
return list(colors)
|
||||||
|
|
||||||
def get_is_new(self, obj):
|
def get_is_new(self, obj):
|
||||||
return timezone.now() < obj.created_at + timedelta(days=7)
|
return timezone.now() < obj.created_at + timedelta(days=7)
|
||||||
|
|
||||||
def get_related_products(self, obj):
|
def get_related_products(self, obj):
|
||||||
if obj.related_products.all().count() >= 5:
|
if obj.related_products.all().count() >= 5:
|
||||||
# Limit to 10 related products
|
|
||||||
related_products = obj.related_products.all()[:10]
|
related_products = obj.related_products.all()[:10]
|
||||||
else:
|
else:
|
||||||
# Limit category products and exclude current product
|
|
||||||
related_products = obj.category.products.exclude(id=obj.id)[:10]
|
related_products = obj.category.products.exclude(id=obj.id)[:10]
|
||||||
|
|
||||||
serializer = DynamicProductSerializer(
|
serializer = DynamicProductSerializer(
|
||||||
|
|||||||
@@ -92,24 +92,41 @@ class AllCategoriesV2(APIView):
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
def get(self, request):
|
def get(self, request):
|
||||||
|
from django.core.cache import cache
|
||||||
|
|
||||||
|
# Check cache first
|
||||||
|
cache_key = 'all_categories_v2'
|
||||||
|
cached_data = cache.get(cache_key)
|
||||||
|
if cached_data:
|
||||||
|
return Response(cached_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
# Optimize query with prefetch_related to avoid N+1 queries
|
# Optimize query with prefetch_related to avoid N+1 queries
|
||||||
unit_categories = UnitCategoryModel.objects.prefetch_related(
|
unit_categories = UnitCategoryModel.objects.prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
'maincategorys',
|
'maincategorys',
|
||||||
queryset=MainCategoryModel.objects.prefetch_related(
|
queryset=MainCategoryModel.objects.only(
|
||||||
|
'id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image', 'video'
|
||||||
|
).prefetch_related(
|
||||||
Prefetch(
|
Prefetch(
|
||||||
'subcategorys',
|
'subcategorys',
|
||||||
queryset=SubCategoryModel.objects.annotate(
|
queryset=SubCategoryModel.objects.only(
|
||||||
|
'id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image', 'parent_id'
|
||||||
|
).annotate(
|
||||||
product_count=Count('products')
|
product_count=Count('products')
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).all()
|
).only('id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image').all()
|
||||||
|
|
||||||
categories_ser = self.serializer_class(
|
categories_ser = self.serializer_class(
|
||||||
instance=unit_categories, many=True, context={'request': request})
|
instance=unit_categories, many=True, context={'request': request})
|
||||||
return Response(categories_ser.data, status=status.HTTP_200_OK)
|
|
||||||
|
response_data = categories_ser.data
|
||||||
|
# Cache for 10 minutes
|
||||||
|
cache.set(cache_key, response_data, 60 * 10)
|
||||||
|
|
||||||
|
return Response(response_data, status=status.HTTP_200_OK)
|
||||||
|
|
||||||
|
|
||||||
class ProductView(APIView):
|
class ProductView(APIView):
|
||||||
|
|||||||
Reference in New Issue
Block a user