poll.py 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. from rest_framework import serializers
  2. from django.urls import reverse
  3. from django.utils.crypto import get_random_string
  4. from django.utils.translation import ugettext as _
  5. from django.utils.translation import ungettext
  6. from misago.threads.models import Poll
  7. MAX_POLL_OPTIONS = 16
  8. class PollSerializer(serializers.ModelSerializer):
  9. acl = serializers.SerializerMethodField()
  10. choices = serializers.SerializerMethodField()
  11. api = serializers.SerializerMethodField()
  12. url = serializers.SerializerMethodField()
  13. class Meta:
  14. model = Poll
  15. fields = [
  16. 'id',
  17. 'poster_name',
  18. 'posted_on',
  19. 'length',
  20. 'question',
  21. 'allowed_choices',
  22. 'allow_revotes',
  23. 'votes',
  24. 'is_public',
  25. 'acl',
  26. 'choices',
  27. 'api',
  28. 'url',
  29. ]
  30. def get_api(self, obj):
  31. return {
  32. 'index': obj.get_api_url(),
  33. 'votes': obj.get_votes_api_url(),
  34. }
  35. def get_url(self, obj):
  36. return {
  37. 'poster': self.get_poster_url(obj),
  38. }
  39. def get_poster_url(self, obj):
  40. if obj.poster_id:
  41. return reverse(
  42. 'misago:user', kwargs={
  43. 'slug': obj.poster_slug,
  44. 'pk': obj.poster_id,
  45. }
  46. )
  47. else:
  48. return None
  49. def get_acl(self, obj):
  50. try:
  51. return obj.acl
  52. except AttributeError:
  53. return None
  54. def get_choices(self, obj):
  55. return obj.choices
  56. class EditPollSerializer(serializers.ModelSerializer):
  57. length = serializers.IntegerField(required=True, min_value=0, max_value=180)
  58. question = serializers.CharField(required=True, max_length=255)
  59. allowed_choices = serializers.IntegerField(required=True, min_value=1)
  60. choices = serializers.ListField(
  61. allow_empty=False,
  62. child=serializers.DictField(),
  63. )
  64. class Meta:
  65. model = Poll
  66. fields = [
  67. 'length',
  68. 'question',
  69. 'allowed_choices',
  70. 'allow_revotes',
  71. 'choices',
  72. ]
  73. def validate_choices(self, choices):
  74. clean_choices = list(map(self.clean_choice, choices))
  75. # generate hashes for added choices
  76. choices_map = {}
  77. for choice in self.instance.choices:
  78. choices_map[choice['hash']] = choice
  79. final_choices = []
  80. for choice in clean_choices:
  81. if choice['hash'] in choices_map:
  82. choices_map[choice['hash']].update({'label': choice['label']})
  83. final_choices.append(choices_map[choice['hash']])
  84. else:
  85. choice.update({
  86. 'hash': get_random_string(12),
  87. 'votes': 0,
  88. })
  89. final_choices.append(choice)
  90. self.validate_choices_num(final_choices)
  91. return final_choices
  92. def clean_choice(self, choice):
  93. clean_choice = {
  94. 'hash': choice.get('hash', get_random_string(12)),
  95. 'label': choice.get('label', ''),
  96. }
  97. serializer = PollChoiceSerializer(data=clean_choice)
  98. if not serializer.is_valid():
  99. raise serializers.ValidationError(_("One or more poll choices are invalid."))
  100. return serializer.data
  101. def validate_choices_num(self, choices):
  102. total_choices = len(choices)
  103. if total_choices < 2:
  104. raise serializers.ValidationError(_("You need to add at least two choices to a poll."))
  105. if total_choices > MAX_POLL_OPTIONS:
  106. message = ungettext(
  107. "You can't add more than %(limit_value)s option to a single poll (added %(show_value)s).",
  108. "You can't add more than %(limit_value)s options to a single poll (added %(show_value)s).",
  109. MAX_POLL_OPTIONS,
  110. )
  111. raise serializers.ValidationError(
  112. message % {
  113. 'limit_value': MAX_POLL_OPTIONS,
  114. 'show_value': total_choices,
  115. }
  116. )
  117. def validate(self, data):
  118. if data['allowed_choices'] > len(data['choices']):
  119. raise serializers.ValidationError(
  120. _("Number of allowed choices can't be greater than number of all choices.")
  121. )
  122. return data
  123. def update(self, instance, validated_data):
  124. if instance.choices:
  125. self.update_choices(instance, validated_data['choices'])
  126. return super(EditPollSerializer, self).update(instance, validated_data)
  127. def update_choices(self, instance, cleaned_choices):
  128. removed_hashes = []
  129. final_hashes = [c['hash'] for c in cleaned_choices]
  130. for choice in instance.choices:
  131. if choice['hash'] not in final_hashes:
  132. instance.votes -= choice['votes']
  133. removed_hashes.append(choice['hash'])
  134. if removed_hashes:
  135. instance.pollvote_set.filter(choice_hash__in=removed_hashes).delete()
  136. class NewPollSerializer(EditPollSerializer):
  137. class Meta:
  138. model = Poll
  139. fields = [
  140. 'length',
  141. 'question',
  142. 'allowed_choices',
  143. 'allow_revotes',
  144. 'is_public',
  145. 'choices',
  146. ]
  147. def validate_choices(self, choices):
  148. clean_choices = list(map(self.clean_choice, choices))
  149. self.validate_choices_num(clean_choices)
  150. for choice in clean_choices:
  151. choice.update({
  152. 'hash': get_random_string(12),
  153. 'votes': 0,
  154. })
  155. return clean_choices
  156. class PollChoiceSerializer(serializers.Serializer):
  157. hash = serializers.CharField(required=True, min_length=12, max_length=12)
  158. label = serializers.CharField(required=True, max_length=255)