Coverage for products/serializers.py: 87%
152 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-13 14:00 -0600
« 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
14PRODUCT_READ_ONLY_FIELDS = ["created_at", "random_slug", "invoice_configuration"]
15PRODUCT_BASE_FIELDS = ["category", "name", "price", "is_active", "membership_type", "segments", "comment"]
18class SimpleSegmentSerializer(serializers.ModelSerializer):
19 """
20 Simple Segment serializer with random_slug and name for use in Products
21 """
23 class Meta:
24 model = Segment
25 fields = ["random_slug", "name"]
28class VolumeDiscountSerializer(serializers.ModelSerializer):
29 """
30 Serializer for VolumeDiscount used in EventProductSerializer
31 """
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
39class EventProductSerializer(serializers.ModelSerializer):
40 """
41 Serializer for event price object
42 """
44 name = serializers.CharField(required=False)
45 available_tickets = serializers.SerializerMethodField()
46 volumediscount_set = VolumeDiscountSerializer(many=True, read_only=True)
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}}
58 def get_available_tickets(self, instance):
59 return instance.max_amount - instance.owners.count()
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
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
100class MembershipProductSerializer(serializers.ModelSerializer):
101 """
102 Serializer for membership products
103 """
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 )
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
127class ProductPolymorphicSerializer(PolymorphicSerializer):
128 """
129 Polymorphic serializer for Product objects and sub objects
130 """
132 model_serializer_mapping = {
133 EventProduct: EventProductSerializer,
134 MembershipProduct: MembershipProductSerializer,
135 }
138class BasicEventProductSerializer(serializers.ModelSerializer):
139 """
140 EventProduct Serializer with basic data for polymorphic
141 """
143 class Meta:
144 model = EventProduct
145 fields = ["random_slug", "name"]
148class BasicMembershipProductSerializer(serializers.ModelSerializer):
149 """
150 MembershipProduct Serializer with basic data for polymorphic
151 """
153 class Meta:
154 model = MembershipProduct
155 fields = ["random_slug", "name"]
158class BasicProductPolymorphicSerializer(PolymorphicSerializer):
159 """
160 Polymorphic serializer for Product objects and sub objects with basic data
161 """
163 model_serializer_mapping = {
164 EventProduct: BasicEventProductSerializer,
165 MembershipProduct: BasicMembershipProductSerializer,
166 }
169class ProductPurchaseSerializer(serializers.ModelSerializer):
170 """
171 Serializer for Product Purchase Creation
172 """
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
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 )
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
209class BulkProductPurchaseSerializer(serializers.Serializer):
210 """
211 Serializers for bulk Product Purchase
212 """
214 product = serializers.PrimaryKeyRelatedField(queryset=MembershipProduct.objects.all())
215 memberships = serializers.PrimaryKeyRelatedField(many=True, queryset=Membership.objects.all(), write_only=True)
217 class Meta:
218 fields = ["product", "memberships"]
221class TicketOwnerSerializer(serializers.ModelSerializer):
222 """
223 Serializer for Ticket Owner
224 """
226 class Meta:
227 model = Ticket
228 fields = ["first_name", "last_name", "email", "phone"]
231class MemberEventProductPurchaseSerializer(serializers.Serializer):
232 """
233 Serializer for Member Event Product Purchase Creation
234 """
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)
244class PublicEventProductPurchaseSerializer(serializers.Serializer):
245 """
246 Serializer for Public Event Product Purchase Creation
247 """
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)
256class ProductPriceVariationSerializer(serializers.ModelSerializer):
257 """
258 Serializer for ProductPriceVariation
259 """
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 ]
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