poll.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176
  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({'hash': get_random_string(12), 'votes': 0})
  75. final_choices.append(choice)
  76. self.validate_choices_num(final_choices)
  77. return final_choices
  78. def clean_choice(self, choice):
  79. clean_choice = {
  80. 'hash': choice.get('hash', get_random_string(12)),
  81. 'label': choice.get('label', ''),
  82. }
  83. serializer = PollChoiceSerializer(data=clean_choice)
  84. if not serializer.is_valid():
  85. raise serializers.ValidationError(_("One or more poll choices are invalid."))
  86. return serializer.data
  87. def validate_choices_num(self, choices):
  88. total_choices = len(choices)
  89. if total_choices < 2:
  90. raise serializers.ValidationError(_("You need to add at least two choices to a poll."))
  91. if total_choices > MAX_POLL_OPTIONS:
  92. message = ungettext(
  93. "You can't add more than %(limit_value)s option to a single poll (added %(show_value)s).",
  94. "You can't add more than %(limit_value)s options to a single poll (added %(show_value)s).",
  95. MAX_POLL_OPTIONS
  96. )
  97. raise serializers.ValidationError(
  98. message % {'limit_value': MAX_POLL_OPTIONS,
  99. 'show_value': total_choices}
  100. )
  101. def validate(self, data):
  102. if data['allowed_choices'] > len(data['choices']):
  103. raise serializers.ValidationError(
  104. _("Number of allowed choices can't be greater than number of all choices.")
  105. )
  106. return data
  107. def update(self, instance, validated_data):
  108. if instance.choices:
  109. self.update_choices(instance, validated_data['choices'])
  110. return super(EditPollSerializer, self).update(instance, validated_data)
  111. def update_choices(self, instance, cleaned_choices):
  112. removed_hashes = []
  113. final_hashes = [c['hash'] for c in cleaned_choices]
  114. for choice in instance.choices:
  115. if choice['hash'] not in final_hashes:
  116. instance.votes -= choice['votes']
  117. removed_hashes.append(choice['hash'])
  118. if removed_hashes:
  119. instance.pollvote_set.filter(choice_hash__in=removed_hashes).delete()
  120. class NewPollSerializer(EditPollSerializer):
  121. class Meta:
  122. model = Poll
  123. fields = (
  124. 'length', 'question', 'allowed_choices', 'allow_revotes', 'is_public', 'choices',
  125. )
  126. def validate_choices(self, choices):
  127. clean_choices = list(map(self.clean_choice, choices))
  128. self.validate_choices_num(clean_choices)
  129. for choice in clean_choices:
  130. choice.update({'hash': get_random_string(12), 'votes': 0})
  131. return clean_choices
  132. class PollChoiceSerializer(serializers.Serializer):
  133. hash = serializers.CharField(required=True, min_length=12, max_length=12)
  134. label = serializers.CharField(required=True, max_length=255)