Coverage for website/sales/api/v2/admin/serializers/order.py: 90.91%

117 statements  

« prev     ^ index     » next       coverage.py v7.6.7, created at 2025-08-14 10:31 +0000

1from django.core.exceptions import ObjectDoesNotExist 

2from django.utils.encoding import smart_str 

3 

4from rest_framework import serializers 

5from rest_framework.exceptions import ValidationError 

6from rest_framework.settings import api_settings 

7 

8from members.api.v2.serializers.member import MemberSerializer 

9from payments.api.v2.serializers import PaymentSerializer 

10from payments.api.v2.serializers.payment_amount import PaymentAmountSerializer 

11from sales.models.order import Order, OrderItem 

12from sales.models.product import ProductListItem 

13 

14 

15class ProductNameRelatedField(serializers.SlugRelatedField): 

16 def get_queryset(self): 

17 shift = self.root.context.get("shift", None) 

18 if shift is None: 

19 shift = self.root.instance.shift 

20 return ProductListItem.objects.filter(product_list=shift.product_list) 

21 

22 def to_internal_value(self, data): 

23 if isinstance(data, ProductListItem): 23 ↛ 24line 23 didn't jump to line 24 because the condition on line 23 was never true

24 return data 

25 

26 queryset = self.get_queryset() 

27 try: 

28 return queryset.get(product__name=data) 

29 except ObjectDoesNotExist: 

30 self.fail( 

31 "does_not_exist", slug_name=self.slug_field, value=smart_str(data) 

32 ) 

33 except (TypeError, ValueError): 

34 self.fail("invalid") 

35 return None 

36 

37 def to_representation(self, obj): 

38 return obj.product.name 

39 

40 

41class OrderItemSerializer(serializers.ModelSerializer): 

42 """Serializer for order items.""" 

43 

44 class Meta: 

45 model = OrderItem 

46 fields = ("product", "amount", "total") 

47 read_only_fields = ("total",) 

48 

49 product = ProductNameRelatedField("product") 

50 

51 total = PaymentAmountSerializer(read_only=True) 

52 

53 def get_fields(self): 

54 fields = super().get_fields() 

55 request = self.context.get("request", None) 

56 if request and request.user and request.user.has_perm("sales.custom_prices"): 

57 fields["total"].read_only = False 

58 if request and request.method == "GET": 

59 fields["product"] = serializers.CharField(source="product_name") 

60 return fields 

61 

62 def create(self, validated_data, **kwargs): 

63 order = self.context["order"] 

64 item = OrderItem.objects.create(order=order, **validated_data) 

65 return item 

66 

67 def update(self, instance, validated_data, **kwargs): 

68 order = self.context["order"] 

69 instance.order = order 

70 instance.total = None # Always recalculate the total amount if updating using API (note the difference from the model that only recalculates if the total is None, to deal with historic data and allow for special discounts) 

71 try: 

72 super().update(instance, validated_data) 

73 except ValueError as e: 

74 raise ValidationError({api_settings.NON_FIELD_ERRORS_KEY: [e]}) from e 

75 return instance 

76 

77 

78class OrderSerializer(serializers.ModelSerializer): 

79 """Serializer for orders.""" 

80 

81 class Meta: 

82 model = Order 

83 fields = ( 

84 "pk", 

85 "shift", 

86 "created_at", 

87 "order_items", 

88 "order_description", 

89 "age_restricted", 

90 "subtotal", 

91 "discount", 

92 "total_amount", 

93 "num_items", 

94 "payment", 

95 "payer", 

96 "payment_url", 

97 ) 

98 read_only_fields = ( 

99 "pk", 

100 "created_at", 

101 "payment", 

102 "num_items", 

103 "order_description", 

104 ) 

105 

106 shift = serializers.PrimaryKeyRelatedField(read_only=True) 

107 

108 age_restricted = serializers.BooleanField(read_only=True) 

109 

110 order_item_serializer_class = OrderItemSerializer 

111 

112 order_items = order_item_serializer_class(many=True, required=False) 

113 

114 subtotal = PaymentAmountSerializer(read_only=True) 

115 

116 discount = PaymentAmountSerializer(read_only=True) 

117 

118 total_amount = PaymentAmountSerializer(read_only=True) 

119 

120 payment = PaymentSerializer(read_only=True) 

121 

122 payer = MemberSerializer(read_only=True, detailed=False) 

123 

124 payment_url = serializers.URLField(read_only=True) 

125 

126 def get_fields(self): 

127 fields = super().get_fields() 

128 request = self.context.get("request", None) 

129 if request and request.user and request.user.has_perm("sales.custom_prices"): 

130 try: 

131 fields["discount"].read_only = False 

132 except KeyError: 

133 pass 

134 return fields 

135 

136 def create(self, validated_data): 

137 shift = self.context["shift"] 

138 if "order_items" in validated_data: 

139 items_data = validated_data.pop("order_items") 

140 order = Order.objects.create(shift=shift, **validated_data) 

141 for item_data in items_data: 

142 OrderItem.objects.create(order=order, **item_data) 

143 

144 order.refresh_from_db() 

145 if order.num_items == 0: 145 ↛ 146line 145 didn't jump to line 146 because the condition on line 145 was never true

146 order.delete() # Delete if the order has no products anymore 

147 raise ValidationError("You cannot order 0 products") 

148 

149 else: 

150 order = Order.objects.create(shift=shift, **validated_data) 

151 self.is_valid(raise_exception=True) 

152 return order 

153 

154 def update(self, instance, validated_data): 

155 # Update the order items for an order 

156 if "order_items" in validated_data: 

157 items_data = validated_data.pop("order_items") 

158 current_items = list(instance.order_items.all()) 

159 

160 # Overwrite all existing order items by the newly provided ones 

161 for item_data in items_data: 

162 if len(current_items) > 0: 

163 item = current_items.pop(0) 

164 self.order_item_serializer_class( 

165 item, context={"order": instance} 

166 ).update(item, item_data) 

167 else: 

168 # Create new order items if required 

169 self.order_item_serializer_class( 

170 context={"order": instance} 

171 ).create(validated_data=item_data) 

172 

173 # Delete all order items that we have not updated 

174 for i in current_items: 

175 i.delete() 

176 

177 # Update other fields of the order as default 

178 instance = super().update(instance, validated_data) 

179 instance = Order.objects.get( 

180 pk=instance.pk 

181 ) # refresh from database to update queryable properties 

182 

183 if instance.num_items == 0: 183 ↛ 184line 183 didn't jump to line 184 because the condition on line 183 was never true

184 instance.delete() # Delete if the order has no products anymore 

185 raise ValidationError("You cannot order 0 products") 

186 

187 return instance 

188 

189 

190class OrderListSerializer(OrderSerializer): 

191 class Meta: 

192 model = Order 

193 fields = ( 

194 "pk", 

195 "created_at", 

196 "total_amount", 

197 "num_items", 

198 ) 

199 read_only_fields = ( 

200 "pk", 

201 "created_at", 

202 "total_amount", 

203 "num_items", 

204 )