OpenSlides/openslides/assignments/serializers.py

254 lines
8.2 KiB
Python

from django.db import transaction
from openslides.poll.serializers import default_votes_validator
from openslides.utils.rest_api import (
BooleanField,
DecimalField,
DictField,
IntegerField,
ListField,
ModelSerializer,
SerializerMethodField,
ValidationError,
)
from ..utils.auth import has_perm
from ..utils.autoupdate import inform_changed_data
from ..utils.validate import validate_html
from .models import (
Assignment,
AssignmentOption,
AssignmentPoll,
AssignmentRelatedUser,
AssignmentVote,
)
def posts_validator(data):
"""
Validator for open posts. It checks that the values for the open posts are greater than 0.
"""
if data["open_posts"] and data["open_posts"] is not None and data["open_posts"] < 1:
raise ValidationError(
{"detail": "Value for 'open_posts' must be greater than 0"}
)
return data
class AssignmentRelatedUserSerializer(ModelSerializer):
"""
Serializer for assignment.models.AssignmentRelatedUser objects.
"""
class Meta:
model = AssignmentRelatedUser
fields = (
"id",
"user",
"elected",
"assignment",
"weight",
) # js-data needs the assignment-id in the nested object to define relations.
class AssignmentVoteSerializer(ModelSerializer):
"""
Serializer for assignment.models.AssignmentVote objects.
"""
class Meta:
model = AssignmentVote
fields = ("weight", "value")
class AssignmentOptionSerializer(ModelSerializer):
"""
Serializer for assignment.models.AssignmentOption objects.
"""
votes = AssignmentVoteSerializer(many=True, read_only=True)
is_elected = SerializerMethodField()
class Meta:
model = AssignmentOption
fields = ("id", "candidate", "is_elected", "votes", "poll", "weight")
def get_is_elected(self, obj):
"""
Returns the election status of the candidate of this option.
If the candidate is None (e.g. deleted) the result is False.
"""
if not obj.candidate:
return False
return obj.poll.assignment.is_elected(obj.candidate)
class AssignmentAllPollSerializer(ModelSerializer):
"""
Serializer for assignment.models.AssignmentPoll objects.
Serializes all polls.
"""
options = AssignmentOptionSerializer(many=True, read_only=True)
votes = ListField(
child=DictField(
child=DecimalField(max_digits=15, decimal_places=6, min_value=-2)
),
write_only=True,
required=False,
)
has_votes = SerializerMethodField()
class Meta:
model = AssignmentPoll
fields = (
"id",
"pollmethod",
"description",
"published",
"options",
"votesabstain",
"votesno",
"votesvalid",
"votesinvalid",
"votescast",
"votes",
"has_votes",
"assignment",
) # js-data needs the assignment-id in the nested object to define relations.
read_only_fields = ("pollmethod",)
validators = (default_votes_validator,)
def get_has_votes(self, obj):
"""
Returns True if this poll has some votes.
"""
return obj.has_votes()
@transaction.atomic
def update(self, instance, validated_data):
"""
Customized update method for polls. To update votes use the write
only field 'votes'.
Example data for a 'pollmethod'='yna' poll with two candidates:
"votes": [{"Yes": 10, "No": 4, "Abstain": -2},
{"Yes": -1, "No": 0, "Abstain": -2}]
Example data for a 'pollmethod' ='yn' poll with two candidates:
"votes": [{"Votes": 10}, {"Votes": 0}]
"""
# Update votes.
votes = validated_data.get("votes")
if votes:
options = list(instance.get_options())
if len(votes) != len(options):
raise ValidationError(
{
"detail": "You have to submit data for {0} candidates.",
"args": [len(options)],
}
)
for index, option in enumerate(options):
if len(votes[index]) != len(instance.get_vote_values()):
raise ValidationError(
{
"detail": "You have to submit data for {0} vote values",
"args": [len(instance.get_vote_values())],
}
)
for vote_value, __ in votes[index].items():
if vote_value not in instance.get_vote_values():
raise ValidationError(
{
"detail": "Vote value {0} is invalid.",
"args": [vote_value],
}
)
instance.set_vote_objects_with_values(
option, votes[index], skip_autoupdate=True
)
# Update remaining writeable fields.
instance.description = validated_data.get("description", instance.description)
instance.published = validated_data.get("published", instance.published)
instance.votesabstain = validated_data.get(
"votesabstain", instance.votesabstain
)
instance.votesno = validated_data.get("votesno", instance.votesno)
instance.votesvalid = validated_data.get("votesvalid", instance.votesvalid)
instance.votesinvalid = validated_data.get(
"votesinvalid", instance.votesinvalid
)
instance.votescast = validated_data.get("votescast", instance.votescast)
instance.save()
return instance
class AssignmentFullSerializer(ModelSerializer):
"""
Serializer for assignment.models.Assignment objects. With all polls.
"""
assignment_related_users = AssignmentRelatedUserSerializer(
many=True, read_only=True
)
polls = AssignmentAllPollSerializer(many=True, read_only=True)
agenda_create = BooleanField(write_only=True, required=False, allow_null=True)
agenda_type = IntegerField(
write_only=True, required=False, min_value=1, max_value=3, allow_null=True
)
agenda_parent_id = IntegerField(write_only=True, required=False, min_value=1)
class Meta:
model = Assignment
fields = (
"id",
"title",
"description",
"open_posts",
"phase",
"assignment_related_users",
"poll_description_default",
"polls",
"agenda_item_id",
"list_of_speakers_id",
"agenda_create",
"agenda_type",
"agenda_parent_id",
"tags",
"attachments",
)
validators = (posts_validator,)
def validate(self, data):
if "description" in data:
data["description"] = validate_html(data["description"])
return data
def create(self, validated_data):
"""
Customized create method. Set information about related agenda item
into agenda_item_update_information container.
"""
tags = validated_data.pop("tags", [])
attachments = validated_data.pop("attachments", [])
request_user = validated_data.pop("request_user") # this should always be there
agenda_create = validated_data.pop("agenda_create", None)
agenda_type = validated_data.pop("agenda_type", None)
agenda_parent_id = validated_data.pop("agenda_parent_id", None)
assignment = Assignment(**validated_data)
if has_perm(request_user, "agenda.can_manage"):
assignment.agenda_item_update_information["create"] = agenda_create
assignment.agenda_item_update_information["type"] = agenda_type
assignment.agenda_item_update_information["parent_id"] = agenda_parent_id
assignment.save()
assignment.tags.add(*tags)
assignment.attachments.add(*attachments)
inform_changed_data(assignment)
return assignment