optimze home page and product page

This commit is contained in:
Parsa Nazer
2026-05-06 10:27:08 +03:30
parent 74554a664a
commit c97570e541
7 changed files with 205 additions and 34 deletions
+35
View File
@@ -15,6 +15,10 @@ from django.shortcuts import redirect, render
from django.utils.html import format_html
from .permissions import ProductDetailCategoryPermission, ProductAdminPermission, ProductVariantAdminPermission, ProductVariantInlineAdminPermission, InPackItemsAdminPermission, AttributeTypeAdminPermission, AttributeValueAdminPermission
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)
class ProductDetailCategoryAdmin(ProductDetailCategoryPermission, ModelAdmin, ImportExportModelAdmin):
@@ -299,6 +303,10 @@ class ProductVariantInLine(ProductVariantInlineAdminPermission, StackedInline):
return ['price', 'sell', 'price_in_dollor']
else:
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
@@ -330,6 +338,10 @@ class ProductVariantAdmin(ProductVariantAdminPermission, ModelAdmin, ImportExpor
list_display = ('product', 'created_at')
# 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)
class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdmin):
@@ -369,6 +381,10 @@ class ProductModelAdmin(ProductAdminPermission, ModelAdmin, ImportExportModelAdm
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']
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):
if obj.variants.all().first():
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)
class SubCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
list_display = ['name', 'parent']
@@ -487,6 +508,20 @@ class SubCategoryModelAdmin(ModelAdmin, ImportExportModelAdmin):
def has_view_permission(self, request, obj = ...):
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)
class CommentAdmin(ModelAdmin, ImportExportModelAdmin):
+34 -16
View File
@@ -122,12 +122,11 @@ class SubCategorySerializer(serializers.ModelSerializer):
'meta_description', 'product_count', 'parent', 'image']
def get_product_count(self, obj):
# Use annotated product_count if available (from optimized query)
# Otherwise fall back to counting (for backward compatibility)
return getattr(obj, 'product_count', obj.products.count())
# Use annotated product_count from database annotation
return getattr(obj, 'product_count', 0)
def get_parent(self, obj):
return obj.parent.name
return obj.parent.name if obj.parent else None
class UnitCategorySerializer(serializers.ModelSerializer):
@@ -159,6 +158,7 @@ class DynamicProductSerializer(serializers.ModelSerializer):
main_image = serializers.SerializerMethodField()
customer_pickup_title = serializers.SerializerMethodField()
customer_pickup_description = serializers.SerializerMethodField()
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
view_type = self.context.get('view_type', 'all')
@@ -180,31 +180,48 @@ class DynamicProductSerializer(serializers.ModelSerializer):
'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):
if obj.image:
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):
best_deal = obj.get_best_deal_variant()
best_deal = self._get_best_deal_variant(obj)
if best_deal:
return f'{best_deal.price:,.0f} تومانءءء'
return 0
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:
price_after_discount = best_deal.price_after_discount
return f'{price_after_discount:,.0f} تومانءءء'
return 0
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:
return best_deal.discount
return 0
def get_customer_pickup_title(self, obj):
if obj.shop:
return obj.shop.customer_pickup_title
@@ -219,7 +236,7 @@ class DynamicProductSerializer(serializers.ModelSerializer):
from account.models import UserFavorites
request = self.context.get('request')
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
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)
else:
varients = obj.variants.all()
colors = set(varient.color for varient in varients)
return ProductVariantSerialzier(instance=varients, many=True, context=self.context).data
def get_colors(self, obj):
# Use values_list to get only color field, reducing data transfer
colors = obj.variants.values_list('color', flat=True).distinct()
return list(filter(None, colors)) # Filter out None values
# Get colors from prefetched variants efficiently
variants = list(obj.variants.all())
colors = set()
for variant in variants:
if variant.color:
colors.add(variant.color)
return list(colors)
def get_is_new(self, obj):
return timezone.now() < obj.created_at + timedelta(days=7)
def get_related_products(self, obj):
if obj.related_products.all().count() >= 5:
# Limit to 10 related products
related_products = obj.related_products.all()[:10]
else:
# Limit category products and exclude current product
related_products = obj.category.products.exclude(id=obj.id)[:10]
serializer = DynamicProductSerializer(
+21 -4
View File
@@ -92,24 +92,41 @@ class AllCategoriesV2(APIView):
},
)
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
unit_categories = UnitCategoryModel.objects.prefetch_related(
Prefetch(
'maincategorys',
queryset=MainCategoryModel.objects.prefetch_related(
queryset=MainCategoryModel.objects.only(
'id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image', 'video'
).prefetch_related(
Prefetch(
'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')
)
)
)
)
).all()
).only('id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image').all()
categories_ser = self.serializer_class(
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):