poll.py 5.6 KB

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