api changes to allow some edits on finished polls
This commit is contained in:
parent
fafb81daca
commit
72ff1b1f09
@ -389,18 +389,28 @@ class AssignmentPollViewSet(BasePollViewSet):
|
||||
for option_id, vote in options_data.items():
|
||||
option = options.get(pk=int(option_id))
|
||||
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 (
|
||||
AssignmentPoll.POLLMETHOD_YN,
|
||||
AssignmentPoll.POLLMETHOD_YNA,
|
||||
):
|
||||
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:
|
||||
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
|
||||
first_option = options.first()
|
||||
|
@ -1180,10 +1180,16 @@ class MotionPollViewSet(BasePollViewSet):
|
||||
A = self.parse_decimal_value(data.get("A"), min_value=-2)
|
||||
|
||||
option = poll.options.get()
|
||||
MotionVote.objects.create(option=option, value="Y", weight=Y)
|
||||
MotionVote.objects.create(option=option, value="N", weight=N)
|
||||
vote, _ = MotionVote.objects.get_or_create(option=option, value="Y")
|
||||
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:
|
||||
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:
|
||||
poll.votesvalid = self.parse_decimal_value(data["votesvalid"], min_value=-2)
|
||||
|
@ -17,6 +17,8 @@ from .models import BasePoll
|
||||
|
||||
|
||||
class BasePollViewSet(ModelViewSet):
|
||||
valid_update_keys = ["majority_method", "onehundred_percent_base"]
|
||||
|
||||
def check_view_permissions(self):
|
||||
"""
|
||||
the vote view is checked seperately. For all other views manage permissions
|
||||
@ -31,18 +33,28 @@ class BasePollViewSet(ModelViewSet):
|
||||
poll = serializer.save()
|
||||
poll.create_options()
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
def update(self, request, *args, **kwargs):
|
||||
"""
|
||||
Customized view endpoint to update a motion poll.
|
||||
"""
|
||||
poll = self.get_object()
|
||||
|
||||
if poll.state != BasePoll.STATE_CREATED:
|
||||
raise ValidationError(
|
||||
{"detail": "You can just edit a poll if it was not started."}
|
||||
)
|
||||
partial = kwargs.get("partial", False)
|
||||
serializer = self.get_serializer(poll, data=request.data, partial=partial)
|
||||
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"])
|
||||
def start(self, request, pk):
|
||||
@ -118,8 +130,6 @@ class BasePollViewSet(ModelViewSet):
|
||||
For motion polls: Just "Y", "N" or "A" (if pollmethod is "YNA")
|
||||
"""
|
||||
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):
|
||||
self.permission_denied(request)
|
||||
@ -129,23 +139,37 @@ class BasePollViewSet(ModelViewSet):
|
||||
if not self.has_manage_permissions():
|
||||
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)
|
||||
# special: change the poll state to finished.
|
||||
poll.state = BasePoll.STATE_FINISHED
|
||||
poll.save()
|
||||
|
||||
elif poll.type == BasePoll.TYPE_NAMED:
|
||||
self.assert_can_vote(poll, request)
|
||||
self.handle_named_vote(request.data, poll, request.user)
|
||||
poll.voted.add(request.user)
|
||||
else:
|
||||
if poll.state != BasePoll.STATE_STARTED:
|
||||
raise ValidationError(
|
||||
{"detail": "You cannot vote for an unstarted poll"}
|
||||
)
|
||||
|
||||
elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS:
|
||||
self.assert_can_vote(poll, request)
|
||||
if poll.type == BasePoll.TYPE_NAMED:
|
||||
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():
|
||||
self.permission_denied(request)
|
||||
self.handle_pseudoanonymous_vote(request.data, poll)
|
||||
poll.voted.add(request.user)
|
||||
elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS:
|
||||
self.assert_can_vote(poll, request)
|
||||
|
||||
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
|
||||
return Response()
|
||||
|
@ -535,6 +535,40 @@ class UpdateAssignmentPoll(TestCase):
|
||||
self.assertTrue(poll.allow_multiple_votes_per_candidate)
|
||||
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):
|
||||
def advancedSetUp(self):
|
||||
@ -721,6 +755,39 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
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):
|
||||
def create_poll(self):
|
||||
|
@ -369,6 +369,40 @@ class UpdateMotionPoll(TestCase):
|
||||
poll = MotionPoll.objects.get()
|
||||
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):
|
||||
def setUp(self):
|
||||
@ -470,6 +504,43 @@ class VoteMotionPollAnalog(TestCase):
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
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):
|
||||
def setUp(self):
|
||||
|
Loading…
Reference in New Issue
Block a user