Files
hossein-por-shop/backend/product/serializers.py
T

362 lines
13 KiB
Python

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