Coverage for products/serializers.py: 87%

152 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-07-13 14:00 -0600

1from django.db.models import Sum 

2from rest_framework import serializers 

3from rest_polymorphic.serializers import PolymorphicSerializer 

4from schema import Schema, SchemaError, Or 

5from discounts.models import VolumeDiscount 

6from events.models import Ticket 

7from segments.models import Segment 

8from memberships.models import Membership, PaymentMethod 

9from organizations.models import MembershipType 

10from organizations.serializers import MembershipTypeSerializer 

11from .models import EventProduct, MembershipProduct, ProductPurchase, ProductPriceVariation 

12 

13 

14PRODUCT_READ_ONLY_FIELDS = ["created_at", "random_slug", "invoice_configuration"] 

15PRODUCT_BASE_FIELDS = ["category", "name", "price", "is_active", "membership_type", "segments", "comment"] 

16 

17 

18class SimpleSegmentSerializer(serializers.ModelSerializer): 

19 """ 

20 Simple Segment serializer with random_slug and name for use in Products 

21 """ 

22 

23 class Meta: 

24 model = Segment 

25 fields = ["random_slug", "name"] 

26 

27 

28class VolumeDiscountSerializer(serializers.ModelSerializer): 

29 """ 

30 Serializer for VolumeDiscount used in EventProductSerializer 

31 """ 

32 

33 class Meta: 

34 model = VolumeDiscount 

35 read_only_fields = ["random_slug", "created_at", "percentage", "max_amount", "min_purchase", "max_purchase"] 

36 fields = read_only_fields 

37 

38 

39class EventProductSerializer(serializers.ModelSerializer): 

40 """ 

41 Serializer for event price object 

42 """ 

43 

44 name = serializers.CharField(required=False) 

45 available_tickets = serializers.SerializerMethodField() 

46 volumediscount_set = VolumeDiscountSerializer(many=True, read_only=True) 

47 

48 class Meta: 

49 model = EventProduct 

50 read_only_fields = PRODUCT_READ_ONLY_FIELDS + ["available_tickets"] 

51 fields = ( 

52 read_only_fields 

53 + PRODUCT_BASE_FIELDS 

54 + ["event", "description", "max_amount", "max_amount_per_purchase", "volumediscount_set"] 

55 ) 

56 extra_kwargs = {"max_amount": {"required": True}} 

57 

58 def get_available_tickets(self, instance): 

59 return instance.max_amount - instance.owners.count() 

60 

61 def validate(self, attrs): 

62 max_amount = attrs.get("max_amount", None) 

63 if max_amount: 63 ↛ 85line 63 didn't jump to line 85, because the condition on line 63 was never false

64 event = self.instance.event if self.instance else attrs.get("event") 

65 sold_tickets = self.instance.owners.count() if self.instance else 0 

66 reserved_tickets = ( 

67 event.products.exclude(pk=getattr(self.instance, "pk", None)) 

68 .aggregate(reserved_tickets=Sum("max_amount")) 

69 .get("reserved_tickets") 

70 or 0 

71 ) 

72 remaining_tickets = event.max_tickets - reserved_tickets 

73 if remaining_tickets == 0: 73 ↛ 74line 73 didn't jump to line 74, because the condition on line 73 was never true

74 raise serializers.ValidationError({"max_amount": "No hay más boletos disponibles para reservar"}) 

75 elif remaining_tickets < max_amount: 75 ↛ 76line 75 didn't jump to line 76, because the condition on line 75 was never true

76 raise serializers.ValidationError( 

77 {"max_amount": f"Solo hay {remaining_tickets} boletos disponibles para reservar"} 

78 ) 

79 elif max_amount < sold_tickets: 79 ↛ 80line 79 didn't jump to line 80, because the condition on line 79 was never true

80 raise serializers.ValidationError( 

81 { 

82 "max_amount": f"Ya se vendieron {sold_tickets} boletos, no se puede reducir el monto a menos de esta cantidad" 

83 } 

84 ) 

85 return attrs 

86 

87 def to_representation(self, instance): 

88 representation = super(EventProductSerializer, self).to_representation(instance) 

89 membership_type = representation.get("membership_type") 

90 objects = MembershipType.objects.filter(random_slug__in=membership_type) 

91 serializer = MembershipTypeSerializer(objects, many=True) 

92 representation["membership_type"] = serializer.data 

93 segments_slugs = representation.get("segments") 

94 segments = Segment.objects.filter(random_slug__in=segments_slugs) 

95 serializer = SimpleSegmentSerializer(instance=segments, many=True) 

96 representation["segments"] = serializer.data 

97 return representation 

98 

99 

100class MembershipProductSerializer(serializers.ModelSerializer): 

101 """ 

102 Serializer for membership products 

103 """ 

104 

105 class Meta: 

106 model = MembershipProduct 

107 read_only_fields = PRODUCT_READ_ONLY_FIELDS 

108 fields = ( 

109 ["installable_from", "installable_until", "renewal_type", "duration", "allow_deferred_payments"] 

110 + PRODUCT_BASE_FIELDS 

111 + read_only_fields 

112 ) 

113 

114 def to_representation(self, instance): 

115 representation = super(MembershipProductSerializer, self).to_representation(instance) 

116 segments_slugs = representation.get("segments") 

117 segments = Segment.objects.filter(random_slug__in=segments_slugs) 

118 serializer = SimpleSegmentSerializer(instance=segments, many=True) 

119 representation["segments"] = serializer.data 

120 membership_type = representation.get("membership_type") 

121 objects = MembershipType.objects.filter(random_slug__in=membership_type) 

122 serializer = MembershipTypeSerializer(objects, many=True) 

123 representation["membership_type"] = serializer.data 

124 return representation 

125 

126 

127class ProductPolymorphicSerializer(PolymorphicSerializer): 

128 """ 

129 Polymorphic serializer for Product objects and sub objects 

130 """ 

131 

132 model_serializer_mapping = { 

133 EventProduct: EventProductSerializer, 

134 MembershipProduct: MembershipProductSerializer, 

135 } 

136 

137 

138class BasicEventProductSerializer(serializers.ModelSerializer): 

139 """ 

140 EventProduct Serializer with basic data for polymorphic 

141 """ 

142 

143 class Meta: 

144 model = EventProduct 

145 fields = ["random_slug", "name"] 

146 

147 

148class BasicMembershipProductSerializer(serializers.ModelSerializer): 

149 """ 

150 MembershipProduct Serializer with basic data for polymorphic 

151 """ 

152 

153 class Meta: 

154 model = MembershipProduct 

155 fields = ["random_slug", "name"] 

156 

157 

158class BasicProductPolymorphicSerializer(PolymorphicSerializer): 

159 """ 

160 Polymorphic serializer for Product objects and sub objects with basic data 

161 """ 

162 

163 model_serializer_mapping = { 

164 EventProduct: BasicEventProductSerializer, 

165 MembershipProduct: BasicMembershipProductSerializer, 

166 } 

167 

168 

169class ProductPurchaseSerializer(serializers.ModelSerializer): 

170 """ 

171 Serializer for Product Purchase Creation 

172 """ 

173 

174 class Meta: 

175 model = ProductPurchase 

176 read_only_fields = ["price_breakdown", "total_price"] 

177 fields = ["membership", "product", "quantity", "installments", "owner_relation"] + read_only_fields 

178 

179 def validate_owner_relation(self, owner_relation): 

180 schema = Schema([{Or("membership", "email"): str, "quantity": int}]) 

181 try: 

182 schema.validate(owner_relation) 

183 return owner_relation 

184 except SchemaError: 

185 raise serializers.ValidationError( 

186 "owner_relation field must be formatted as {'membership': str, 'quantity': int}" 

187 ) 

188 

189 def validate(self, attrs): 

190 membership = attrs.get("membership") 

191 owner_relation = attrs.get("owner_relation", []) 

192 if membership: 192 ↛ 194line 192 didn't jump to line 194, because the condition on line 192 was never false

193 subsidiaries_slugs = membership.subsidiaries.values_list("random_slug", flat=True) 

194 total_quantity = 0 

195 for object in owner_relation: 

196 if membership: 196 ↛ 200line 196 didn't jump to line 200, because the condition on line 196 was never false

197 slug = object.get("membership") 

198 if slug not in subsidiaries_slugs and slug != membership.random_slug: 

199 raise serializers.ValidationError(f"Membership {slug} is not a subsidiary of {membership.name}") 

200 total_quantity += object.get("quantity") 

201 selected_quantity = attrs.get("quantity") 

202 if total_quantity > selected_quantity: 

203 raise serializers.ValidationError( 

204 f"There are more assigned products ({total_quantity}) than selected quantity ({selected_quantity})" 

205 ) 

206 return attrs 

207 

208 

209class BulkProductPurchaseSerializer(serializers.Serializer): 

210 """ 

211 Serializers for bulk Product Purchase 

212 """ 

213 

214 product = serializers.PrimaryKeyRelatedField(queryset=MembershipProduct.objects.all()) 

215 memberships = serializers.PrimaryKeyRelatedField(many=True, queryset=Membership.objects.all(), write_only=True) 

216 

217 class Meta: 

218 fields = ["product", "memberships"] 

219 

220 

221class TicketOwnerSerializer(serializers.ModelSerializer): 

222 """ 

223 Serializer for Ticket Owner 

224 """ 

225 

226 class Meta: 

227 model = Ticket 

228 fields = ["first_name", "last_name", "email", "phone"] 

229 

230 

231class MemberEventProductPurchaseSerializer(serializers.Serializer): 

232 """ 

233 Serializer for Member Event Product Purchase Creation 

234 """ 

235 

236 membership = serializers.PrimaryKeyRelatedField(queryset=Membership.objects.all()) 

237 email = serializers.EmailField(required=True) 

238 product = serializers.PrimaryKeyRelatedField(queryset=EventProduct.objects.all()) 

239 quantity = serializers.IntegerField() 

240 payment_method = serializers.PrimaryKeyRelatedField(queryset=PaymentMethod.objects.all()) 

241 ticket_owner = TicketOwnerSerializer(many=True) 

242 

243 

244class PublicEventProductPurchaseSerializer(serializers.Serializer): 

245 """ 

246 Serializer for Public Event Product Purchase Creation 

247 """ 

248 

249 membership = serializers.PrimaryKeyRelatedField(queryset=Membership.objects.all()) 

250 email = serializers.EmailField(required=True) 

251 product = serializers.PrimaryKeyRelatedField(queryset=EventProduct.objects.all()) 

252 quantity = serializers.IntegerField() 

253 ticket_owner = TicketOwnerSerializer(many=True) 

254 

255 

256class ProductPriceVariationSerializer(serializers.ModelSerializer): 

257 """ 

258 Serializer for ProductPriceVariation 

259 """ 

260 

261 class Meta: 

262 model = ProductPriceVariation 

263 read_only_fields = ["random_slug", "created_at", "updated_at"] 

264 fields = read_only_fields + [ 

265 "product", 

266 "price_variation", 

267 "membership_type", 

268 "segments", 

269 "is_active", 

270 "min_amount", 

271 "max_amount", 

272 "comment", 

273 ] 

274 

275 def to_representation(self, instance): 

276 representation = super(ProductPriceVariationSerializer, self).to_representation(instance) 

277 segments_slugs = representation.get("segments") 

278 segments = Segment.objects.filter(random_slug__in=segments_slugs) 

279 serializer = SimpleSegmentSerializer(instance=segments, many=True) 

280 representation["segments"] = serializer.data 

281 membership_type = representation.get("membership_type") 

282 objects = MembershipType.objects.filter(random_slug__in=membership_type) 

283 serializer = MembershipTypeSerializer(objects, many=True) 

284 representation["membership_type"] = serializer.data 

285 return representation