2017-08-24 12:26:55 +02:00
|
|
|
from typing import Dict # noqa
|
|
|
|
|
2015-04-30 19:13:28 +02:00
|
|
|
from django.db import transaction
|
|
|
|
from django.utils.translation import ugettext as _
|
|
|
|
|
2017-04-28 22:10:18 +02:00
|
|
|
from ..poll.serializers import default_votes_validator
|
|
|
|
from ..utils.rest_api import (
|
2015-04-30 19:13:28 +02:00
|
|
|
CharField,
|
2015-07-22 15:23:57 +02:00
|
|
|
DictField,
|
2016-07-29 23:33:47 +02:00
|
|
|
Field,
|
2015-04-30 19:13:28 +02:00
|
|
|
IntegerField,
|
|
|
|
ModelSerializer,
|
|
|
|
PrimaryKeyRelatedField,
|
2015-10-21 21:13:45 +02:00
|
|
|
SerializerMethodField,
|
2015-06-16 10:37:23 +02:00
|
|
|
ValidationError,
|
|
|
|
)
|
2017-04-28 22:10:18 +02:00
|
|
|
from ..utils.validate import validate_html
|
2015-01-24 16:35:50 +01:00
|
|
|
from .models import (
|
|
|
|
Category,
|
|
|
|
Motion,
|
2016-10-01 20:42:44 +02:00
|
|
|
MotionBlock,
|
2016-09-10 18:49:38 +02:00
|
|
|
MotionChangeRecommendation,
|
2015-01-24 16:35:50 +01:00
|
|
|
MotionLog,
|
|
|
|
MotionPoll,
|
|
|
|
MotionVersion,
|
|
|
|
State,
|
2015-06-16 10:37:23 +02:00
|
|
|
Workflow,
|
|
|
|
)
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
|
2015-04-30 19:13:28 +02:00
|
|
|
def validate_workflow_field(value):
|
|
|
|
"""
|
|
|
|
Validator to ensure that the workflow with the given id exists.
|
|
|
|
"""
|
|
|
|
if not Workflow.objects.filter(pk=value).exists():
|
2016-02-06 00:02:22 +01:00
|
|
|
raise ValidationError({'detail': _('Workflow %(pk)d does not exist.') % {'pk': value}})
|
2015-04-30 19:13:28 +02:00
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class CategorySerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.Category objects.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
|
|
model = Category
|
2015-02-04 00:08:38 +01:00
|
|
|
fields = ('id', 'name', 'prefix',)
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
|
2016-10-01 20:42:44 +02:00
|
|
|
class MotionBlockSerializer(ModelSerializer):
|
|
|
|
"""
|
|
|
|
Serializer for motion.models.Category objects.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
|
|
model = MotionBlock
|
|
|
|
fields = ('id', 'title', 'agenda_item_id',)
|
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class StateSerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.State objects.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
|
|
model = State
|
|
|
|
fields = (
|
|
|
|
'id',
|
|
|
|
'name',
|
|
|
|
'action_word',
|
2016-09-03 21:43:11 +02:00
|
|
|
'recommendation_label',
|
2015-11-03 21:38:53 +01:00
|
|
|
'css_class',
|
2015-01-24 16:35:50 +01:00
|
|
|
'required_permission_to_see',
|
|
|
|
'allow_support',
|
|
|
|
'allow_create_poll',
|
|
|
|
'allow_submitter_edit',
|
|
|
|
'versioning',
|
|
|
|
'leave_old_version_active',
|
|
|
|
'dont_set_identifier',
|
2016-10-27 14:01:12 +02:00
|
|
|
'show_state_extension_field',
|
|
|
|
'show_recommendation_extension_field',
|
2015-11-03 10:03:44 +01:00
|
|
|
'next_states',
|
|
|
|
'workflow')
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class WorkflowSerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.Workflow objects.
|
|
|
|
"""
|
2015-11-03 10:03:44 +01:00
|
|
|
states = StateSerializer(many=True, read_only=True)
|
2015-02-12 18:48:14 +01:00
|
|
|
first_state = PrimaryKeyRelatedField(read_only=True)
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Workflow
|
2015-11-03 10:03:44 +01:00
|
|
|
fields = ('id', 'name', 'states', 'first_state',)
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
|
2016-07-29 23:33:47 +02:00
|
|
|
class MotionCommentsJSONSerializerField(Field):
|
|
|
|
"""
|
|
|
|
Serializer for motions's comments JSONField.
|
|
|
|
"""
|
|
|
|
def to_representation(self, obj):
|
|
|
|
"""
|
|
|
|
Returns the value of the field.
|
|
|
|
"""
|
|
|
|
return obj
|
|
|
|
|
|
|
|
def to_internal_value(self, data):
|
|
|
|
"""
|
|
|
|
Checks that data is a list of strings.
|
|
|
|
"""
|
|
|
|
if type(data) is not list:
|
|
|
|
raise ValidationError({'detail': 'Data must be an array.'})
|
|
|
|
for element in data:
|
|
|
|
if type(element) is not str:
|
|
|
|
raise ValidationError({'detail': 'Data must be an array of strings.'})
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class MotionLogSerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.MotionLog objects.
|
|
|
|
"""
|
2015-11-03 21:38:53 +01:00
|
|
|
message = SerializerMethodField()
|
|
|
|
|
2015-01-24 16:35:50 +01:00
|
|
|
class Meta:
|
|
|
|
model = MotionLog
|
2015-11-03 21:38:53 +01:00
|
|
|
fields = ('message_list', 'person', 'time', 'message',)
|
|
|
|
|
|
|
|
def get_message(self, obj):
|
|
|
|
"""
|
|
|
|
Concats the message parts to one string. Useful for smart template code.
|
|
|
|
"""
|
|
|
|
return str(obj)
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class MotionPollSerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.MotionPoll objects.
|
|
|
|
"""
|
2015-10-21 21:13:45 +02:00
|
|
|
yes = SerializerMethodField()
|
|
|
|
no = SerializerMethodField()
|
|
|
|
abstain = SerializerMethodField()
|
2015-07-22 15:23:57 +02:00
|
|
|
votes = DictField(
|
2015-11-19 19:56:01 +01:00
|
|
|
child=IntegerField(min_value=-2, allow_null=True),
|
2015-07-22 15:23:57 +02:00
|
|
|
write_only=True)
|
2016-01-14 22:43:49 +01:00
|
|
|
has_votes = SerializerMethodField()
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = MotionPoll
|
|
|
|
fields = (
|
2015-07-22 15:23:57 +02:00
|
|
|
'id',
|
2015-10-13 21:23:21 +02:00
|
|
|
'motion',
|
2015-10-21 21:13:45 +02:00
|
|
|
'yes',
|
|
|
|
'no',
|
|
|
|
'abstain',
|
2015-01-24 16:35:50 +01:00
|
|
|
'votesvalid',
|
|
|
|
'votesinvalid',
|
2015-07-22 15:23:57 +02:00
|
|
|
'votescast',
|
2016-01-14 22:43:49 +01:00
|
|
|
'votes',
|
|
|
|
'has_votes')
|
2016-02-09 21:04:29 +01:00
|
|
|
validators = (default_votes_validator,)
|
2015-10-21 21:13:45 +02:00
|
|
|
|
2016-02-27 20:49:28 +01:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
# The following dictionary is just a cache for several votes.
|
2017-08-24 12:26:55 +02:00
|
|
|
self._votes_dicts = {} # type: Dict[int, Dict[int, int]]
|
2016-02-27 20:49:28 +01:00
|
|
|
return super().__init__(*args, **kwargs)
|
|
|
|
|
2015-10-21 21:13:45 +02:00
|
|
|
def get_yes(self, obj):
|
2015-10-21 22:24:11 +02:00
|
|
|
try:
|
2016-02-24 10:27:42 +01:00
|
|
|
result = self.get_votes_dict(obj)['Yes']
|
|
|
|
except KeyError:
|
2015-10-21 22:24:11 +02:00
|
|
|
result = None
|
|
|
|
return result
|
2015-10-21 21:13:45 +02:00
|
|
|
|
|
|
|
def get_no(self, obj):
|
2015-10-21 22:24:11 +02:00
|
|
|
try:
|
2016-02-24 10:27:42 +01:00
|
|
|
result = self.get_votes_dict(obj)['No']
|
|
|
|
except KeyError:
|
2015-10-21 22:24:11 +02:00
|
|
|
result = None
|
|
|
|
return result
|
2015-10-21 21:13:45 +02:00
|
|
|
|
|
|
|
def get_abstain(self, obj):
|
2015-10-21 22:24:11 +02:00
|
|
|
try:
|
2016-02-24 10:27:42 +01:00
|
|
|
result = self.get_votes_dict(obj)['Abstain']
|
|
|
|
except KeyError:
|
2015-10-21 22:24:11 +02:00
|
|
|
result = None
|
|
|
|
return result
|
2015-07-22 15:23:57 +02:00
|
|
|
|
2016-02-24 10:27:42 +01:00
|
|
|
def get_votes_dict(self, obj):
|
|
|
|
try:
|
2016-02-27 20:49:28 +01:00
|
|
|
votes_dict = self._votes_dicts[obj.pk]
|
|
|
|
except KeyError:
|
|
|
|
votes_dict = self._votes_dicts[obj.pk] = {}
|
2016-02-24 10:27:42 +01:00
|
|
|
for vote in obj.get_votes():
|
|
|
|
votes_dict[vote.value] = vote.weight
|
|
|
|
return votes_dict
|
|
|
|
|
2016-01-14 22:43:49 +01:00
|
|
|
def get_has_votes(self, obj):
|
|
|
|
"""
|
|
|
|
Returns True if this poll has some votes.
|
|
|
|
"""
|
|
|
|
return obj.has_votes()
|
|
|
|
|
2015-07-22 15:23:57 +02:00
|
|
|
@transaction.atomic
|
|
|
|
def update(self, instance, validated_data):
|
|
|
|
"""
|
|
|
|
Customized update method for polls. To update votes use the write
|
|
|
|
only field 'votes'.
|
|
|
|
|
|
|
|
Example data:
|
|
|
|
|
|
|
|
"votes": {"Yes": 10, "No": 4, "Abstain": -2}
|
|
|
|
"""
|
|
|
|
# Update votes.
|
|
|
|
votes = validated_data.get('votes')
|
|
|
|
if votes:
|
|
|
|
if len(votes) != len(instance.get_vote_values()):
|
|
|
|
raise ValidationError({
|
|
|
|
'detail': _('You have to submit data for %d vote values.') % len(instance.get_vote_values())})
|
|
|
|
for vote_value, vote_weight in votes.items():
|
|
|
|
if vote_value not in instance.get_vote_values():
|
|
|
|
raise ValidationError({
|
|
|
|
'detail': _('Vote value %s is invalid.') % vote_value})
|
2016-10-12 11:23:53 +02:00
|
|
|
instance.set_vote_objects_with_values(instance.get_options().get(), votes, skip_autoupdate=True)
|
2015-07-22 15:23:57 +02:00
|
|
|
|
|
|
|
# Update remaining writeable fields.
|
|
|
|
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
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class MotionVersionSerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.MotionVersion objects.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
|
|
model = MotionVersion
|
|
|
|
fields = (
|
|
|
|
'id',
|
|
|
|
'version_number',
|
|
|
|
'creation_time',
|
|
|
|
'title',
|
|
|
|
'text',
|
|
|
|
'reason',)
|
|
|
|
|
|
|
|
|
2016-09-10 18:49:38 +02:00
|
|
|
class MotionChangeRecommendationSerializer(ModelSerializer):
|
|
|
|
"""
|
|
|
|
Serializer for motion.models.MotionChangeRecommendation objects.
|
|
|
|
"""
|
|
|
|
class Meta:
|
|
|
|
model = MotionChangeRecommendation
|
|
|
|
fields = (
|
|
|
|
'id',
|
|
|
|
'motion_version',
|
2016-11-13 13:19:16 +01:00
|
|
|
'rejected',
|
2016-11-13 16:42:56 +01:00
|
|
|
'type',
|
2016-09-10 18:49:38 +02:00
|
|
|
'line_from',
|
|
|
|
'line_to',
|
|
|
|
'text',
|
|
|
|
'creation_time',)
|
|
|
|
|
2017-01-20 11:34:05 +01:00
|
|
|
def validate(self, data):
|
2017-03-28 08:27:54 +02:00
|
|
|
if 'text' in data:
|
|
|
|
data['text'] = validate_html(data['text'])
|
2017-01-20 11:34:05 +01:00
|
|
|
return data
|
|
|
|
|
2016-09-10 18:49:38 +02:00
|
|
|
|
2015-02-12 18:48:14 +01:00
|
|
|
class MotionSerializer(ModelSerializer):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
|
|
|
Serializer for motion.models.Motion objects.
|
|
|
|
"""
|
2015-02-12 18:48:14 +01:00
|
|
|
active_version = PrimaryKeyRelatedField(read_only=True)
|
2016-07-29 23:33:47 +02:00
|
|
|
comments = MotionCommentsJSONSerializerField(required=False)
|
2015-01-24 16:35:50 +01:00
|
|
|
log_messages = MotionLogSerializer(many=True, read_only=True)
|
2015-04-30 19:13:28 +02:00
|
|
|
polls = MotionPollSerializer(many=True, read_only=True)
|
|
|
|
reason = CharField(allow_blank=True, required=False, write_only=True)
|
2016-12-09 18:00:45 +01:00
|
|
|
state_required_permission_to_see = SerializerMethodField()
|
2015-04-30 19:13:28 +02:00
|
|
|
text = CharField(write_only=True)
|
|
|
|
title = CharField(max_length=255, write_only=True)
|
|
|
|
versions = MotionVersionSerializer(many=True, read_only=True)
|
2015-11-03 10:03:44 +01:00
|
|
|
workflow_id = IntegerField(
|
|
|
|
min_value=1,
|
|
|
|
required=False,
|
|
|
|
validators=[validate_workflow_field],
|
|
|
|
write_only=True)
|
2015-01-24 16:35:50 +01:00
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = Motion
|
|
|
|
fields = (
|
2015-02-04 00:08:38 +01:00
|
|
|
'id',
|
2015-01-24 16:35:50 +01:00
|
|
|
'identifier',
|
2015-04-30 19:13:28 +02:00
|
|
|
'title',
|
|
|
|
'text',
|
|
|
|
'reason',
|
2015-01-24 16:35:50 +01:00
|
|
|
'versions',
|
|
|
|
'active_version',
|
2015-04-30 19:13:28 +02:00
|
|
|
'parent',
|
|
|
|
'category',
|
2016-10-01 20:42:44 +02:00
|
|
|
'motion_block',
|
2016-07-13 14:45:40 +02:00
|
|
|
'origin',
|
2015-04-30 19:13:28 +02:00
|
|
|
'submitters',
|
|
|
|
'supporters',
|
2016-07-29 23:33:47 +02:00
|
|
|
'comments',
|
2015-01-24 16:35:50 +01:00
|
|
|
'state',
|
2016-12-09 18:00:45 +01:00
|
|
|
'state_required_permission_to_see',
|
2015-11-03 10:03:44 +01:00
|
|
|
'workflow_id',
|
2016-09-03 21:43:11 +02:00
|
|
|
'recommendation',
|
2015-04-30 19:13:28 +02:00
|
|
|
'tags',
|
2015-01-24 16:35:50 +01:00
|
|
|
'attachments',
|
|
|
|
'polls',
|
2015-10-24 19:02:43 +02:00
|
|
|
'agenda_item_id',
|
2015-01-24 16:35:50 +01:00
|
|
|
'log_messages',)
|
2016-09-03 21:43:11 +02:00
|
|
|
read_only_fields = ('state', 'recommendation',) # Some other fields are also read_only. See definitions above.
|
2015-01-24 16:35:50 +01:00
|
|
|
|
2017-01-20 11:34:05 +01:00
|
|
|
def validate(self, data):
|
2017-03-28 08:27:54 +02:00
|
|
|
if 'text'in data:
|
|
|
|
data['text'] = validate_html(data['text'])
|
|
|
|
if 'reason' in data:
|
|
|
|
data['reason'] = validate_html(data['reason'])
|
2017-01-20 11:34:05 +01:00
|
|
|
validated_comments = []
|
|
|
|
for comment in data.get('comments', []):
|
|
|
|
validated_comments.append(validate_html(comment))
|
|
|
|
data['comments'] = validated_comments
|
|
|
|
return data
|
|
|
|
|
2015-04-30 19:13:28 +02:00
|
|
|
@transaction.atomic
|
|
|
|
def create(self, validated_data):
|
|
|
|
"""
|
|
|
|
Customized method to create a new motion from some data.
|
|
|
|
"""
|
|
|
|
motion = Motion()
|
|
|
|
motion.title = validated_data['title']
|
|
|
|
motion.text = validated_data['text']
|
|
|
|
motion.reason = validated_data.get('reason', '')
|
|
|
|
motion.identifier = validated_data.get('identifier')
|
|
|
|
motion.category = validated_data.get('category')
|
2016-10-01 20:42:44 +02:00
|
|
|
motion.motion_block = validated_data.get('motion_block')
|
2016-07-13 14:45:40 +02:00
|
|
|
motion.origin = validated_data.get('origin', '')
|
2016-07-29 23:33:47 +02:00
|
|
|
motion.comments = validated_data.get('comments')
|
2016-08-19 21:03:14 +02:00
|
|
|
motion.parent = validated_data.get('parent')
|
2015-11-03 10:03:44 +01:00
|
|
|
motion.reset_state(validated_data.get('workflow_id'))
|
2015-04-30 19:13:28 +02:00
|
|
|
motion.save()
|
2015-07-06 09:19:42 +02:00
|
|
|
if validated_data.get('submitters'):
|
2015-04-30 19:13:28 +02:00
|
|
|
motion.submitters.add(*validated_data['submitters'])
|
2016-09-08 09:44:47 +02:00
|
|
|
elif validated_data['request_user'].is_authenticated():
|
2015-04-30 19:13:28 +02:00
|
|
|
motion.submitters.add(validated_data['request_user'])
|
2015-07-06 09:19:42 +02:00
|
|
|
motion.supporters.add(*validated_data.get('supporters', []))
|
|
|
|
motion.attachments.add(*validated_data.get('attachments', []))
|
|
|
|
motion.tags.add(*validated_data.get('tags', []))
|
2015-04-30 19:13:28 +02:00
|
|
|
return motion
|
|
|
|
|
|
|
|
@transaction.atomic
|
|
|
|
def update(self, motion, validated_data):
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
2015-04-30 19:13:28 +02:00
|
|
|
Customized method to update a motion.
|
2015-01-24 16:35:50 +01:00
|
|
|
"""
|
2016-10-01 20:42:44 +02:00
|
|
|
# Identifier, category, motion_block, origin and comments.
|
|
|
|
for key in ('identifier', 'category', 'motion_block', 'origin', 'comments'):
|
2015-04-30 19:13:28 +02:00
|
|
|
if key in validated_data.keys():
|
|
|
|
setattr(motion, key, validated_data[key])
|
|
|
|
|
|
|
|
# Workflow.
|
2015-11-03 10:03:44 +01:00
|
|
|
workflow_id = validated_data.get('workflow_id')
|
|
|
|
if workflow_id is not None and workflow_id != motion.workflow:
|
|
|
|
motion.reset_state(workflow_id)
|
2015-04-30 19:13:28 +02:00
|
|
|
|
|
|
|
# Decide if a new version is saved to the database.
|
|
|
|
if (motion.state.versioning and
|
|
|
|
not validated_data.get('disable_versioning', False)): # TODO
|
|
|
|
version = motion.get_new_version()
|
|
|
|
else:
|
|
|
|
version = motion.get_last_version()
|
|
|
|
|
|
|
|
# Title, text, reason.
|
|
|
|
for key in ('title', 'text', 'reason'):
|
|
|
|
if key in validated_data.keys():
|
|
|
|
setattr(version, key, validated_data[key])
|
|
|
|
|
|
|
|
motion.save(use_version=version)
|
|
|
|
|
|
|
|
# Submitters, supporters, attachments and tags
|
|
|
|
for key in ('submitters', 'supporters', 'attachments', 'tags'):
|
|
|
|
if key in validated_data.keys():
|
|
|
|
attr = getattr(motion, key)
|
|
|
|
attr.clear()
|
|
|
|
attr.add(*validated_data[key])
|
|
|
|
|
|
|
|
return motion
|
2016-12-09 18:00:45 +01:00
|
|
|
|
|
|
|
def get_state_required_permission_to_see(self, motion):
|
|
|
|
"""
|
|
|
|
Returns the permission (as string) that is required for non
|
|
|
|
managers that are not submitters to see this motion in this state.
|
|
|
|
|
|
|
|
Hint: Most states have and empty string here so this restriction is
|
|
|
|
disabled.
|
|
|
|
"""
|
|
|
|
return motion.state.required_permission_to_see
|