import logging from collections import defaultdict from contextlib import suppress from typing import Collection, Any from django.contrib.auth.models import AnonymousUser from django.core.cache import cache from django.db.models.functions import Length from rest_framework.fields import JSONField, SerializerMethodField from rest_framework.serializers import ModelSerializer from rest_framework_recursive.fields import RecursiveField from core.models import ( Attribute, AttributeGroup, AttributeValue, Brand, Category, Feedback, Order, OrderProduct, Product, ProductImage, ProductTag, PromoCode, Promotion, Stock, Vendor, Wishlist, ) from core.serializers.simple import CategorySimpleSerializer, ProductSimpleSerializer from core.serializers.utility import AddressSerializer logger = logging.getLogger("django") class AttributeGroupDetailSerializer(ModelSerializer): children = RecursiveField(many=True) class Meta: model = AttributeGroup fields = [ "uuid", "name", "children", "created", "modified", ] class CategoryDetailSerializer(ModelSerializer): children = SerializerMethodField() image = SerializerMethodField() filterable_attributes = SerializerMethodField() class Meta: model = Category fields = [ "uuid", "name", "description", "image", "markup_percent", "filterable_attributes", "children", "slug", "created", "modified", ] def get_image(self, obj: Category) -> str | None: with suppress(ValueError): return obj.image.url return None def get_filterable_attributes(self, obj: Category) -> list[dict]: cache_key = f"{obj.uuid}_filterable_results" filterable_results = cache.get(cache_key) if filterable_results is not None: return filterable_results attrs_qs = obj.attributes.filter(is_active=True, is_filterable=True) attributes = list(attrs_qs) attr_ids = [a.id for a in attributes] raw_vals = ( AttributeValue.objects.annotate(value_length=Length("value")) .filter( attribute_id__in=attr_ids, product__category=obj, value_length__lte=30, ) .values_list("attribute_id", "value") .distinct() ) grouped = defaultdict(list) for attr_id, val in raw_vals: grouped[attr_id].append(val) filterable_results = [] for attr in attributes: vals = grouped.get(attr.id, []) # type: ignore slice_vals = vals[:128] if len(vals) > 128 else vals filterable_results.append( { "attribute_name": attr.name, "possible_values": slice_vals, "value_type": attr.value_type, } ) cache.set(cache_key, filterable_results, 3600) return filterable_results def get_children(self, obj) -> Collection[Any]: request = self.context.get("request") if request is not None and request.user.has_perm("view_category"): children = obj.children.all() else: children = obj.children.filter(is_active=True) return CategoryDetailSerializer(children, many=True, context=self.context).data if obj.children.exists() else [] class BrandDetailSerializer(ModelSerializer): categories = CategorySimpleSerializer(many=True) small_logo = SerializerMethodField() big_logo = SerializerMethodField() class Meta: model = Brand fields = [ "uuid", "name", "categories", "created", "modified", "big_logo", "small_logo", ] def get_small_logo(self, obj: Brand) -> str | None: with suppress(ValueError): return obj.small_logo.url return None def get_big_logo(self, obj: Brand) -> str | None: with suppress(ValueError): return obj.big_logo.url return None class BrandProductDetailSerializer(ModelSerializer): small_logo = SerializerMethodField() big_logo = SerializerMethodField() class Meta: model = Brand fields = [ "uuid", "name", "created", "modified", "big_logo", "small_logo", ] def get_small_logo(self, obj: Brand) -> str | None: with suppress(ValueError): return obj.small_logo.url return None def get_big_logo(self, obj: Brand) -> str | None: with suppress(ValueError): return obj.big_logo.url return None class ProductTagDetailSerializer(ModelSerializer): class Meta: model = ProductTag fields = [ "uuid", "tag_name", "name", "created", "modified", ] class ProductImageDetailSerializer(ModelSerializer): image = SerializerMethodField() class Meta: model = ProductImage fields = [ "uuid", "alt", "image", "priority", "created", "modified", ] def get_image(self, obj: ProductImage) -> str: return obj.image.url or "" class AttributeDetailSerializer(ModelSerializer): categories = CategoryDetailSerializer(many=True) group = AttributeGroupDetailSerializer() class Meta: model = Attribute fields = [ "uuid", "name", "value_type", "categories", "group", "created", "modified", ] class AttributeInnerSerializer(ModelSerializer): group = AttributeGroupDetailSerializer() class Meta: model = Attribute fields = [ "uuid", "name", "value_type", "group", "created", "modified", ] class AttributeValueDetailSerializer(ModelSerializer): attribute = AttributeInnerSerializer() class Meta: model = AttributeValue fields = [ "uuid", "attribute", "value", "created", "modified", ] class VendorDetailSerializer(ModelSerializer): class Meta: model = Vendor fields = [ "uuid", "name", "authentication", "markup_percent", "created", "modified", ] class StockDetailSerializer(ModelSerializer): vendor = VendorDetailSerializer() class Meta: model = Stock fields = [ "uuid", "vendor", "price", "purchase_price", "quantity", "sku", "digital_asset", "created", "modified", ] class PromoCodeDetailSerializer(ModelSerializer): class Meta: model = PromoCode fields = [ "uuid", "code", "discount_amount", "discount_percent", "start_time", "end_time", "used_on", "created", "modified", ] class ProductDetailSerializer(ModelSerializer): brand = BrandProductDetailSerializer() category = CategorySimpleSerializer() tags = ProductTagDetailSerializer( many=True, ) images = ProductImageDetailSerializer( many=True, ) attributes = AttributeValueDetailSerializer( many=True, ) rating = SerializerMethodField() price = SerializerMethodField() quantity = SerializerMethodField() feedbacks_count = SerializerMethodField() class Meta: model = Product fields = [ "uuid", "sku", "name", "description", "partnumber", "is_digital", "brand", "category", "feedbacks_count", "quantity", "tags", "slug", "images", "attributes", "rating", "price", "created", "modified", ] def get_rating(self, obj: Product) -> float: return obj.rating def get_price(self, obj: Product) -> float: return obj.price def get_feedbacks_count(self, obj: Product) -> int: return obj.feedbacks_count def get_quantity(self, obj: Product) -> int: return obj.quantity class PromotionDetailSerializer(ModelSerializer): products = ProductDetailSerializer( many=True, ) class Meta: model = Promotion fields = [ "uuid", "name", "discount_percent", "description", "products", "created", "modified", ] class WishlistDetailSerializer(ModelSerializer): products = ProductSimpleSerializer( many=True, ) class Meta: model = Wishlist fields = [ "uuid", "products", "created", "modified", ] class OrderProductDetailSerializer(ModelSerializer): product = ProductDetailSerializer() billing_address = AddressSerializer(read_only=True, required=False) shipping_address = AddressSerializer(read_only=True, required=False) attributes = JSONField(required=False) class Meta: model = OrderProduct fields = [ "uuid", "product", "quantity", "buy_price", "comments", "notifications", "billing_address", "shipping_address", "attributes", "status", "created", "modified", ] class FeedbackDetailSerializer(ModelSerializer): order_product = OrderProductDetailSerializer() class Meta: model = Feedback fields = [ "uuid", "rating", "comment", "order_product", "created", "modified", ] class OrderDetailSerializer(ModelSerializer): promo_code = PromoCodeDetailSerializer() order_products = OrderProductDetailSerializer( many=True, ) total_price = SerializerMethodField(read_only=True) class Meta: model = Order fields = [ "uuid", "status", "total_price", "promo_code", "billing_address", "shipping_address", "buy_time", "order_products", "human_readable_id", "created", "modified", ] def get_total_price(self, obj: Order) -> float: return obj.total_price