Coverage for src/wishlists/serializers.py: 95%

22 statements  

« prev     ^ index     » next       coverage.py v7.9.2, created at 2025-08-04 12:59 +0300

1from django.contrib.contenttypes.models import ContentType 

2from django.db.models import Avg 

3 

4from rest_framework import serializers 

5 

6from src.wishlists.models import Wishlist 

7 

8 

9class WishlistSerializer(serializers.ModelSerializer): 

10 """ 

11 This serializer handles the conversion of Wishlist model instances to JSON 

12 and includes additional computed fields for product information and ratings. 

13 

14 Key Features: 

15 - Includes product information from the related product 

16 - Calculates average rating from approved reviews 

17 - Provides price range (min/max) from inventory items 

18 - Determines if product is sold out based on inventory 

19 - Handles GenericForeignKey relationships 

20 - Provides read-only fields for computed values 

21 """ 

22 

23 # SlugRelatedField for content_type allows us to use the model name as a string 

24 # instead of the numeric ID. 

25 # The 'model' field returns the model name (e.g., 'earwear', 'necklace') 

26 content_type = serializers.SlugRelatedField( 

27 slug_field='model', 

28 queryset=ContentType.objects.all(), 

29 ) 

30 

31 # SerializerMethodField for product_info allows us to include additional 

32 # information about the product without modifying the model 

33 product_info = serializers.SerializerMethodField() 

34 

35 class Meta: 

36 model = Wishlist 

37 # Fields to include in the serialized output 

38 fields = [ 

39 'id', 

40 'user', 

41 'created_at', 

42 'content_type', 

43 'object_id', 

44 'product_info', 

45 ] 

46 # Fields that cannot be modified through the API 

47 read_only_fields = [ 

48 'id', 

49 'created_at', 

50 'user', 

51 'product_info', 

52 ] 

53 

54 def get_product_info(self, obj): 

55 """ 

56 This method retrieves detailed information about the product including: 

57 - Basic product details (id, images, collection, color, stone, metal) 

58 - Average rating from approved reviews 

59 - Price range from inventory items 

60 - Sold out status based on inventory availability 

61 """ 

62 # Get the related product object through GenericForeignKey 

63 product = obj.product 

64 

65 # Calculate average rating from approved reviews only 

66 # Uses Django's aggregation to get the average rating 

67 # Returns 0 if no approved reviews exist 

68 avg_rating = ( 

69 product.review.filter( 

70 approved=True, 

71 ).aggregate( 

72 avg=Avg( 

73 'rating', 

74 ) 

75 )['avg'] 

76 or 0 

77 ) 

78 

79 # Get all inventory items for the product 

80 inventory_items = product.inventory.all() 

81 if inventory_items: 

82 # Calculate price range from all inventory items 

83 prices = [item.price for item in inventory_items] 

84 min_price = min(prices) 

85 max_price = max(prices) 

86 else: 

87 # No inventory items available 

88 min_price = max_price = 0 

89 

90 # Determine if product is sold out 

91 # Checks if any inventory items have quantity > 0 

92 is_sold_out = not inventory_items.filter( 

93 quantity__gt=0, 

94 ).exists() 

95 

96 # Return comprehensive product information 

97 return { 

98 'id': product.id, 

99 'first_image': product.first_image, 

100 'second_image': product.second_image, 

101 'collection__name': product.collection.name, 

102 'color__name': product.color.name, 

103 'stone__name': product.stone.name, 

104 'metal__name': product.metal.name, 

105 'is_sold_out': is_sold_out, 

106 # Round to 2 decimal places 

107 'average_rating': round(avg_rating, 2), 

108 'min_price': min_price, 

109 'max_price': max_price, 

110 }