poll.py 5.8 KB

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