dd4754d045
Before this commit, there where two different locks when updating the restricted data cache. A future lock, what is faster but only works in the same thread. The other lock is in redis, it is not so fast, but also works in many threads. The future lock was buggy, because on a second call of update_restricted_data the same future was reused. So on the second run, the future was already done. I don't see any way to delete. The last client would have to delete it, but there is no way to find out which client the last one is.
274 lines
8.3 KiB
Python
274 lines
8.3 KiB
Python
from django.db import transaction
|
|
|
|
from openslides.poll.serializers import default_votes_validator
|
|
from openslides.utils.rest_api import (
|
|
DecimalField,
|
|
DictField,
|
|
IntegerField,
|
|
ListField,
|
|
ListSerializer,
|
|
ModelSerializer,
|
|
SerializerMethodField,
|
|
ValidationError,
|
|
)
|
|
|
|
from .models import (
|
|
Assignment,
|
|
AssignmentOption,
|
|
AssignmentPoll,
|
|
AssignmentRelatedUser,
|
|
AssignmentVote,
|
|
models,
|
|
)
|
|
from ..utils.validate import validate_html
|
|
|
|
|
|
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.
|
|
"""
|
|
return obj.poll.assignment.is_elected(obj.candidate)
|
|
|
|
|
|
class FilterPollListSerializer(ListSerializer):
|
|
"""
|
|
Customized serializer to filter polls (exclude unpublished).
|
|
"""
|
|
|
|
def to_representation(self, data):
|
|
"""
|
|
List of object instances -> List of dicts of primitive datatypes.
|
|
|
|
This method is adapted to filter the data and exclude unpublished polls.
|
|
"""
|
|
# Dealing with nested relationships, data can be a Manager,
|
|
# so, first get a queryset from the Manager if needed
|
|
iterable = (
|
|
data.filter(published=True) if isinstance(data, models.Manager) else data
|
|
)
|
|
return [self.child.to_representation(item) for item in iterable]
|
|
|
|
|
|
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": f"You have to submit data for {len(options)} candidates."
|
|
}
|
|
)
|
|
for index, option in enumerate(options):
|
|
if len(votes[index]) != len(instance.get_vote_values()):
|
|
raise ValidationError(
|
|
{
|
|
"detail": f"You have to submit data for {len(instance.get_vote_values())} vote values."
|
|
}
|
|
)
|
|
for vote_value, __ in votes[index].items():
|
|
if vote_value not in instance.get_vote_values():
|
|
raise ValidationError(
|
|
{"detail": f"Vote value {vote_value} is invalid."}
|
|
)
|
|
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 AssignmentShortPollSerializer(AssignmentAllPollSerializer):
|
|
"""
|
|
Serializer for assignment.models.AssignmentPoll objects.
|
|
|
|
Serializes only short polls (excluded unpublished polls).
|
|
"""
|
|
|
|
class Meta:
|
|
list_serializer_class = FilterPollListSerializer
|
|
model = AssignmentPoll
|
|
fields = (
|
|
"id",
|
|
"pollmethod",
|
|
"description",
|
|
"published",
|
|
"options",
|
|
"votesabstain",
|
|
"votesno",
|
|
"votesvalid",
|
|
"votesinvalid",
|
|
"votescast",
|
|
"has_votes",
|
|
)
|
|
|
|
|
|
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_type = IntegerField(
|
|
write_only=True, required=False, min_value=1, max_value=3
|
|
)
|
|
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",
|
|
"agenda_type",
|
|
"agenda_parent_id",
|
|
"tags",
|
|
)
|
|
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.
|
|
"""
|
|
agenda_type = validated_data.pop("agenda_type", None)
|
|
agenda_parent_id = validated_data.pop("agenda_parent_id", None)
|
|
assignment = Assignment(**validated_data)
|
|
assignment.agenda_item_update_information["type"] = agenda_type
|
|
assignment.agenda_item_update_information["parent_id"] = agenda_parent_id
|
|
assignment.save()
|
|
return assignment
|