poll.py 5.9 KB

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