from .models import * from rest_framework import serializers from django.utils import timezone from datetime import timedelta from django.contrib.auth.models import AnonymousUser class DetailSerializer(serializers.ModelSerializer): texts = serializers.SerializerMethodField() class Meta: model = DetailModel exclude = ['detail_model', 'detail_text1', 'detail_text2', 'detail_text3', 'detail_text4'] def get_texts(self, obj): return [ text for text in [ obj.detail_text1, obj.detail_text2, obj.detail_text3, obj.detail_text4, ] if text ] class ProductDetailSerializer(serializers.ModelSerializer): details = DetailSerializer(many=True, read_only=True) detail_category = serializers.StringRelatedField() class Meta: model = ProductDetailModel exclude = ['name'] class AttributeTypeSerialzier(serializers.ModelSerializer): class Meta: model = AttributeType fields = "__all__" class AttributeValueSerialzier(serializers.ModelSerializer): attribute_type = AttributeTypeSerialzier() class Meta: model = AttributeValue fields = "__all__" class InPackItemsSerialzier(serializers.ModelSerializer): class Meta: model = InPackItems fields = '__all__' class ProductImageSerailizer(serializers.ModelSerializer): class Meta: model = ProductImageModel fields = '__all__' class ProductVariantSerialzier(serializers.ModelSerializer): product_attributes = AttributeValueSerialzier(many=True) in_pack_items = InPackItemsSerialzier(many=True) images = ProductImageSerailizer(many=True) details = ProductDetailSerializer(many=True, read_only=True) cart_quantity = serializers.SerializerMethodField() price = serializers.SerializerMethodField() price_after_discount = serializers.SerializerMethodField() special_discount_amount = serializers.SerializerMethodField() special_discount_link = serializers.SerializerMethodField() class Meta: model = ProductVariant exclude = ('min_price', 'sell', 'currency', 'product', 'input_price', 'price_in_dollor', 'profit', 'special_discount_percent') def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) view_type = self.context.get('view_type', None) if view_type == 'list': self.fields.pop('in_pack_items', None) def get_special_discount_amount(self, obj): if obj.special_discount_percent: special_discount_amount = obj.profit * (obj.special_discount_percent / 100) return f'{special_discount_amount:,.0f} تومانءءء' else: return None def get_special_discount_link(self, obj): request = self.context.get('request') if request.user.is_authenticated: return 'https://google.com' else: return None def get_price_after_discount(self, obj): return f'{obj.price_after_discount:,.0f} تومانءءء' def get_cart_quantity(self, obj): request = self.context.get('request') if not request or not request.user.is_authenticated: return 0 cart_items = self.context.get('cart_items', []) if cart_items: for item in cart_items: if item['product']['id'] == obj.id: return item['quantity'] return 0 def get_price(self, obj): return f'{obj.price:,.0f} تومانءءء' class SubCategorySerializer(serializers.ModelSerializer): product_count = serializers.SerializerMethodField() parent = serializers.SerializerMethodField() class Meta: model = SubCategoryModel fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'product_count', 'parent', 'image'] def get_product_count(self, obj): # Use annotated product_count from database annotation return getattr(obj, 'product_count', 0) def get_parent(self, obj): return obj.parent.name if obj.parent else None class UnitCategorySerializer(serializers.ModelSerializer): class Meta: model = UnitCategoryModel fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'image'] class MainCategorySerializer(serializers.ModelSerializer): subcategorys = SubCategorySerializer(many=True) class Meta: model = MainCategoryModel fields = ['id', 'name', 'slug', 'icon', 'meta_title', 'meta_description', 'subcategorys', 'image', 'video'] class DynamicProductSerializer(serializers.ModelSerializer): variants = serializers.SerializerMethodField() colors = serializers.SerializerMethodField() category = SubCategorySerializer(read_only=True) is_new = serializers.SerializerMethodField() related_products = serializers.SerializerMethodField() added_to_favorites = serializers.SerializerMethodField() best_deal_price_before_discount = serializers.SerializerMethodField() best_deal_price_after_discount = serializers.SerializerMethodField() best_deal_discount = serializers.SerializerMethodField() main_image = serializers.SerializerMethodField() customer_pickup_title = serializers.SerializerMethodField() customer_pickup_description = serializers.SerializerMethodField() average_rating = serializers.SerializerMethodField() user_rating = serializers.SerializerMethodField() def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) view_type = self.context.get('view_type', 'all') if view_type != 'all': allowed_fields = self.Meta.view_type[view_type] allowed = set(allowed_fields) existing = set(self.fields.keys()) for field_name in existing - allowed: self.fields.pop(field_name) class Meta: model = ProductModel fields = "__all__" view_type = { 'list': ['id', 'name', 'rating', 'slug', 'category', 'colors', 'image', 'best_deal_price_before_discount', 'best_deal_price_after_discount', 'best_deal_discount', 'main_image', 'average_rating'], 'slider': ['id', 'name', 'rating', 'slug', 'category', 'variants', 'colors', 'image', 'best_deal_price_before_discount', 'best_deal_price_after_discount', 'best_deal_discount', 'average_rating'], 'instance': ['id', 'name', 'description', 'rating', 'slug', 'meta_description', 'meta_keywords', 'meta_rating', 'category', 'related_products', 'in_pack_items', 'variants', 'colors', 'added_to_favorites', 'image', 'customer_pickup_title', 'customer_pickup_description', 'average_rating', 'user_rating'], 'chat': ['id', 'name', 'description', 'variants', 'image'] } def get_user_rating(self, obj): request = self.context.get('request') if not request.user.is_authenticated: return None product_ratings = obj.ratings.all() if product_ratings.filter(user=request.user).exists(): return product_ratings.filter(user=request.user).first().rating else: return None 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 # 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 = 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 = 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 = 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 return None def get_customer_pickup_description(self, obj): if obj.shop: return obj.shop.customer_pickup_description return None def get_added_to_favorites(self, obj): from account.models import UserFavorites request = self.context.get('request') if not request or not request.user.is_authenticated: return False # Use exists() with filter instead of fetching all products return UserFavorites.objects.filter(user=request.user, products=obj).exists() def get_average_rating(self, obj): """Get cached average rating for product - optimized with prefetch""" from django.core.cache import cache from django.db.models import Avg cache_key = f'product_avg_rating_{obj.id}' avg_rating = cache.get(cache_key) if avg_rating is None: # Try to use prefetched ratings if available (no query needed) try: prefetched_ratings = obj._prefetched_objects_cache.get('ratings') if prefetched_ratings is not None: # Use prefetched data - no database query ratings_list = [r.rating for r in prefetched_ratings] if ratings_list: avg_rating = round(sum(ratings_list) / len(ratings_list), 2) else: avg_rating = 0 else: # Fall back to aggregation query if not prefetched avg_rating = obj.ratings.aggregate(Avg('rating'))['rating__avg'] or 0 avg_rating = round(avg_rating, 2) except (AttributeError, KeyError): # Fall back to aggregation query avg_rating = obj.ratings.aggregate(Avg('rating'))['rating__avg'] or 0 avg_rating = round(avg_rating, 2) # Cache for 1 hour cache.set(cache_key, avg_rating, 3600) return avg_rating def get_variants(self, obj): view_type = self.context.get('view_type') if view_type == 'slider': varients = obj.variants.filter(slider_category__isnull=False) else: varients = obj.variants.all() return ProductVariantSerialzier(instance=varients, many=True, context=self.context).data def get_colors(self, obj): # 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: related_products = obj.related_products.all()[:10] else: related_products = obj.category.products.exclude(id=obj.id)[:10] serializer = DynamicProductSerializer( related_products, many=True, context={ 'view_type': 'list', 'request': self.context.get('request') } ) return serializer.data class CommentSerializer(serializers.ModelSerializer): class Meta: model = CommentModel exclude = ('review_status', ) read_only_fields = ('review_status', 'product', 'user') class BotProductSerializer(serializers.ModelSerializer): class Meta: model = ProductModel fields = [ 'pk', 'name' ] class ProductRatingSerializer(serializers.ModelSerializer): class Meta: model = ProductRating fields = ['rating'] def validate_rating(self, value): if value not in [1, 2, 3, 4, 5]: raise serializers.ValidationError("امتیاز باید بین 1 تا 5 باشد") return value def create(self, validated_data): product_id = self.context.get('product_id') user = self.context.get('request').user rating = ProductRating.objects.create( product_id=product_id, user=user, rating=validated_data['rating'] ) return rating