Coverage for src/orders/serializers.py: 91%
65 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-08-04 12:59 +0300
« prev ^ index » next coverage.py v7.9.2, created at 2025-08-04 12:59 +0300
1from rest_framework import serializers
3from src.orders.constants import CardFieldLengths
4from src.orders.models import Order
5from src.orders.services import OrderService, PaymentValidationService
6from src.common.mixins import InventoryMixin
9class OrderSerializer(serializers.ModelSerializer):
10 """
11 Serializer for the Order model.
13 Key Features:
14 - Serializes order details, including inventory, status, and user.
15 - Excludes size and quantity fields to ensure only unique products are shown in order history.
16 - Includes product_info (details about the product from inventory, with size removed).
17 - Includes product_content_type and product_object_id for review integration (used by frontend to submit reviews for the correct product).
18 - Calculates total price for the order item.
19 """
21 inventory = serializers.PrimaryKeyRelatedField(
22 queryset=Order._meta.get_field('inventory').related_model.objects.all()
23 )
24 product_info = serializers.SerializerMethodField()
25 total_price = serializers.SerializerMethodField()
26 status_display = serializers.CharField(
27 source='get_status_display', read_only=True
28 )
29 product_content_type = serializers.SerializerMethodField()
30 product_object_id = serializers.SerializerMethodField()
32 class Meta:
33 model = Order
34 fields = [
35 'id',
36 'user',
37 'status',
38 'status_display',
39 'created_at',
40 'order_group',
41 'inventory',
42 'product_info',
43 'total_price',
44 'product_content_type',
45 'product_object_id',
46 ]
47 read_only_fields = [
48 'id',
49 'created_at',
50 'user',
51 'order_group',
52 'product_info',
53 'total_price',
54 'status_display',
55 'product_content_type',
56 'product_object_id',
57 ]
58 depth = 3
60 def get_product_info(self, obj):
61 """
62 Returns product details for the order item, using the related inventory's product.
63 Removes size from the product_info if present.
64 """
65 info = InventoryMixin.get_product_info(obj)
66 if isinstance(info, dict) and 'size' in info:
67 info.pop('size')
69 return info
71 def get_total_price(self, obj):
72 """
73 Calculates the total price for this order item (price * quantity).
74 """
75 return InventoryMixin.get_total_price_per_product(obj)
77 def get_product_content_type(self, obj):
78 """
79 Returns the model name of the related product (e.g., 'earwear'), used for review integration.
80 """
81 inventory = obj.inventory
82 if not inventory:
83 return None
85 product = getattr(inventory, 'product', None)
86 if not product:
87 return None
89 return product._meta.model_name
91 def get_product_object_id(self, obj):
92 """
93 Returns the primary key of the related product, used for review integration.
94 """
95 inventory = obj.inventory
96 if not inventory:
97 return None
99 product = getattr(inventory, 'product', None)
100 if not product:
101 return None
103 return product.id
106class OrderGroupSerializer(serializers.Serializer):
107 """
108 Serializer for a group of orders (products purchased together in one checkout).
109 Aggregates order info, total price, and product details for the group.
110 """
112 order_group = serializers.UUIDField(read_only=True)
113 status = serializers.CharField(read_only=True)
114 status_display = serializers.CharField(read_only=True)
115 created_at = serializers.DateTimeField(read_only=True)
116 total_price = serializers.FloatField(read_only=True)
117 total_items = serializers.IntegerField(read_only=True)
118 products = OrderSerializer(many=True, read_only=True)
120 def to_representation(self, instance):
121 """
122 Customizes the output format for grouped orders.
123 """
124 if isinstance(instance, dict) and 'orders' in instance:
125 orders = instance['orders']
126 if not orders:
127 return {}
129 first_order = orders[0]
130 total_items = sum(order.quantity for order in orders)
132 return {
133 'order_group': str(first_order.order_group),
134 'status': first_order.status,
135 'status_display': first_order.get_status_display(),
136 'created_at': first_order.created_at,
137 'total_price': OrderService.calculate_order_group_total(
138 str(first_order.order_group), first_order.user
139 ),
140 'total_items': total_items,
141 'products': OrderSerializer(orders, many=True).data,
142 }
144 return super().to_representation(instance)
147class OrderCreateSerializer(serializers.Serializer):
148 """
149 Serializer for creating an order from payment data.
150 Validates credit card and payment info before processing the order.
151 """
153 card_number = serializers.CharField(
154 max_length=CardFieldLengths.CARD_NUMBER_MAX_LENGTH
155 )
156 card_holder_name = serializers.CharField(
157 max_length=CardFieldLengths.CARD_HOLDER_NAME_MAX_LENGTH
158 )
159 expiry_date = serializers.CharField(
160 max_length=CardFieldLengths.EXPIRY_DATE_MAX_LENGTH
161 )
162 cvv = serializers.CharField(max_length=CardFieldLengths.CVV_MAX_LENGTH)
164 def validate(self, data):
165 """
166 Uses PaymentValidationService to check all payment fields for validity.
167 """
168 PaymentValidationService.validate_payment_data(
169 {
170 'card_number': data.get('card_number'),
171 'card_holder_name': data.get('card_holder_name'),
172 'cvv': data.get('cvv'),
173 'expiry_date': data.get('expiry_date'),
174 }
175 )
177 return data