api changes to allow some edits on finished polls

This commit is contained in:
jsangmeister 2019-11-27 15:44:17 +01:00 committed by FinnStutzenstein
parent fafb81daca
commit 72ff1b1f09
5 changed files with 202 additions and 24 deletions

View File

@ -389,18 +389,28 @@ class AssignmentPollViewSet(BasePollViewSet):
for option_id, vote in options_data.items(): for option_id, vote in options_data.items():
option = options.get(pk=int(option_id)) option = options.get(pk=int(option_id))
Y = self.parse_decimal_value(vote["Y"], min_value=-2) Y = self.parse_decimal_value(vote["Y"], min_value=-2)
AssignmentVote.objects.create(option=option, value="Y", weight=Y) vote_obj, _ = AssignmentVote.objects.get_or_create(option=option, value="Y")
vote_obj.weight = Y
vote_obj.save()
if poll.pollmethod in ( if poll.pollmethod in (
AssignmentPoll.POLLMETHOD_YN, AssignmentPoll.POLLMETHOD_YN,
AssignmentPoll.POLLMETHOD_YNA, AssignmentPoll.POLLMETHOD_YNA,
): ):
N = self.parse_decimal_value(vote["N"], min_value=-2) N = self.parse_decimal_value(vote["N"], min_value=-2)
AssignmentVote.objects.create(option=option, value="N", weight=N) vote_obj, _ = AssignmentVote.objects.get_or_create(
option=option, value="N"
)
vote_obj.weight = N
vote_obj.save()
if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA: if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA:
A = self.parse_decimal_value(vote["A"], min_value=-2) A = self.parse_decimal_value(vote["A"], min_value=-2)
AssignmentVote.objects.create(option=option, value="A", weight=A) vote_obj, _ = AssignmentVote.objects.get_or_create(
option=option, value="A"
)
vote_obj.weight = A
vote_obj.save()
# Create votes for global no and global abstain # Create votes for global no and global abstain
first_option = options.first() first_option = options.first()

View File

@ -1180,10 +1180,16 @@ class MotionPollViewSet(BasePollViewSet):
A = self.parse_decimal_value(data.get("A"), min_value=-2) A = self.parse_decimal_value(data.get("A"), min_value=-2)
option = poll.options.get() option = poll.options.get()
MotionVote.objects.create(option=option, value="Y", weight=Y) vote, _ = MotionVote.objects.get_or_create(option=option, value="Y")
MotionVote.objects.create(option=option, value="N", weight=N) vote.weight = Y
vote.save()
vote, _ = MotionVote.objects.get_or_create(option=option, value="N")
vote.weight = N
vote.save()
if poll.pollmethod == MotionPoll.POLLMETHOD_YNA: if poll.pollmethod == MotionPoll.POLLMETHOD_YNA:
MotionVote.objects.create(option=option, value="A", weight=A) vote, _ = MotionVote.objects.get_or_create(option=option, value="A")
vote.weight = A
vote.save()
if "votesvalid" in data: if "votesvalid" in data:
poll.votesvalid = self.parse_decimal_value(data["votesvalid"], min_value=-2) poll.votesvalid = self.parse_decimal_value(data["votesvalid"], min_value=-2)

View File

@ -17,6 +17,8 @@ from .models import BasePoll
class BasePollViewSet(ModelViewSet): class BasePollViewSet(ModelViewSet):
valid_update_keys = ["majority_method", "onehundred_percent_base"]
def check_view_permissions(self): def check_view_permissions(self):
""" """
the vote view is checked seperately. For all other views manage permissions the vote view is checked seperately. For all other views manage permissions
@ -31,18 +33,28 @@ class BasePollViewSet(ModelViewSet):
poll = serializer.save() poll = serializer.save()
poll.create_options() poll.create_options()
def update(self, *args, **kwargs): def update(self, request, *args, **kwargs):
""" """
Customized view endpoint to update a motion poll. Customized view endpoint to update a motion poll.
""" """
poll = self.get_object() poll = self.get_object()
if poll.state != BasePoll.STATE_CREATED: partial = kwargs.get("partial", False)
raise ValidationError( serializer = self.get_serializer(poll, data=request.data, partial=partial)
{"detail": "You can just edit a poll if it was not started."} serializer.is_valid(raise_exception=False)
)
return super().update(*args, **kwargs) if poll.state != BasePoll.STATE_CREATED:
invalid_keys = set(serializer.validated_data.keys()) - set(
self.valid_update_keys
)
if len(invalid_keys):
raise ValidationError(
{
"detail": f"The poll is not in the created state. You can only edit: {', '.join(self.valid_update_keys)}"
}
)
return super().update(request, *args, **kwargs)
@detail_route(methods=["POST"]) @detail_route(methods=["POST"])
def start(self, request, pk): def start(self, request, pk):
@ -118,8 +130,6 @@ class BasePollViewSet(ModelViewSet):
For motion polls: Just "Y", "N" or "A" (if pollmethod is "YNA") For motion polls: Just "Y", "N" or "A" (if pollmethod is "YNA")
""" """
poll = self.get_object() poll = self.get_object()
if poll.state != BasePoll.STATE_STARTED:
raise ValidationError({"detail": "You cannot vote for an unstarted poll"})
if isinstance(request.user, AnonymousUser): if isinstance(request.user, AnonymousUser):
self.permission_denied(request) self.permission_denied(request)
@ -129,23 +139,37 @@ class BasePollViewSet(ModelViewSet):
if not self.has_manage_permissions(): if not self.has_manage_permissions():
self.permission_denied(request) self.permission_denied(request)
if (
poll.state != BasePoll.STATE_STARTED
and poll.state != BasePoll.STATE_FINISHED
):
raise ValidationError(
{"detail": "You cannot vote for a poll in this state"}
)
self.handle_analog_vote(request.data, poll, request.user) self.handle_analog_vote(request.data, poll, request.user)
# special: change the poll state to finished. # special: change the poll state to finished.
poll.state = BasePoll.STATE_FINISHED poll.state = BasePoll.STATE_FINISHED
poll.save() poll.save()
elif poll.type == BasePoll.TYPE_NAMED: else:
self.assert_can_vote(poll, request) if poll.state != BasePoll.STATE_STARTED:
self.handle_named_vote(request.data, poll, request.user) raise ValidationError(
poll.voted.add(request.user) {"detail": "You cannot vote for an unstarted poll"}
)
elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS: if poll.type == BasePoll.TYPE_NAMED:
self.assert_can_vote(poll, request) self.assert_can_vote(poll, request)
self.handle_named_vote(request.data, poll, request.user)
poll.voted.add(request.user)
if request.user in poll.voted.all(): elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS:
self.permission_denied(request) self.assert_can_vote(poll, request)
self.handle_pseudoanonymous_vote(request.data, poll)
poll.voted.add(request.user) if request.user in poll.voted.all():
self.permission_denied(request)
self.handle_pseudoanonymous_vote(request.data, poll)
poll.voted.add(request.user)
inform_changed_data(poll) # needed for the changed voted relation inform_changed_data(poll) # needed for the changed voted relation
return Response() return Response()

View File

@ -535,6 +535,40 @@ class UpdateAssignmentPoll(TestCase):
self.assertTrue(poll.allow_multiple_votes_per_candidate) self.assertTrue(poll.allow_multiple_votes_per_candidate)
self.assertEqual(poll.votes_amount, 42) self.assertEqual(poll.votes_amount, 42)
def test_patch_majority_method_state_not_created(self):
self.poll.state = 2
self.poll.save()
response = self.client.patch(
reverse("assignmentpoll-detail", args=[self.poll.pk]),
{"majority_method": "two_thirds"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
self.assertEqual(poll.majority_method, "two_thirds")
def test_patch_100_percent_base_state_not_created(self):
self.poll.state = 2
self.poll.save()
response = self.client.patch(
reverse("assignmentpoll-detail", args=[self.poll.pk]),
{"onehundred_percent_base": "cast"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
self.assertEqual(poll.onehundred_percent_base, "cast")
def test_patch_wrong_100_percent_base_state_not_created(self):
self.poll.state = 2
self.poll.pollmethod = AssignmentPoll.POLLMETHOD_YN
self.poll.save()
response = self.client.patch(
reverse("assignmentpoll-detail", args=[self.poll.pk]),
{"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
self.assertEqual(poll.onehundred_percent_base, "YN")
class VoteAssignmentPollBaseTestClass(TestCase): class VoteAssignmentPollBaseTestClass(TestCase):
def advancedSetUp(self): def advancedSetUp(self):
@ -721,6 +755,39 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists()) self.assertFalse(AssignmentVote.objects.exists())
def test_vote_state_finished(self):
self.start_poll()
self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {"1": {"Y": 5, "N": 0, "A": 1}},
"votesvalid": "-2",
"votesinvalid": "1",
"votescast": "-1",
},
)
self.poll.state = 3
self.poll.save()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {"1": {"Y": 2, "N": 2, "A": 2}},
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "3",
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
self.assertEqual(poll.votesvalid, Decimal("4.64"))
self.assertEqual(poll.votesinvalid, Decimal("-2"))
self.assertEqual(poll.votescast, Decimal("3"))
self.assertEqual(poll.get_votes().count(), 3)
option = poll.options.get()
self.assertEqual(option.yes, Decimal("2"))
self.assertEqual(option.no, Decimal("2"))
self.assertEqual(option.abstain, Decimal("2"))
class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass): class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
def create_poll(self): def create_poll(self):

View File

@ -369,6 +369,40 @@ class UpdateMotionPoll(TestCase):
poll = MotionPoll.objects.get() poll = MotionPoll.objects.get()
self.assertEqual(poll.title, "test_title_beeFaihuNae1vej2ai8m") self.assertEqual(poll.title, "test_title_beeFaihuNae1vej2ai8m")
def test_patch_majority_method_state_not_created(self):
self.poll.state = 2
self.poll.save()
response = self.client.patch(
reverse("motionpoll-detail", args=[self.poll.pk]),
{"majority_method": "two_thirds"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.majority_method, "two_thirds")
def test_patch_100_percent_base_state_not_created(self):
self.poll.state = 2
self.poll.save()
response = self.client.patch(
reverse("motionpoll-detail", args=[self.poll.pk]),
{"onehundred_percent_base": "cast"},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.onehundred_percent_base, "cast")
def test_patch_wrong_100_percent_base_state_not_created(self):
self.poll.state = 2
self.poll.pollmethod = MotionPoll.POLLMETHOD_YN
self.poll.save()
response = self.client.patch(
reverse("motionpoll-detail", args=[self.poll.pk]),
{"onehundred_percent_base": MotionPoll.PERCENT_BASE_YNA},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.onehundred_percent_base, "YN")
class VoteMotionPollAnalog(TestCase): class VoteMotionPollAnalog(TestCase):
def setUp(self): def setUp(self):
@ -470,6 +504,43 @@ class VoteMotionPollAnalog(TestCase):
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST) self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists()) self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_state_finished(self):
self.start_poll()
self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{
"Y": "3",
"N": "1",
"A": "5",
"votesvalid": "-2",
"votesinvalid": "1",
"votescast": "-1",
},
)
self.poll.state = 3
self.poll.save()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{
"Y": "1",
"N": "2.35",
"A": "-1",
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "3",
},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.votesvalid, Decimal("4.64"))
self.assertEqual(poll.votesinvalid, Decimal("-2"))
self.assertEqual(poll.votescast, Decimal("3"))
self.assertEqual(poll.get_votes().count(), 3)
option = poll.options.get()
self.assertEqual(option.yes, Decimal("1"))
self.assertEqual(option.no, Decimal("2.35"))
self.assertEqual(option.abstain, Decimal("-1"))
class VoteMotionPollNamed(TestCase): class VoteMotionPollNamed(TestCase):
def setUp(self): def setUp(self):