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

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) 

21 

22 

23class AffiliationSerializer(serializers.ModelSerializer): 

24 """ 

25 Serializer for Affiliation (User-Membership) relation 

26 """ 

27 

28 email = serializers.EmailField(source="user.email") 

29 last_login = serializers.DateTimeField(source="user.last_login", read_only=True) 

30 

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}} 

36 

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) 

41 

42 

43class SimplifiedSerializer(serializers.ModelSerializer): 

44 """ 

45 Serializer class for membership with few data 

46 """ 

47 

48 segment = DisplaySegmentSerializer(read_only=True) 

49 type_of = MembershipTypeDisplaySerializer(read_only=True) 

50 

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}} 

56 

57 

58class MembershipSerializer(serializers.ModelSerializer): 

59 """ 

60 Serializer for User's Membership 

61 """ 

62 

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) 

77 

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 

101 

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 

107 

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") 

115 

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") 

122 

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") 

132 

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 

151 

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 

168 

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 

198 

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 

207 

208 def to_representation(self, instance): 

209 representation = super(MembershipSerializer, self).to_representation(instance) 

210 representation["tags"] = self.get_tags(instance) 

211 return representation 

212 

213 

214class SimpleMembershipProductForPeriodSerializer(serializers.ModelSerializer): 

215 """ 

216 Simple serializer for MembershipProduct used on MembershipPeriodSerializer 

217 """ 

218 

219 next_product = serializers.SerializerMethodField() 

220 

221 class Meta: 

222 model = MembershipProduct 

223 fields = ["name", "next_product"] 

224 

225 def get_next_product(self, instance): 

226 if instance.next_product: 

227 return instance.next_product.name 

228 return None 

229 

230 

231class MembershipPeriodForRenewalSerializer(serializers.ModelSerializer): 

232 """ 

233 Simple serializer for MemberhsipPeriod used on MembershipRenewalSerializer 

234 """ 

235 

236 product = SimpleMembershipProductForPeriodSerializer() 

237 

238 class Meta: 

239 model = MembershipPeriod 

240 fields = ["period_end", "product"] 

241 

242 

243class MembershipRenewalSerializer(serializers.ModelSerializer): 

244 """ 

245 Serializer with basic Membership data for unpaginated version 

246 """ 

247 

248 type_of = serializers.CharField(source="type_of.name") 

249 previous_period = serializers.SerializerMethodField() 

250 segment = serializers.SerializerMethodField() 

251 

252 class Meta: 

253 model = Membership 

254 fields = ["random_slug", "name", "type_of", "previous_period", "segment"] 

255 

256 def get_segment(self, instance): 

257 return getattr(getattr(instance, "segment", None), "name", None) 

258 

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 

264 

265 

266class MembershipMemberSerializer(MembershipSerializer): 

267 """ 

268 Membership Read Only Serializer for member panel 

269 """ 

270 

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") 

274 

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"] 

293 

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 

302 

303 

304class ReferralsSerializer(MembershipSerializer): 

305 """ 

306 Serializer class for referred tree view 

307 """ 

308 

309 direct = serializers.SerializerMethodField() 

310 indirect = serializers.SerializerMethodField() 

311 direct_count = serializers.SerializerMethodField() 

312 indirect_count = serializers.SerializerMethodField() 

313 

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 

329 

330 def get_direct(self, instance): 

331 serializer = SimplifiedSerializer(instance.referrals.all(), many=True) 

332 return serializer.data 

333 

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 

338 

339 def get_direct_count(self, instance): 

340 return instance.referrals.count() 

341 

342 def get_indirect_count(self, instance): 

343 qs = instance.get_descendants(include_self=False).exclude(referred_by=instance) 

344 return qs.count() 

345 

346 

347class MemberDirectorySerializer(serializers.ModelSerializer): 

348 """ 

349 Serializer for member data in member directory 

350 """ 

351 

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) 

357 

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 ] 

370 

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 

375 

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 

380 

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 

385 

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 

393 

394 

395class BasicMembershipSerializer(serializers.ModelSerializer): 

396 """ 

397 Membership Serializer with basic data 

398 """ 

399 

400 class Meta: 

401 model = Membership 

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