Coverage for memberships/serializers/membership.py: 94%
237 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-05-08 16:38 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-05-08 16:38 -0600
1from rest_framework import serializers
2from schema import Schema, SchemaError, Optional
3from organizations.models import MembershipType
4from organizations.serializers import MembershipTypeDisplaySerializer
5from segments.models import Segment
6from segments.serializers import DisplaySegmentSerializer
7from products.models import MembershipProduct
8from users.models import User
9from .administration import NestedMembershipPeriodSerializer
10from .contact import MembershipEmailSerializer, MembershipPhoneSerializer, MembershipAddressSerializer
11from ..models import (
12 Affiliation,
13 Membership,
14 Tag,
15 TagCategory,
16 MembershipEmail,
17 MembershipPhone,
18 MembershipAddress,
19 MembershipPeriod,
20)
23class AffiliationSerializer(serializers.ModelSerializer):
24 """
25 Serializer for Affiliation (User-Membership) relation
26 """
28 email = serializers.EmailField(source="user.email")
29 last_login = serializers.DateTimeField(source="user.last_login", read_only=True)
31 class Meta:
32 model = Affiliation
33 read_only_fields = ["id", "email", "last_login"]
34 fields = read_only_fields + ["membership", "name", "is_owner", "is_active"]
35 extra_kwargs = {"membership": {"write_only": True}}
37 def create(self, validated_data):
38 user_data = validated_data.pop("user")
39 validated_data["user"], _ = User.objects.get_or_create(**user_data)
40 return super(AffiliationSerializer, self).create(validated_data)
43class SimplifiedSerializer(serializers.ModelSerializer):
44 """
45 Serializer class for membership with few data
46 """
48 segment = DisplaySegmentSerializer(read_only=True)
49 type_of = MembershipTypeDisplaySerializer(read_only=True)
51 class Meta:
52 model = Membership
53 read_only_fields = ["is_active", "name", "extra_fields", "segment", "type_of"]
54 fields = ["random_slug"] + read_only_fields
55 extra_kwargs = {"random_slug": {"read_only": False, "required": False, "allow_null": True}}
58class MembershipSerializer(serializers.ModelSerializer):
59 """
60 Serializer for User's Membership
61 """
63 email = serializers.EmailField(write_only=True)
64 users = AffiliationSerializer(many=True, required=False, source="affiliation_set.all")
65 segment = DisplaySegmentSerializer(allow_null=True)
66 referred_by = SimplifiedSerializer(required=False, allow_null=True)
67 holder = SimplifiedSerializer(required=False, read_only=True)
68 type_of = MembershipTypeDisplaySerializer()
69 tags = serializers.DictField(write_only=True)
70 contact_emails = MembershipEmailSerializer(write_only=True, many=True, required=False)
71 contact_phones = MembershipPhoneSerializer(write_only=True, many=True, required=False)
72 contact_addresses = MembershipAddressSerializer(write_only=True, many=True, required=False)
73 contact_emails_detail = MembershipEmailSerializer(read_only=True, many=True, source="emails.all")
74 contact_phones_detail = MembershipPhoneSerializer(read_only=True, many=True, source="phones.all")
75 contact_addresses_detail = MembershipAddressSerializer(read_only=True, many=True, source="addresses.all")
76 active_period = NestedMembershipPeriodSerializer(read_only=True)
78 class Meta:
79 model = Membership
80 read_only_fields = ["contact_emails_detail", "contact_phones_detail", "contact_addresses_detail"]
81 fields = [
82 "random_slug",
83 "email",
84 "name",
85 "image",
86 "bio",
87 "is_active",
88 "extra_fields",
89 "tags",
90 "type_of",
91 "segment",
92 "users",
93 "holder",
94 "referred_by",
95 "contact_emails",
96 "contact_phones",
97 "contact_addresses",
98 "register_date",
99 "active_period",
100 ] + read_only_fields
102 def __init__(self, *args, **kwargs):
103 super(MembershipSerializer, self).__init__(*args, **kwargs)
104 organization = self.context.get("organization", None)
105 if organization and organization.membership_fields.exists():
106 self.fields["extra_fields"].required = True
108 def validate_segment(self, item):
109 if item: 109 ↛ exitline 109 didn't return from function 'validate_segment', because the condition on line 109 was never false
110 slug = item.get("random_slug")
111 try:
112 return Segment.objects.get(random_slug=slug, organization=self.context.get("organization"))
113 except Segment.DoesNotExist:
114 raise serializers.ValidationError(f"Segment {slug} does not exists in organization")
116 def validate_type_of(self, item):
117 slug = item.get("random_slug")
118 try:
119 return MembershipType.objects.get(random_slug=slug, organization=self.context.get("organization"))
120 except MembershipType.DoesNotExist:
121 raise serializers.ValidationError(f"Membership Type {slug} does not exists in organization")
123 def validate_referred_by(self, item):
124 if item: 124 ↛ exitline 124 didn't return from function 'validate_referred_by', because the condition on line 124 was never false
125 slug = item.get("random_slug", None)
126 if slug is None:
127 return None
128 try:
129 return Membership.objects.get(random_slug=slug, organization=self.context.get("organization"))
130 except Membership.DoesNotExist:
131 raise serializers.ValidationError(f"Membership {slug} does not exists in organization")
133 def validate_tags(self, tags):
134 organization = self.context.get("organization")
135 try:
136 Schema({Optional(cat.name): [str] for cat in organization.tagcategories.all()}).validate(tags)
137 except SchemaError as e:
138 raise serializers.ValidationError(str(e))
139 objects = []
140 for category_name, tag_list in tags.items():
141 for tag_name in tag_list:
142 try:
143 objects.append(
144 Tag.objects.get(
145 name=tag_name, category__name=category_name, organization=self.context.get("organization")
146 )
147 )
148 except Tag.DoesNotExist:
149 raise serializers.ValidationError(f"Tag {tag_name} does not exists in category {category_name}")
150 return objects
152 def validate_extra_fields(self, extra_fields):
153 """validate extra fields for json field"""
154 organization = self.context.get("organization")
155 fields = organization.membership_fields.all()
156 schema = Schema({f.slug if f.is_required else Optional(f.slug): str for f in fields})
157 try:
158 schema.validate(extra_fields)
159 except SchemaError:
160 raise serializers.ValidationError(
161 f"{organization.name} extra fields are {', '.join(f.slug for f in fields)}"
162 )
163 for f in fields:
164 field_value = extra_fields.get(f.slug, None)
165 if field_value is not None and field_value not in f.choices:
166 raise serializers.ValidationError(f"Valid values for {f.name} are {f.choices}")
167 return extra_fields
169 def create(self, validated_data):
170 """
171 Override to get or create user for affiliation's first user
172 """
173 email = validated_data.pop("email")
174 user, _ = User.objects.get_or_create(email=email, defaults={"name": validated_data.get("name")})
175 contact_emails = validated_data.pop("contact_emails", [])
176 contact_phones = validated_data.pop("contact_phones", [])
177 contact_addresses = validated_data.pop("contact_addresses", [])
178 tags = validated_data.pop("tags", {})
179 membership = super(MembershipSerializer, self).create(validated_data)
180 membership.tags.add(*tags)
181 membership.users.add(user)
182 for contact_email in contact_emails:
183 contact_email["membership"] = membership
184 MembershipEmail.objects.create(**contact_email)
185 for contact_phone in contact_phones:
186 contact_phone["membership"] = membership
187 MembershipPhone.objects.create(**contact_phone)
188 for contact_address in contact_addresses:
189 internal_number = contact_address.pop("internal_number")
190 type_of = contact_address.pop("type_of")
191 membership_address = MembershipAddress()
192 membership_address.membership = membership
193 membership_address.internal_number = internal_number
194 membership_address.type_of = type_of
195 membership_address.address = contact_address
196 membership_address.save()
197 return membership
199 def get_tags(self, instance):
200 tags = instance.tags.all()
201 data = {}
202 for tag in tags:
203 previous = data.get(tag.category.name, [])
204 previous.append(tag.name)
205 data[tag.category.name] = previous
206 return data
208 def to_representation(self, instance):
209 representation = super(MembershipSerializer, self).to_representation(instance)
210 representation["tags"] = self.get_tags(instance)
211 return representation
214class SimpleMembershipProductForPeriodSerializer(serializers.ModelSerializer):
215 """
216 Simple serializer for MembershipProduct used on MembershipPeriodSerializer
217 """
219 next_product = serializers.SerializerMethodField()
221 class Meta:
222 model = MembershipProduct
223 fields = ["name", "next_product"]
225 def get_next_product(self, instance):
226 if instance.next_product:
227 return instance.next_product.name
228 return None
231class MembershipPeriodForRenewalSerializer(serializers.ModelSerializer):
232 """
233 Simple serializer for MemberhsipPeriod used on MembershipRenewalSerializer
234 """
236 product = SimpleMembershipProductForPeriodSerializer()
238 class Meta:
239 model = MembershipPeriod
240 fields = ["period_end", "product"]
243class MembershipRenewalSerializer(serializers.ModelSerializer):
244 """
245 Serializer with basic Membership data for unpaginated version
246 """
248 type_of = serializers.CharField(source="type_of.name")
249 previous_period = serializers.SerializerMethodField()
250 segment = serializers.SerializerMethodField()
252 class Meta:
253 model = Membership
254 fields = ["random_slug", "name", "type_of", "previous_period", "segment"]
256 def get_segment(self, instance):
257 return getattr(getattr(instance, "segment", None), "name", None)
259 def get_previous_period(self, instance):
260 period = instance.products.instance_of(MembershipPeriod).first()
261 if period:
262 return MembershipPeriodForRenewalSerializer(period).data
263 return None
266class MembershipMemberSerializer(MembershipSerializer):
267 """
268 Membership Read Only Serializer for member panel
269 """
271 contact_emails_detail = MembershipEmailSerializer(read_only=True, many=True, source="emails.all")
272 contact_phones_detail = MembershipPhoneSerializer(read_only=True, many=True, source="phones.all")
273 contact_addresses_detail = MembershipAddressSerializer(read_only=True, many=True, source="addresses.all")
275 class Meta(MembershipSerializer.Meta):
276 read_only_fields = [
277 "random_slug",
278 "name",
279 "contact_emails_detail",
280 "contact_phones_detail",
281 "contact_addresses_detail",
282 "users",
283 "is_active",
284 "extra_fields",
285 "type_of",
286 "segment",
287 "holder",
288 "referred_by",
289 "register_date",
290 "active_period",
291 ]
292 fields = read_only_fields + ["image", "bio", "referral_id", "visibility"]
294 def get_tags(self, instance):
295 tags = instance.tags.exclude(category__member_usage=TagCategory.MemberUsageChoices.SECRET)
296 data = {}
297 for tag in tags:
298 previous = data.get(tag.category.name, [])
299 previous.append(tag.name)
300 data[tag.category.name] = previous
301 return data
304class ReferralsSerializer(MembershipSerializer):
305 """
306 Serializer class for referred tree view
307 """
309 direct = serializers.SerializerMethodField()
310 indirect = serializers.SerializerMethodField()
311 direct_count = serializers.SerializerMethodField()
312 indirect_count = serializers.SerializerMethodField()
314 class Meta(MembershipSerializer.Meta):
315 fields = [
316 "random_slug",
317 "is_active",
318 "email",
319 "name",
320 "extra_fields",
321 "segment",
322 "type_of",
323 "direct",
324 "indirect",
325 "direct_count",
326 "indirect_count",
327 ]
328 read_only_fields = fields
330 def get_direct(self, instance):
331 serializer = SimplifiedSerializer(instance.referrals.all(), many=True)
332 return serializer.data
334 def get_indirect(self, instance):
335 qs = instance.get_descendants(include_self=False).exclude(referred_by=instance)
336 serializer = SimplifiedSerializer(qs, many=True)
337 return serializer.data
339 def get_direct_count(self, instance):
340 return instance.referrals.count()
342 def get_indirect_count(self, instance):
343 qs = instance.get_descendants(include_self=False).exclude(referred_by=instance)
344 return qs.count()
347class MemberDirectorySerializer(serializers.ModelSerializer):
348 """
349 Serializer for member data in member directory
350 """
352 contact_emails_detail = serializers.SerializerMethodField(read_only=True)
353 contact_phones_detail = serializers.SerializerMethodField(read_only=True)
354 contact_addresses_detail = serializers.SerializerMethodField(read_only=True)
355 segment = DisplaySegmentSerializer(read_only=True)
356 type_of = MembershipTypeDisplaySerializer(read_only=True)
358 class Meta:
359 model = Membership
360 fields = [
361 "random_slug",
362 "name",
363 "bio",
364 "contact_emails_detail",
365 "contact_phones_detail",
366 "contact_addresses_detail",
367 "segment",
368 "type_of",
369 ]
371 def get_contact_emails_detail(self, instance):
372 qs = instance.emails.filter(in_directory=True)
373 serializer = MembershipEmailSerializer(qs, many=True)
374 return serializer.data
376 def get_contact_phones_detail(self, instance):
377 qs = instance.phones.filter(in_directory=True)
378 serializer = MembershipPhoneSerializer(qs, many=True)
379 return serializer.data
381 def get_contact_addresses_detail(self, instance):
382 qs = instance.addresses.filter(in_directory=True)
383 serializer = MembershipAddressSerializer(qs, many=True)
384 return serializer.data
386 def to_representation(self, instance):
387 representation = super(MemberDirectorySerializer, self).to_representation(instance)
388 if instance.visibility == Membership.VisibilityChoices.RESTRICTED: 388 ↛ 389line 388 didn't jump to line 389, because the condition on line 388 was never true
389 del representation["contact_emails_detail"]
390 del representation["contact_phones_detail"]
391 del representation["contact_addresses_detail"]
392 return representation
395class BasicMembershipSerializer(serializers.ModelSerializer):
396 """
397 Membership Serializer with basic data
398 """
400 class Meta:
401 model = Membership
402 fields = ["random_slug", "name"]