Coverage for apps/campaigns/serializers/fan.py: 66%

185 statements  

« prev     ^ index     » next       coverage.py v6.4.4, created at 2023-09-29 12:54 -0600

1from django.db import transaction 

2from django.utils import timezone 

3from rest_framework import serializers 

4from rest_polymorphic.serializers import PolymorphicSerializer 

5 

6from apps.organizations.serializers import BasicOrganizationSerializer, OrganizationUploadPolymorphicSerializer 

7from apps.users.models import Employee, SuperUser 

8from apps.users.serializers import UserPolymorphicSerializer 

9from base.serializers import ToRepresentationMixin 

10 

11from ..models import ( 

12 CampaignClaim, 

13 GeolocationCampaign, 

14 GeolocationCampaignClaim, 

15 QuizzCampaign, 

16 QuizzCampaignClaim, 

17 QuizzCampaignClaimAnswer, 

18 QuizzCampaignQuestion, 

19 QuizzCampaignQuestionAnswer, 

20 SubmitCampaign, 

21 SubmitCampaignClaim, 

22 VideoCampaign, 

23 VideoCampaignClaim, 

24) 

25from ..utils import distance_between_lat_long 

26 

27BASIC_CAMPAIGN_FIELDS = ["random_slug", "organization", "name", "image"] 

28CAMPAIGN_BASE_READ_ONLY_FIELDS = ["random_slug", "created_at", "slug"] 

29CAMPAIGN_BASE_FIELDS = [ 

30 "name", 

31 "short_description", 

32 "description", 

33 "valid_from", 

34 "valid_to", 

35 "image", 

36 "value", 

37 "status", 

38 "author", 

39 "total_number_of_claims", 

40 "total_number_of_claims_per_fan", 

41 "can_be_claimed", 

42 "total_times_claimed", 

43 "times_claimed", 

44 "amount_claimed", 

45] 

46 

47 

48class BaseCampaignSerializer(serializers.ModelSerializer): 

49 """ 

50 Base Campaign Serializer 

51 """ 

52 

53 image = OrganizationUploadPolymorphicSerializer() 

54 author = UserPolymorphicSerializer() 

55 can_be_claimed = serializers.SerializerMethodField() 

56 total_times_claimed = serializers.SerializerMethodField() 

57 times_claimed = serializers.SerializerMethodField() 

58 amount_claimed = serializers.SerializerMethodField() 

59 

60 def get_can_be_claimed(self, instance): 

61 user = self.context.get("request").user 

62 today = timezone.datetime.today().date() 

63 

64 if instance.valid_from > today or instance.valid_to < today: 

65 return False 

66 

67 if user.is_anonymous or isinstance(user, (Employee, SuperUser)): 

68 return False 

69 

70 if instance.total_number_of_claims_per_fan is not None: 

71 if user.campaigns.filter(campaign=instance).count() >= instance.total_number_of_claims_per_fan: 

72 return False 

73 

74 if instance.total_number_of_claims is not None: 

75 if instance.participants.count() >= instance.total_number_of_claims: 

76 return False 

77 

78 return True 

79 

80 def get_times_claimed(self, instance): 

81 user = self.context.get("request").user 

82 

83 if user.is_anonymous: 

84 return 0 

85 

86 return instance.participants.filter(fan=user).count() 

87 

88 def get_total_times_claimed(self, instance): 

89 return instance.participants.count() 

90 

91 def get_amount_claimed(self, instance): 

92 user = self.context.get("request").user 

93 

94 if user.is_anonymous: 

95 return 0 

96 

97 return instance.participants.filter(fan=user).count() * instance.value 

98 

99 

100class FanVideoCampaignSerializer(BaseCampaignSerializer): 

101 """ 

102 Serializer for VideoCampaign 

103 """ 

104 

105 class Meta: 

106 model = VideoCampaign 

107 read_only_fields = CAMPAIGN_BASE_READ_ONLY_FIELDS 

108 fields = read_only_fields + CAMPAIGN_BASE_FIELDS + ["video_link", "watch_seconds_for_completion"] 

109 

110 

111class FanGeolocationCampaignSerializer(BaseCampaignSerializer): 

112 """ 

113 Serializer for Geolocation Campaign 

114 """ 

115 

116 class Meta: 

117 model = GeolocationCampaign 

118 read_only_fields = CAMPAIGN_BASE_READ_ONLY_FIELDS 

119 fields = read_only_fields + CAMPAIGN_BASE_FIELDS + ["address", "latitude", "longitude", "radius"] 

120 

121 

122class FanSubmitCampaignSerializer(BaseCampaignSerializer): 

123 """ 

124 Serializer for Submit Campaign 

125 """ 

126 

127 class Meta: 

128 model = SubmitCampaign 

129 read_only_fields = CAMPAIGN_BASE_READ_ONLY_FIELDS 

130 fields = read_only_fields + CAMPAIGN_BASE_FIELDS + ["is_approved"] 

131 

132 

133class QuizzCampaignQuestionAnswerSerializer(serializers.ModelSerializer): 

134 """ 

135 Serializer for Quizz Campaign Question Answer 

136 """ 

137 

138 class Meta: 

139 model = QuizzCampaignQuestionAnswer 

140 read_only_fields = ["random_slug"] 

141 fields = read_only_fields + ["answer"] 

142 

143 

144class QuizzCampaignQuestionSerializer(serializers.ModelSerializer): 

145 """ 

146 Serializer for Quiz Campaign Question 

147 """ 

148 

149 answers = QuizzCampaignQuestionAnswerSerializer(many=True) 

150 

151 class Meta: 

152 model = QuizzCampaignQuestion 

153 read_only_fields = ["random_slug"] 

154 fields = read_only_fields + ["question", "type_of", "answers"] 

155 

156 

157class FanQuizzCampaignSerializer(BaseCampaignSerializer): 

158 """ 

159 Serializer for Quizz Campaign 

160 """ 

161 

162 questions = QuizzCampaignQuestionSerializer(many=True) 

163 

164 class Meta: 

165 model = QuizzCampaign 

166 read_only_fields = CAMPAIGN_BASE_READ_ONLY_FIELDS 

167 fields = read_only_fields + CAMPAIGN_BASE_FIELDS + ["correct_answers_for_approval", "questions"] 

168 

169 

170class FanCampaignPolymorphicSerializer(PolymorphicSerializer): 

171 """ 

172 Polymorphic serializer for Campaign objects 

173 """ 

174 

175 model_serializer_mapping = { 

176 VideoCampaign: FanVideoCampaignSerializer, 

177 GeolocationCampaign: FanGeolocationCampaignSerializer, 

178 SubmitCampaign: FanSubmitCampaignSerializer, 

179 QuizzCampaign: FanQuizzCampaignSerializer, 

180 } 

181 

182 

183class BaseBasicCampaignSerializer(ToRepresentationMixin): 

184 """ 

185 Base serializer for Campaign with minimum data 

186 """ 

187 

188 REPRESENTATION_FIELDS = [ 

189 ("organization", BasicOrganizationSerializer, False), 

190 ("image", OrganizationUploadPolymorphicSerializer, False), 

191 ] 

192 

193 

194class BasicVideoCampaignSerializer(BaseBasicCampaignSerializer): 

195 """ 

196 Serializer for VideoCampaign 

197 """ 

198 

199 class Meta: 

200 model = VideoCampaign 

201 fields = BASIC_CAMPAIGN_FIELDS 

202 

203 

204class BasicGeolocationCampaignSerializer(BaseBasicCampaignSerializer): 

205 """ 

206 Serializer for GeolocationCampaign 

207 """ 

208 

209 class Meta: 

210 model = GeolocationCampaign 

211 fields = BASIC_CAMPAIGN_FIELDS 

212 

213 

214class BasicSubmitCampaignSerializer(BaseBasicCampaignSerializer): 

215 """ 

216 Serializer for SubmitCampaign 

217 """ 

218 

219 class Meta: 

220 model = SubmitCampaign 

221 fields = BASIC_CAMPAIGN_FIELDS 

222 

223 

224class BasicQuizzCampaignSerializer(BaseBasicCampaignSerializer): 

225 """ 

226 Serializer for QuizzCampaign 

227 """ 

228 

229 class Meta: 

230 model = QuizzCampaign 

231 fields = BASIC_CAMPAIGN_FIELDS 

232 

233 

234class BasicCampaignPolymorphicSerializer(PolymorphicSerializer): 

235 """ 

236 Polymorphic serializer for Campaigns with minimum data 

237 """ 

238 

239 model_serializer_mapping = { 

240 VideoCampaign: BasicVideoCampaignSerializer, 

241 GeolocationCampaign: BasicGeolocationCampaignSerializer, 

242 SubmitCampaign: BasicSubmitCampaignSerializer, 

243 QuizzCampaign: BasicQuizzCampaignSerializer, 

244 } 

245 

246 

247class BaseCampaignClaimSerializer(ToRepresentationMixin): 

248 """ 

249 Serializer for Fan Campign 

250 """ 

251 

252 REPRESENTATION_FIELDS = [ 

253 ("fan", UserPolymorphicSerializer, False), 

254 ("campaign", BasicCampaignPolymorphicSerializer, False), 

255 ] 

256 

257 class Meta: 

258 model = CampaignClaim 

259 read_only_fields = ["random_slug", "created_at"] 

260 fields = read_only_fields + ["fan", "campaign"] 

261 

262 def validate(self, attrs): 

263 fan = attrs.get("fan") 

264 campaign = attrs.get("campaign") 

265 

266 if campaign.total_number_of_claims_per_fan is not None: 

267 if fan.campaigns.filter(campaign=campaign).count() >= campaign.total_number_of_claims_per_fan: 

268 raise serializers.ValidationError( 

269 f"This campaign can only be claimed {campaign.total_number_of_claims_per_fan} times." 

270 ) 

271 

272 if campaign.total_number_of_claims is not None: 

273 if campaign.participants.count() >= campaign.total_number_of_claims: 

274 raise serializers.ValidationError("This campaign cannot be claimed any more") 

275 

276 return attrs 

277 

278 

279class ValidateCampaignMixin: 

280 def validate_campaign(self, campaign): 

281 model_name = self.Meta.model.__name__.replace("CampaignClaim", "") 

282 if not isinstance(campaign, eval(f"{model_name}Campaign")): 

283 raise serializers.ValidationError(f"The campaign must be of type {model_name}.") 

284 

285 return campaign 

286 

287 

288class VideoCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin): 

289 """ 

290 Serializer for Video Campaign Claim 

291 """ 

292 

293 class Meta: 

294 model = VideoCampaignClaim 

295 read_only_fields = ["random_slug", "created_at"] 

296 fields = read_only_fields + ["fan", "campaign"] 

297 

298 

299class GeolocationCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin): 

300 """ 

301 Serializer for Geolocation Campaign Claim 

302 """ 

303 

304 class Meta: 

305 model = GeolocationCampaignClaim 

306 read_only_fields = ["random_slug", "created_at"] 

307 fields = read_only_fields + ["fan", "campaign", "latitude", "longitude"] 

308 

309 def validate(self, attrs): 

310 attrs = super().validate(attrs) 

311 campaign = attrs.get("campaign") 

312 latitude = attrs.get("latitude") 

313 longitude = attrs.get("longitude") 

314 

315 distance = distance_between_lat_long(latitude, longitude, campaign.latitude, campaign.longitude) 

316 

317 if distance > campaign.radius: 

318 raise serializers.ValidationError("You are to far from the designated location.") 

319 

320 return attrs 

321 

322 

323class SubmitCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin): 

324 """ 

325 Serializer for Submit Campaign Claim 

326 """ 

327 

328 class Meta: 

329 model = SubmitCampaignClaim 

330 read_only_fields = ["random_slug", "created_at"] 

331 fields = read_only_fields + ["fan", "campaign", "file"] 

332 

333 

334class QuizzCampaignClaimAnswerSerializer(BaseCampaignSerializer): 

335 """ 

336 Serializer for Quizz Campaign Claims 

337 """ 

338 

339 class Meta: 

340 model = QuizzCampaignClaimAnswer 

341 read_only_fields = ["random_slug", "created_at"] 

342 fields = read_only_fields + ["quizz_campaign_claim", "quizz_campaign_question", "quizz_campaign_answers"] 

343 extra_kwargs = {"quizz_campaign_claim": {"required": False, "write_only": True}} 

344 

345 

346class QuizzCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin): 

347 """ 

348 Serializer for Quizz Campaign Claim 

349 """ 

350 

351 answers = QuizzCampaignClaimAnswerSerializer(many=True) 

352 

353 class Meta: 

354 model = QuizzCampaignClaim 

355 read_only_fields = ["random_slug", "created_at"] 

356 fields = read_only_fields + ["campaign", "fan", "is_valid", "answers"] 

357 

358 def create(self, validated_data): 

359 with transaction.atomic(): 

360 answers = validated_data.pop("answers", []) 

361 

362 quizz_campaign_claim = super().create(validated_data) 

363 

364 for answer in answers: 

365 answer["quizz_campaign_claim"] = quizz_campaign_claim 

366 answer_serializer = QuizzCampaignClaimAnswerSerializer(answer) 

367 answer_serializer.create(answer) 

368 

369 errors_count = 0 

370 

371 for answer in quizz_campaign_claim.answers.all(): 

372 errors = answer.quizz_campaign_answers.filter(is_correct=False).exists() 

373 if errors: 

374 errors_count += 1 

375 

376 if errors_count > ( 

377 quizz_campaign_claim.campaign.questions.count() 

378 - quizz_campaign_claim.campaign.correct_answers_for_approval 

379 ): 

380 quizz_campaign_claim.is_valid = False 

381 quizz_campaign_claim.save() 

382 

383 return quizz_campaign_claim 

384 

385 def validate(self, attrs): 

386 fan = attrs.get("fan") 

387 campaign = attrs.get("campaign") 

388 

389 if fan.campaigns.filter(campaign=campaign).exists(): 

390 raise serializers.ValidationError({"detail": "You can only participate once."}) 

391 

392 return attrs 

393 

394 

395class CampaignClaimPolymorphicSerializer(PolymorphicSerializer): 

396 """ 

397 Polymorphic serializer for Campaign Claim objects 

398 """ 

399 

400 model_serializer_mapping = { 

401 CampaignClaim: BaseCampaignClaimSerializer, 

402 VideoCampaignClaim: VideoCampaignClaimSerializer, 

403 GeolocationCampaignClaim: GeolocationCampaignClaimSerializer, 

404 SubmitCampaignClaim: SubmitCampaignClaimSerializer, 

405 QuizzCampaignClaim: QuizzCampaignClaimSerializer, 

406 } 

407 

408 class Meta: 

409 model = CampaignClaim