poll.py 5.2 KB

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