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():
|
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()
|
||||||
|
@ -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)
|
||||||
|
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
Loading…
Reference in New Issue
Block a user