from rest_framework import serializers from django.urls import reverse from django.utils.crypto import get_random_string from django.utils.translation import ugettext as _ from django.utils.translation import ungettext from misago.threads.models import Poll __all__ = [ 'PollSerializer', 'EditPollSerializer', 'NewPollSerializer', 'PollChoiceSerializer', ] MAX_POLL_OPTIONS = 16 class PollSerializer(serializers.ModelSerializer): acl = serializers.SerializerMethodField() choices = serializers.SerializerMethodField() api = serializers.SerializerMethodField() url = serializers.SerializerMethodField() class Meta: model = Poll fields = ( 'id', 'poster_name', 'posted_on', 'length', 'question', 'allowed_choices', 'allow_revotes', 'votes', 'is_public', 'acl', 'choices', 'api', 'url', ) def get_api(self, obj): return { 'index': obj.get_api_url(), 'votes': obj.get_votes_api_url(), } def get_url(self, obj): return { 'poster': self.get_poster_url(obj), } def get_poster_url(self, obj): if obj.poster_id: return reverse('misago:user', kwargs={ 'slug': obj.poster_slug, 'pk': obj.poster_id, }) else: return None def get_acl(self, obj): try: return obj.acl except AttributeError: return None def get_choices(self, obj): return obj.choices class EditPollSerializer(serializers.ModelSerializer): length = serializers.IntegerField(required=True, min_value=0, max_value=180) question = serializers.CharField(required=True, max_length=255) allowed_choices = serializers.IntegerField(required=True, min_value=1) choices = serializers.ListField( allow_empty=False, child=serializers.DictField(), ) class Meta: model = Poll fields = ( 'length', 'question', 'allowed_choices', 'allow_revotes', 'choices', ) def validate_choices(self, choices): clean_choices = list(map(self.clean_choice, choices)) # generate hashes for added choices choices_map = {} for choice in self.instance.choices: choices_map[choice['hash']] = choice final_choices = [] for choice in clean_choices: if choice['hash'] in choices_map: choices_map[choice['hash']].update({ 'label': choice['label'] }) final_choices.append(choices_map[choice['hash']]) else: choice.update({ 'hash': get_random_string(12), 'votes': 0 }) final_choices.append(choice) self.validate_choices_num(final_choices) return final_choices def clean_choice(self, choice): clean_choice = { 'hash': choice.get('hash', get_random_string(12)), 'label': choice.get('label', ''), } serializer = PollChoiceSerializer(data=clean_choice) if not serializer.is_valid(): raise serializers.ValidationError(_("One or more poll choices are invalid.")) return serializer.data def validate_choices_num(self, choices): total_choices = len(choices) if total_choices < 2: raise serializers.ValidationError(_("You need to add at least two choices to a poll.")) if total_choices > MAX_POLL_OPTIONS: message = ungettext( "You can't add more than %(limit_value)s option to a single poll (added %(show_value)s).", "You can't add more than %(limit_value)s options to a single poll (added %(show_value)s).", MAX_POLL_OPTIONS) raise serializers.ValidationError(message % { 'limit_value': MAX_POLL_OPTIONS, 'show_value': total_choices }) def validate(self, data): if data['allowed_choices'] > len(data['choices']): raise serializers.ValidationError( _("Number of allowed choices can't be greater than number of all choices.")) return data def update(self, instance, validated_data): if instance.choices: self.update_choices(instance, validated_data['choices']) return super(EditPollSerializer, self).update(instance, validated_data) def update_choices(self, instance, cleaned_choices): removed_hashes = [] final_hashes = [c['hash'] for c in cleaned_choices] for choice in instance.choices: if choice['hash'] not in final_hashes: instance.votes -= choice['votes'] removed_hashes.append(choice['hash']) if removed_hashes: instance.pollvote_set.filter(choice_hash__in=removed_hashes).delete() class NewPollSerializer(EditPollSerializer): class Meta: model = Poll fields = ( 'length', 'question', 'allowed_choices', 'allow_revotes', 'is_public', 'choices', ) def validate_choices(self, choices): clean_choices = list(map(self.clean_choice, choices)) self.validate_choices_num(clean_choices) for choice in clean_choices: choice.update({ 'hash': get_random_string(12), 'votes': 0 }) return clean_choices class PollChoiceSerializer(serializers.Serializer): hash = serializers.CharField(required=True, min_length=12, max_length=12) label = serializers.CharField(required=True, max_length=255)