poll.py 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. from django.urls import reverse
  2. from django.utils.crypto import get_random_string
  3. from django.utils.translation import gettext as _
  4. from django.utils.translation import ngettext
  5. from rest_framework import serializers
  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 {"index": obj.get_api_url(), "votes": obj.get_votes_api_url()}
  38. def get_url(self, obj):
  39. return {"poster": self.get_poster_url(obj)}
  40. def get_poster_url(self, obj):
  41. if obj.poster_id:
  42. return reverse(
  43. "misago:user", kwargs={"slug": obj.poster_slug, "pk": obj.poster_id}
  44. )
  45. else:
  46. return None
  47. def get_acl(self, obj):
  48. try:
  49. return obj.acl
  50. except AttributeError:
  51. return None
  52. def get_choices(self, obj):
  53. return obj.choices
  54. class EditPollSerializer(serializers.ModelSerializer):
  55. length = serializers.IntegerField(required=True, min_value=0, max_value=180)
  56. question = serializers.CharField(required=True, max_length=255)
  57. allowed_choices = serializers.IntegerField(required=True, min_value=1)
  58. choices = serializers.ListField(allow_empty=False, child=serializers.DictField())
  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(
  86. _("One or more poll choices are invalid.")
  87. )
  88. return serializer.data
  89. def validate_choices_num(self, choices):
  90. total_choices = len(choices)
  91. if total_choices < 2:
  92. raise serializers.ValidationError(
  93. _("You need to add at least two choices to a poll.")
  94. )
  95. if total_choices > MAX_POLL_OPTIONS:
  96. message = ngettext(
  97. "You can't add more than %(limit_value)s option to a single poll (added %(show_value)s).",
  98. "You can't add more than %(limit_value)s options to a single poll (added %(show_value)s).",
  99. MAX_POLL_OPTIONS,
  100. )
  101. raise serializers.ValidationError(
  102. message % {"limit_value": MAX_POLL_OPTIONS, "show_value": total_choices}
  103. )
  104. def validate(self, data):
  105. if data["allowed_choices"] > len(data["choices"]):
  106. raise serializers.ValidationError(
  107. _(
  108. "Number of allowed choices can't be greater than number of all choices."
  109. )
  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().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",
  130. "question",
  131. "allowed_choices",
  132. "allow_revotes",
  133. "is_public",
  134. "choices",
  135. ]
  136. def validate_choices(self, choices):
  137. clean_choices = list(map(self.clean_choice, choices))
  138. self.validate_choices_num(clean_choices)
  139. for choice in clean_choices:
  140. choice.update({"hash": get_random_string(12), "votes": 0})
  141. return clean_choices
  142. class PollChoiceSerializer(serializers.Serializer):
  143. hash = serializers.CharField(required=True, min_length=12, max_length=12)
  144. label = serializers.CharField(required=True, max_length=255)