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
« 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
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
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
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]
48class BaseCampaignSerializer(serializers.ModelSerializer):
49 """
50 Base Campaign Serializer
51 """
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()
60 def get_can_be_claimed(self, instance):
61 user = self.context.get("request").user
62 today = timezone.datetime.today().date()
64 if instance.valid_from > today or instance.valid_to < today:
65 return False
67 if user.is_anonymous or isinstance(user, (Employee, SuperUser)):
68 return False
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
74 if instance.total_number_of_claims is not None:
75 if instance.participants.count() >= instance.total_number_of_claims:
76 return False
78 return True
80 def get_times_claimed(self, instance):
81 user = self.context.get("request").user
83 if user.is_anonymous:
84 return 0
86 return instance.participants.filter(fan=user).count()
88 def get_total_times_claimed(self, instance):
89 return instance.participants.count()
91 def get_amount_claimed(self, instance):
92 user = self.context.get("request").user
94 if user.is_anonymous:
95 return 0
97 return instance.participants.filter(fan=user).count() * instance.value
100class FanVideoCampaignSerializer(BaseCampaignSerializer):
101 """
102 Serializer for VideoCampaign
103 """
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"]
111class FanGeolocationCampaignSerializer(BaseCampaignSerializer):
112 """
113 Serializer for Geolocation Campaign
114 """
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"]
122class FanSubmitCampaignSerializer(BaseCampaignSerializer):
123 """
124 Serializer for Submit Campaign
125 """
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"]
133class QuizzCampaignQuestionAnswerSerializer(serializers.ModelSerializer):
134 """
135 Serializer for Quizz Campaign Question Answer
136 """
138 class Meta:
139 model = QuizzCampaignQuestionAnswer
140 read_only_fields = ["random_slug"]
141 fields = read_only_fields + ["answer"]
144class QuizzCampaignQuestionSerializer(serializers.ModelSerializer):
145 """
146 Serializer for Quiz Campaign Question
147 """
149 answers = QuizzCampaignQuestionAnswerSerializer(many=True)
151 class Meta:
152 model = QuizzCampaignQuestion
153 read_only_fields = ["random_slug"]
154 fields = read_only_fields + ["question", "type_of", "answers"]
157class FanQuizzCampaignSerializer(BaseCampaignSerializer):
158 """
159 Serializer for Quizz Campaign
160 """
162 questions = QuizzCampaignQuestionSerializer(many=True)
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"]
170class FanCampaignPolymorphicSerializer(PolymorphicSerializer):
171 """
172 Polymorphic serializer for Campaign objects
173 """
175 model_serializer_mapping = {
176 VideoCampaign: FanVideoCampaignSerializer,
177 GeolocationCampaign: FanGeolocationCampaignSerializer,
178 SubmitCampaign: FanSubmitCampaignSerializer,
179 QuizzCampaign: FanQuizzCampaignSerializer,
180 }
183class BaseBasicCampaignSerializer(ToRepresentationMixin):
184 """
185 Base serializer for Campaign with minimum data
186 """
188 REPRESENTATION_FIELDS = [
189 ("organization", BasicOrganizationSerializer, False),
190 ("image", OrganizationUploadPolymorphicSerializer, False),
191 ]
194class BasicVideoCampaignSerializer(BaseBasicCampaignSerializer):
195 """
196 Serializer for VideoCampaign
197 """
199 class Meta:
200 model = VideoCampaign
201 fields = BASIC_CAMPAIGN_FIELDS
204class BasicGeolocationCampaignSerializer(BaseBasicCampaignSerializer):
205 """
206 Serializer for GeolocationCampaign
207 """
209 class Meta:
210 model = GeolocationCampaign
211 fields = BASIC_CAMPAIGN_FIELDS
214class BasicSubmitCampaignSerializer(BaseBasicCampaignSerializer):
215 """
216 Serializer for SubmitCampaign
217 """
219 class Meta:
220 model = SubmitCampaign
221 fields = BASIC_CAMPAIGN_FIELDS
224class BasicQuizzCampaignSerializer(BaseBasicCampaignSerializer):
225 """
226 Serializer for QuizzCampaign
227 """
229 class Meta:
230 model = QuizzCampaign
231 fields = BASIC_CAMPAIGN_FIELDS
234class BasicCampaignPolymorphicSerializer(PolymorphicSerializer):
235 """
236 Polymorphic serializer for Campaigns with minimum data
237 """
239 model_serializer_mapping = {
240 VideoCampaign: BasicVideoCampaignSerializer,
241 GeolocationCampaign: BasicGeolocationCampaignSerializer,
242 SubmitCampaign: BasicSubmitCampaignSerializer,
243 QuizzCampaign: BasicQuizzCampaignSerializer,
244 }
247class BaseCampaignClaimSerializer(ToRepresentationMixin):
248 """
249 Serializer for Fan Campign
250 """
252 REPRESENTATION_FIELDS = [
253 ("fan", UserPolymorphicSerializer, False),
254 ("campaign", BasicCampaignPolymorphicSerializer, False),
255 ]
257 class Meta:
258 model = CampaignClaim
259 read_only_fields = ["random_slug", "created_at"]
260 fields = read_only_fields + ["fan", "campaign"]
262 def validate(self, attrs):
263 fan = attrs.get("fan")
264 campaign = attrs.get("campaign")
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 )
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")
276 return attrs
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}.")
285 return campaign
288class VideoCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin):
289 """
290 Serializer for Video Campaign Claim
291 """
293 class Meta:
294 model = VideoCampaignClaim
295 read_only_fields = ["random_slug", "created_at"]
296 fields = read_only_fields + ["fan", "campaign"]
299class GeolocationCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin):
300 """
301 Serializer for Geolocation Campaign Claim
302 """
304 class Meta:
305 model = GeolocationCampaignClaim
306 read_only_fields = ["random_slug", "created_at"]
307 fields = read_only_fields + ["fan", "campaign", "latitude", "longitude"]
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")
315 distance = distance_between_lat_long(latitude, longitude, campaign.latitude, campaign.longitude)
317 if distance > campaign.radius:
318 raise serializers.ValidationError("You are to far from the designated location.")
320 return attrs
323class SubmitCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin):
324 """
325 Serializer for Submit Campaign Claim
326 """
328 class Meta:
329 model = SubmitCampaignClaim
330 read_only_fields = ["random_slug", "created_at"]
331 fields = read_only_fields + ["fan", "campaign", "file"]
334class QuizzCampaignClaimAnswerSerializer(BaseCampaignSerializer):
335 """
336 Serializer for Quizz Campaign Claims
337 """
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}}
346class QuizzCampaignClaimSerializer(BaseCampaignClaimSerializer, ValidateCampaignMixin):
347 """
348 Serializer for Quizz Campaign Claim
349 """
351 answers = QuizzCampaignClaimAnswerSerializer(many=True)
353 class Meta:
354 model = QuizzCampaignClaim
355 read_only_fields = ["random_slug", "created_at"]
356 fields = read_only_fields + ["campaign", "fan", "is_valid", "answers"]
358 def create(self, validated_data):
359 with transaction.atomic():
360 answers = validated_data.pop("answers", [])
362 quizz_campaign_claim = super().create(validated_data)
364 for answer in answers:
365 answer["quizz_campaign_claim"] = quizz_campaign_claim
366 answer_serializer = QuizzCampaignClaimAnswerSerializer(answer)
367 answer_serializer.create(answer)
369 errors_count = 0
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
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()
383 return quizz_campaign_claim
385 def validate(self, attrs):
386 fan = attrs.get("fan")
387 campaign = attrs.get("campaign")
389 if fan.campaigns.filter(campaign=campaign).exists():
390 raise serializers.ValidationError({"detail": "You can only participate once."})
392 return attrs
395class CampaignClaimPolymorphicSerializer(PolymorphicSerializer):
396 """
397 Polymorphic serializer for Campaign Claim objects
398 """
400 model_serializer_mapping = {
401 CampaignClaim: BaseCampaignClaimSerializer,
402 VideoCampaignClaim: VideoCampaignClaimSerializer,
403 GeolocationCampaignClaim: GeolocationCampaignClaimSerializer,
404 SubmitCampaignClaim: SubmitCampaignClaimSerializer,
405 QuizzCampaignClaim: QuizzCampaignClaimSerializer,
406 }
408 class Meta:
409 model = CampaignClaim