Add vote delegation on server side

Add user_has_voted_for_delegations. Add tests

Prevent self delegation

Make delegated_user visible
This commit is contained in:
Joshua Sangmeister 2020-09-10 12:09:05 +02:00 committed by Finn Stutzenstein
parent ed2c298928
commit 3ac8569712
No known key found for this signature in database
GPG Key ID: 9042F605C6324654
17 changed files with 923 additions and 202 deletions

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.16 on 2020-09-10 11:02
from django.conf import settings
from django.db import migrations, models
import openslides.utils.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("assignments", "0014_remove_deprecated_slides"),
]
operations = [
migrations.AddField(
model_name="assignmentvote",
name="delegated_user",
field=models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
related_name="assignmentvote_delegated_votes",
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@ -289,7 +289,7 @@ class AssignmentPollViewSet(BasePollViewSet):
poll.db_amount_global_no = Decimal(0)
poll.save()
def handle_analog_vote(self, data, poll, user):
def handle_analog_vote(self, data, poll):
for field in ["votesvalid", "votesinvalid", "votescast"]:
setattr(poll, field, data[field])
@ -336,7 +336,7 @@ class AssignmentPollViewSet(BasePollViewSet):
poll.save()
def validate_vote_data(self, data, poll, user):
def validate_vote_data(self, data, poll):
"""
Request data:
analog:
@ -478,10 +478,12 @@ class AssignmentPollViewSet(BasePollViewSet):
options_data = data
def create_votes_type_votes(self, data, poll, vote_weight, vote_user):
def create_votes_type_votes(self, data, poll, vote_weight, vote_user, request_user):
"""
Helper function for handle_(named|pseudoanonymous)_vote
Assumes data is already validated
vote_user is the user whose vote is given
request_user is the user who gives the vote, may be a delegate
"""
options = poll.get_options()
if isinstance(data, dict):
@ -495,30 +497,46 @@ class AssignmentPollViewSet(BasePollViewSet):
if config["users_activate_vote_weight"]:
weight *= vote_weight
vote = AssignmentVote.objects.create(
option=option, user=vote_user, weight=weight, value="Y"
option=option,
user=vote_user,
delegated_user=request_user,
weight=weight,
value="Y",
)
inform_changed_data(vote, no_delete_on_restriction=True)
else: # global_no or global_abstain
option = options[0]
weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1)
vote = AssignmentVote.objects.create(
option=option, user=vote_user, weight=weight, value=data
option=option,
user=vote_user,
delegated_user=request_user,
weight=weight,
value=data,
)
inform_changed_data(vote, no_delete_on_restriction=True)
inform_changed_data(option)
inform_changed_data(poll)
def create_votes_types_yn_yna(self, data, poll, vote_weight, vote_user):
def create_votes_types_yn_yna(
self, data, poll, vote_weight, vote_user, request_user
):
"""
Helper function for handle_(named|pseudoanonymous)_vote
Assumes data is already validated
vote_user is the user whose vote is given
request_user is the user who gives the vote, may be a delegate
"""
options = poll.get_options()
weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1)
for option_id, result in data.items():
option = options.get(pk=option_id)
vote = AssignmentVote.objects.create(
option=option, user=vote_user, value=result, weight=weight
option=option,
user=vote_user,
delegated_user=request_user,
value=result,
weight=weight,
)
inform_changed_data(vote, no_delete_on_restriction=True)
inform_changed_data(option, no_delete_on_restriction=True)
@ -527,24 +545,28 @@ class AssignmentPollViewSet(BasePollViewSet):
VotedModel = AssignmentPoll.voted.through
VotedModel.objects.create(assignmentpoll=poll, user=user)
def handle_named_vote(self, data, poll, user):
def handle_named_vote(self, data, poll, vote_user, request_user):
if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
self.create_votes_type_votes(data, poll, user.vote_weight, user)
self.create_votes_type_votes(
data, poll, vote_user.vote_weight, vote_user, request_user
)
elif poll.pollmethod in (
AssignmentPoll.POLLMETHOD_YN,
AssignmentPoll.POLLMETHOD_YNA,
):
self.create_votes_types_yn_yna(data, poll, user.vote_weight, user)
self.create_votes_types_yn_yna(
data, poll, vote_user.vote_weight, vote_user, request_user
)
def handle_pseudoanonymous_vote(self, data, poll, user):
if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
self.create_votes_type_votes(data, poll, user.vote_weight, None)
self.create_votes_type_votes(data, poll, user.vote_weight, None, None)
elif poll.pollmethod in (
AssignmentPoll.POLLMETHOD_YN,
AssignmentPoll.POLLMETHOD_YNA,
):
self.create_votes_types_yn_yna(data, poll, user.vote_weight, None)
self.create_votes_types_yn_yna(data, poll, user.vote_weight, None, None)
def convert_option_data(self, poll, data):
poll_options = poll.get_options()

View File

@ -0,0 +1,29 @@
# Generated by Django 2.2.16 on 2020-09-10 11:02
from django.conf import settings
from django.db import migrations, models
import openslides.utils.models
class Migration(migrations.Migration):
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
("motions", "0036_rename_verbose_poll_types"),
]
operations = [
migrations.AddField(
model_name="motionvote",
name="delegated_user",
field=models.ForeignKey(
blank=True,
default=None,
null=True,
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
related_name="motionvote_delegated_votes",
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@ -1190,7 +1190,7 @@ class MotionPollViewSet(BasePollViewSet):
return result
def handle_analog_vote(self, data, poll, user):
def handle_analog_vote(self, data, poll):
option = poll.options.get()
vote, _ = MotionVote.objects.get_or_create(option=option, value="Y")
vote.weight = data["Y"]
@ -1209,7 +1209,7 @@ class MotionPollViewSet(BasePollViewSet):
poll.save()
def validate_vote_data(self, data, poll, user):
def validate_vote_data(self, data, poll):
"""
Request data for analog:
{ "Y": <amount>, "N": <amount>, ["A": <amount>],
@ -1240,15 +1240,25 @@ class MotionPollViewSet(BasePollViewSet):
VotedModel = MotionPoll.voted.through
VotedModel.objects.create(motionpoll=poll, user=user)
def handle_named_vote(self, data, poll, user):
self.handle_named_and_pseudoanonymous_vote(data, user, user, poll)
def handle_named_vote(self, data, poll, vote_user, request_user):
self.handle_named_and_pseudoanonymous_vote(
data,
poll,
weight_user=vote_user,
vote_user=vote_user,
request_user=request_user,
)
def handle_pseudoanonymous_vote(self, data, poll, user):
self.handle_named_and_pseudoanonymous_vote(data, user, None, poll)
self.handle_named_and_pseudoanonymous_vote(data, poll, user, None, None)
def handle_named_and_pseudoanonymous_vote(self, data, weight_user, vote_user, poll):
def handle_named_and_pseudoanonymous_vote(
self, data, poll, weight_user, vote_user, request_user
):
option = poll.options.get()
vote = MotionVote.objects.create(user=vote_user, option=option)
vote = MotionVote.objects.create(
user=vote_user, delegated_user=request_user, option=option
)
vote.value = data
vote.weight = (
weight_user.vote_weight

View File

@ -2,8 +2,13 @@ import json
from typing import Any, Dict, List
from ..poll.views import BasePoll
from ..utils import logging
from ..utils.access_permissions import BaseAccessPermissions
from ..utils.auth import async_has_perm
from ..utils.auth import async_has_perm, user_collection_string
from ..utils.cache import element_cache
logger = logging.getLogger(__name__)
class BaseVoteAccessPermissions(BaseAccessPermissions):
@ -26,6 +31,7 @@ class BaseVoteAccessPermissions(BaseAccessPermissions):
for vote in full_data
if vote["pollstate"] == BasePoll.STATE_PUBLISHED
or vote["user_id"] == user_id
or vote["delegated_user_id"] == user_id
]
return data
@ -71,8 +77,24 @@ class BasePollAccessPermissions(BaseAccessPermissions):
"""
# add has_voted for all users to check whether op has voted
# also fill user_has_voted_for_delegations with all users for which he has
# already voted
user_data = await element_cache.get_element_data(
user_collection_string, user_id
)
if user_data is None:
logger.error(f"Could not find userdata for {user_id}")
vote_delegated_from_ids = set()
else:
vote_delegated_from_ids = set(user_data["vote_delegated_from_users_id"])
for poll in full_data:
poll["user_has_voted"] = user_id in poll["voted_id"]
voted_ids = set(poll["voted_id"])
voted_for_delegations = list(
vote_delegated_from_ids.intersection(voted_ids)
)
poll["user_has_voted_for_delegations"] = voted_for_delegations
if await async_has_perm(user_id, self.manage_permission):
data = full_data

View File

@ -29,6 +29,14 @@ class BaseVote(models.Model):
blank=True,
on_delete=SET_NULL_AND_AUTOUPDATE,
)
delegated_user = models.ForeignKey(
settings.AUTH_USER_MODEL,
default=None,
null=True,
blank=True,
on_delete=SET_NULL_AND_AUTOUPDATE,
related_name="%(class)s_delegated_votes",
)
class Meta:
abstract = True

View File

@ -12,7 +12,15 @@ from ..utils.rest_api import (
from .models import BasePoll
BASE_VOTE_FIELDS = ("id", "weight", "value", "user", "option", "pollstate")
BASE_VOTE_FIELDS = (
"id",
"weight",
"value",
"user",
"delegated_user",
"option",
"pollstate",
)
class BaseVoteSerializer(ModelSerializer):

View File

@ -1,5 +1,6 @@
from textwrap import dedent
from django.contrib.auth import get_user_model
from django.contrib.auth.models import AnonymousUser
from django.db import transaction
from django.db.utils import IntegrityError
@ -104,8 +105,8 @@ class BasePollViewSet(ModelViewSet):
# convert user ids to option ids
self.convert_option_data(poll, vote_data)
self.validate_vote_data(vote_data, poll, request.user)
self.handle_analog_vote(vote_data, poll, request.user)
self.validate_vote_data(vote_data, poll)
self.handle_analog_vote(vote_data, poll)
if request.data.get("publish_immediately"):
poll.state = BasePoll.STATE_PUBLISHED
@ -198,25 +199,41 @@ class BasePollViewSet(ModelViewSet):
if isinstance(request.user, AnonymousUser):
self.permission_denied(request)
# check permissions based on poll type and handle requests
self.assert_can_vote(poll, request)
# data format is:
# { data: <vote_data>, [user_id: int] }
# if user_id is given, the operator votes for this user instead of himself
# user_id is ignored for analog polls
data = request.data
self.validate_vote_data(data, poll, request.user)
if "data" not in data:
raise ValidationError({"detail": "No data provided."})
vote_data = data["data"]
if "user_id" in data and poll.type != BasePoll.TYPE_ANALOG:
try:
vote_user = get_user_model().objects.get(pk=data["user_id"])
except get_user_model().DoesNotExist:
raise ValidationError({"detail": "The given user does not exist."})
else:
vote_user = request.user
# check permissions based on poll type and user
self.assert_can_vote(poll, request, vote_user)
# validate the vote data
self.validate_vote_data(vote_data, poll)
if poll.type == BasePoll.TYPE_ANALOG:
self.handle_analog_vote(data, poll, request.user)
if request.data.get("publish_immediately") == "1":
self.handle_analog_vote(vote_data, poll)
if vote_data.get("publish_immediately") == "1":
poll.state = BasePoll.STATE_PUBLISHED
else:
poll.state = BasePoll.STATE_FINISHED
poll.save()
elif poll.type == BasePoll.TYPE_NAMED:
self.handle_named_vote(data, poll, request.user)
self.handle_named_vote(vote_data, poll, vote_user, request.user)
elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS:
self.handle_pseudoanonymous_vote(data, poll, request.user)
self.handle_pseudoanonymous_vote(vote_data, poll, vote_user)
inform_changed_data(poll)
@ -231,13 +248,16 @@ class BasePollViewSet(ModelViewSet):
inform_changed_data(poll.get_votes(), final_data=True)
return Response()
def assert_can_vote(self, poll, request):
def assert_can_vote(self, poll, request, vote_user):
"""
Raises a permission denied, if the user is not allowed to vote (or has already voted).
Adds the user to the voted array, so this needs to be reverted on error!
Adds the user to the voted array, so this needs to be reverted if a later error happens!
Analog: has to have manage permissions
Named & Pseudoanonymous: has to be in a poll group and present
"""
if request.user != vote_user and request.user != vote_user.vote_delegated_to:
self.permission_denied(request)
if poll.type == BasePoll.TYPE_ANALOG:
if not self.has_manage_permissions():
self.permission_denied(request)
@ -246,14 +266,14 @@ class BasePollViewSet(ModelViewSet):
raise ValidationError("You can only vote on a started poll.")
if not request.user.is_present or not in_some_groups(
request.user.id,
vote_user.id,
list(poll.groups.values_list("pk", flat=True)),
exact=True,
):
self.permission_denied(request)
try:
self.add_user_to_voted_array(request.user, poll)
self.add_user_to_voted_array(vote_user, poll)
inform_changed_data(poll)
except IntegrityError:
raise ValidationError({"detail": "You have already voted"})
@ -292,20 +312,20 @@ class BasePollViewSet(ModelViewSet):
"""
raise NotImplementedError()
def validate_vote_data(self, data, poll, user):
def validate_vote_data(self, data, poll):
"""
To be implemented by subclass. Validates the data according to poll type and method and fields by validated versions.
Raises ValidationError on failure
"""
raise NotImplementedError()
def handle_analog_vote(self, data, poll, user):
def handle_analog_vote(self, data, poll):
"""
To be implemented by subclass. Handles the analog vote. Assumes data is validated
"""
raise NotImplementedError()
def handle_named_vote(self, data, poll, user):
def handle_named_vote(self, data, poll, vote_user, request_user):
"""
To be implemented by subclass. Handles the named vote. Assumes data is validated.
Needs to manage the voted-array per option.

View File

@ -58,6 +58,8 @@ class UserAccessPermissions(BaseAccessPermissions):
own_data_fields = set(little_data_fields)
own_data_fields.add("email")
own_data_fields.add("gender")
own_data_fields.add("vote_delegated_to_id")
own_data_fields.add("vote_delegated_from_users_id")
# Check user permissions.
if await async_has_perm(user_id, "users.can_see_name"):

View File

@ -0,0 +1,27 @@
# Generated by Django 2.2.16 on 2020-09-03 11:13
from django.conf import settings
from django.db import migrations, models
import openslides.utils.models
class Migration(migrations.Migration):
dependencies = [
("users", "0014_user_rename_permission"),
]
operations = [
migrations.AddField(
model_name="user",
name="vote_delegated_to",
field=models.ForeignKey(
blank=True,
null=True,
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
related_name="vote_delegated_from_users",
to=settings.AUTH_USER_MODEL,
),
),
]

View File

@ -22,7 +22,11 @@ from openslides.utils.manager import BaseManager
from ..core.config import config
from ..utils.auth import GROUP_ADMIN_PK
from ..utils.models import CASCADE_AND_AUTOUPDATE, RESTModelMixin
from ..utils.models import (
CASCADE_AND_AUTOUPDATE,
SET_NULL_AND_AUTOUPDATE,
RESTModelMixin,
)
from .access_permissions import (
GroupAccessPermissions,
PersonalNoteAccessPermissions,
@ -54,7 +58,8 @@ class UserManager(BaseUserManager):
queryset=Permission.objects.select_related("content_type"),
)
),
)
),
"vote_delegated_from_users",
)
def create_user(self, username, password, skip_autoupdate=False, **kwargs):
@ -164,6 +169,14 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
default=Decimal("1"), max_digits=15, decimal_places=6, null=False, blank=True
)
vote_delegated_to = models.ForeignKey(
"self",
on_delete=SET_NULL_AND_AUTOUPDATE,
null=True,
blank=True,
related_name="vote_delegated_from_users",
)
objects = UserManager()
class Meta:

View File

@ -7,6 +7,7 @@ from ..utils.rest_api import (
JSONField,
ModelSerializer,
RelatedField,
SerializerMethodField,
ValidationError,
)
from ..utils.validate import validate_html_strict
@ -36,6 +37,8 @@ USERCANSEEEXTRASERIALIZER_FIELDS = USERCANSEESERIALIZER_FIELDS + (
"comment",
"is_active",
"auth_type",
"vote_delegated_to_id",
"vote_delegated_from_users_id",
)
@ -57,11 +60,14 @@ class UserSerializer(ModelSerializer):
),
)
vote_delegated_from_users_id = SerializerMethodField()
class Meta:
model = User
fields = USERCANSEEEXTRASERIALIZER_FIELDS + (
"default_password",
"session_auth_hash",
"vote_delegated_to",
)
read_only_fields = ("last_email_send", "auth_type")
@ -119,6 +125,13 @@ class UserSerializer(ModelSerializer):
inform_changed_data(user)
return user
def get_vote_delegated_from_users_id(self, user):
# check needed to prevent errors on import since we only give an OrderedDict there
if hasattr(user, "vote_delegated_from_users"):
return [delegator.id for delegator in user.vote_delegated_from_users.all()]
else:
return []
class PermissionRelatedField(RelatedField):
"""

View File

@ -174,9 +174,77 @@ class UserViewSet(ModelViewSet):
):
request.data["username"] = user.username
# check that no chains are created with vote delegation
delegate_id = request.data.get("vote_delegated_to_id")
if delegate_id:
try:
delegate = User.objects.get(id=delegate_id)
except User.DoesNotExist:
raise ValidationError(
{
"detail": f"Vote delegation: The user with id {delegate_id} does not exist"
}
)
self.assert_no_self_delegation(user, [delegate_id])
self.assert_vote_not_delegated(delegate)
self.assert_has_no_delegated_votes(user)
inform_changed_data(delegate)
if user.vote_delegated_to:
inform_changed_data(user.vote_delegated_to)
# handle delegated_from field seperately since its a SerializerMethodField
new_delegation_ids = request.data.get("vote_delegated_from_users_id")
if "vote_delegated_from_users_id" in request.data:
del request.data["vote_delegated_from_users_id"]
response = super().update(request, *args, **kwargs)
# after rest of the request succeeded, handle delegation changes
if new_delegation_ids:
self.assert_no_self_delegation(user, new_delegation_ids)
self.assert_vote_not_delegated(user)
for id in new_delegation_ids:
delegation_user = User.objects.get(id=id)
self.assert_has_no_delegated_votes(delegation_user)
delegation_user.vote_delegated_to = user
delegation_user.save()
delegations_to_remove = user.vote_delegated_from_users.exclude(
id__in=(new_delegation_ids or [])
)
for old_delegation_user in delegations_to_remove:
old_delegation_user.vote_delegated_to = None
old_delegation_user.save()
# if only delegated_from was changed, we need an autoupdate for the operator
if new_delegation_ids or delegations_to_remove:
inform_changed_data(user)
return response
def assert_vote_not_delegated(self, user):
if user.vote_delegated_to:
raise ValidationError(
{
"detail": "You cannot delegate a vote to a user who has already delegated his vote."
}
)
def assert_has_no_delegated_votes(self, user):
if user.vote_delegated_from_users and len(user.vote_delegated_from_users.all()):
raise ValidationError(
{
"detail": "You cannot delegate a vote of a user who is already a delegate of another user."
}
)
def assert_no_self_delegation(self, user, delegate_ids):
if user.id in delegate_ids:
raise ValidationError({"detail": "You cannot delegate a vote to yourself."})
def destroy(self, request, *args, **kwargs):
"""
Customized view endpoint to delete an user.
@ -391,6 +459,7 @@ class UserViewSet(ModelViewSet):
data = serializer.prepare_password(serializer.data)
groups = data["groups_id"]
del data["groups_id"]
del data["vote_delegated_from_users_id"]
db_user = User(**data)
try:

View File

@ -770,13 +770,15 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {
"1": {"Y": "1", "N": "2.35", "A": "-1"},
"2": {"Y": "30", "N": "-2", "A": "8.93"},
"data": {
"options": {
"1": {"Y": "1", "N": "2.35", "A": "-1"},
"2": {"Y": "30", "N": "-2", "A": "8.93"},
},
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "-2",
},
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "-2",
},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -800,10 +802,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {"1": {"Y": "1", "N": "1", "A": "1"}},
"votesvalid": "-1.5",
"votesinvalid": "-2",
"votescast": "-2",
"data": {
"options": {"1": {"Y": "1", "N": "1", "A": "1"}},
"votesvalid": "-1.5",
"votesinvalid": "-2",
"votescast": "-2",
},
},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -814,10 +818,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {
"1": {"Y": "1", "N": "2.35", "A": "-1"},
"2": {"Y": "1", "N": "2.35", "A": "-1"},
}
"data": {
"options": {
"1": {"Y": "1", "N": "2.35", "A": "-1"},
"2": {"Y": "1", "N": "2.35", "A": "-1"},
}
},
},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -828,7 +834,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"options": {"1": {"Y": "1", "N": "2.35", "A": "-1"}}},
{"data": {"options": {"1": {"Y": "1", "N": "2.35", "A": "-1"}}}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
@ -839,9 +845,11 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {
"1": {"Y": "1", "N": "2.35", "A": "-1"},
"3": {"Y": "1", "N": "2.35", "A": "-1"},
"data": {
"options": {
"1": {"Y": "1", "N": "2.35", "A": "-1"},
"3": {"Y": "1", "N": "2.35", "A": "-1"},
}
}
},
)
@ -851,25 +859,31 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
def test_no_permissions(self):
self.start_poll()
self.make_admin_delegate()
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentVote.objects.exists())
def test_wrong_state(self):
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
def test_missing_data(self):
self.start_poll()
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
def test_wrong_data_format(self):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), [1, 2, 5]
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
@ -878,7 +892,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"options": [1, "string"]},
{"data": {"options": [1, "string"]}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
@ -887,7 +901,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"options": {"string": "some_other_string"}},
{"data": {"options": {"string": "some_other_string"}}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
@ -896,7 +910,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"options": {"1": [None]}},
{"data": {"options": {"1": [None]}}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
@ -907,7 +921,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
data = {"options": {"1": {"Y": "1", "N": "3", "A": "-1"}}}
del data["options"]["1"][value]
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), data
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": data}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
@ -917,10 +931,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{
"options": {"1": {"Y": 5, "N": 0, "A": 1}},
"votesvalid": "-2",
"votesinvalid": "1",
"votescast": "-1",
"data": {
"options": {"1": {"Y": 5, "N": 0, "A": 1}},
"votesvalid": "-2",
"votesinvalid": "1",
"votescast": "-1",
}
},
)
self.poll.state = 3
@ -928,10 +944,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
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",
"data": {
"options": {"1": {"Y": 2, "N": 2, "A": 2}},
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "3",
}
},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -973,7 +991,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "2": "N", "3": "A"},
{"data": {"1": "Y", "2": "N", "3": "A"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1007,7 +1025,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "2": "N", "3": "A"},
{"data": {"1": "Y", "2": "N", "3": "A"}},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
self.assertEqual(AssignmentVote.objects.count(), 3)
@ -1039,12 +1057,12 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "N"},
{"data": {"1": "N"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1064,7 +1082,8 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
option2 = self.poll2.options.get()
# Do request to poll with option2 (which is wrong...)
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {str(option2.id): "Y"}
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {str(option2.id): "Y"}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertEqual(AssignmentVote.objects.count(), 0)
@ -1081,7 +1100,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "2": "N"},
{"data": {"1": "Y", "2": "N"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1092,7 +1111,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1103,7 +1122,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "3": "N"},
{"data": {"1": "Y", "3": "N"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1114,7 +1133,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.make_admin_delegate()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
@ -1125,7 +1144,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
gclient = self.create_guest_client()
response = gclient.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
@ -1137,20 +1156,24 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.admin.save()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
def test_wrong_state(self):
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
def test_missing_data(self):
self.start_poll()
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(
response, status.HTTP_200_OK
) # new "feature" because of partial requests: empty requests work!
@ -1160,7 +1183,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
[1, 2, 5],
{"data": [1, 2, 5]},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1170,7 +1193,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "string"},
{"data": {"1": "string"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1180,7 +1203,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"id": "Y"},
{"data": {"id": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1190,7 +1213,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": [None]},
{"data": {"1": [None]}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1229,7 +1252,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 1, "2": 0},
{"data": {"1": 1, "2": 0}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1254,12 +1277,12 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 1, "2": 0},
{"data": {"1": 1, "2": 0}},
format="json",
)
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 0, "2": 1},
{"data": {"1": 0, "2": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1278,7 +1301,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.poll.save()
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), "N"
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "N"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
@ -1294,7 +1317,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.poll.save()
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), "N"
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "N"}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
@ -1305,7 +1328,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.poll.save()
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), "A"
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
@ -1321,7 +1344,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.poll.save()
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), "A"
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
@ -1331,7 +1354,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": -1},
{"data": {"1": -1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1342,7 +1365,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 2, "2": 1},
{"data": {"1": 2, "2": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1361,7 +1384,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 2, "2": 2},
{"data": {"1": 2, "2": 2}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1372,7 +1395,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 1, "2": 1, "3": 1},
{"data": {"1": 1, "2": 1, "3": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1381,7 +1404,9 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
def test_wrong_options(self):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"2": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"2": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
@ -1390,7 +1415,9 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
self.make_admin_delegate()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentVote.objects.exists())
@ -1399,7 +1426,9 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
gclient = self.create_guest_client()
response = gclient.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentVote.objects.exists())
@ -1409,21 +1438,27 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.admin.is_present = False
self.admin.save()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
def test_wrong_state(self):
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
def test_missing_data(self):
self.start_poll()
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
self.assertFalse(AssignmentVote.objects.exists())
@ -1431,7 +1466,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
[1, 2, 5],
{"data": [1, 2, 5]},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1441,7 +1476,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "string"},
{"data": {"1": "string"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1451,7 +1486,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"id": 1},
{"data": {"id": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1461,7 +1496,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": [None]},
{"data": {"1": [None]}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1495,7 +1530,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "2": "N", "3": "A"},
{"data": {"1": "Y", "2": "N", "3": "A"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1522,12 +1557,12 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "N"},
{"data": {"1": "N"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1541,7 +1576,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "2": "N"},
{"data": {"1": "Y", "2": "N"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1552,7 +1587,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1563,7 +1598,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y", "3": "N"},
{"data": {"1": "Y", "3": "N"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1574,7 +1609,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.make_admin_delegate()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
@ -1585,7 +1620,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
gclient = self.create_guest_client()
response = gclient.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
@ -1597,20 +1632,24 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.admin.save()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "Y"},
{"data": {"1": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
def test_wrong_state(self):
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
def test_missing_data(self):
self.start_poll()
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
self.assertFalse(AssignmentVote.objects.exists())
@ -1618,7 +1657,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
[1, 2, 5],
{"data": [1, 2, 5]},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1628,7 +1667,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "string"},
{"data": {"1": "string"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1638,7 +1677,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"id": "Y"},
{"data": {"id": "Y"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1648,7 +1687,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": [None]},
{"data": {"1": [None]}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1687,7 +1726,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 1, "2": 0},
{"data": {"1": 1, "2": 0}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1714,12 +1753,12 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 1, "2": 0},
{"data": {"1": 1, "2": 0}},
format="json",
)
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 0, "2": 1},
{"data": {"1": 0, "2": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1737,7 +1776,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": -1},
{"data": {"1": -1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1748,7 +1787,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 2, "2": 1},
{"data": {"1": 2, "2": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -1769,7 +1808,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 2, "2": 2},
{"data": {"1": 2, "2": 2}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1780,7 +1819,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": 1, "2": 1, "3": 1},
{"data": {"1": 1, "2": 1, "3": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1789,7 +1828,9 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
def test_wrong_options(self):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"2": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"2": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
@ -1798,7 +1839,9 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
self.make_admin_delegate()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentVote.objects.exists())
@ -1807,7 +1850,9 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
gclient = self.create_guest_client()
response = gclient.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentVote.objects.exists())
@ -1817,21 +1862,27 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.admin.is_present = False
self.admin.save()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
def test_wrong_state(self):
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"data": {"1": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(AssignmentVote.objects.exists())
def test_missing_data(self):
self.start_poll()
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
self.assertFalse(AssignmentVote.objects.exists())
@ -1839,7 +1890,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
[1, 2, 5],
{"data": {"data": [1, 2, 5]}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1849,7 +1900,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": "string"},
{"data": {"1": "string"}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1859,7 +1910,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"id": 1},
{"data": {"id": 1}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1869,7 +1920,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.start_poll()
response = self.client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]),
{"1": [None]},
{"data": {"1": [None]}},
format="json",
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
@ -1923,7 +1974,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
def test_vote(self):
response = self.user_client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": "A"}
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {"1": "A"}}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
@ -1956,6 +2007,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"votesinvalid": "0.000000",
"votesvalid": "1.000000",
"user_has_voted": False,
"user_has_voted_for_delegations": [],
"voted_id": [self.user.id],
},
"assignments/assignment-option:1": {
@ -1973,6 +2025,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"option_id": 1,
"pollstate": AssignmentPoll.STATE_STARTED,
"user_id": self.user.id,
"delegated_user_id": self.user.id,
"value": "A",
"weight": "1.000000",
},
@ -1989,6 +2042,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"option_id": 1,
"pollstate": AssignmentPoll.STATE_STARTED,
"user_id": self.user.id,
"delegated_user_id": self.user.id,
"value": "A",
"weight": "1.000000",
},
@ -2018,6 +2072,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"id": 1,
"votes_amount": 1,
"user_has_voted": user == self.user,
"user_has_voted_for_delegations": [],
},
)
@ -2073,6 +2128,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"votesinvalid": "0.000000",
"votesvalid": "1.000000",
"user_has_voted": user == self.user,
"user_has_voted_for_delegations": [],
"voted_id": [self.user.id],
},
)
@ -2084,6 +2140,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"weight": "1.000000",
"value": "A",
"user_id": 3,
"delegated_user_id": None,
"option_id": 1,
},
)
@ -2108,9 +2165,9 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
):
poll_type = AssignmentPoll.TYPE_PSEUDOANONYMOUS
def test_vote(self):
def test_votex(self):
response = self.user_client.post(
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": "A"}
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {"1": "A"}}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
@ -2136,6 +2193,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
"description": self.description,
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
"user_has_voted": False,
"user_has_voted_for_delegations": [],
"voted_id": [self.user.id],
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
@ -2159,6 +2217,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
"option_id": 1,
"pollstate": AssignmentPoll.STATE_STARTED,
"user_id": None,
"delegated_user_id": None,
"value": "A",
"weight": "1.000000",
},
@ -2190,6 +2249,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
"id": 1,
"votes_amount": 1,
"user_has_voted": user == self.user,
"user_has_voted_for_delegations": [],
},
)
@ -2245,6 +2305,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
"votesinvalid": "0.000000",
"votesvalid": "1.000000",
"user_has_voted": user == self.user,
"user_has_voted_for_delegations": [],
"voted_id": [self.user.id],
},
"assignments/assignment-vote:1": {
@ -2253,6 +2314,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
"weight": "1.000000",
"value": "A",
"user_id": None,
"delegated_user_id": None,
"option_id": 1,
},
"assignments/assignment-option:1": {

View File

@ -167,6 +167,7 @@ class CreateMotionPoll(TestCase):
"id": 1,
"voted_id": [],
"user_has_voted": False,
"user_has_voted_for_delegations": [],
},
)
self.assertEqual(autoupdate[1], [])
@ -610,12 +611,14 @@ class VoteMotionPollAnalog(TestCase):
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{
"Y": "1",
"N": "2.35",
"A": "-1",
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "-2",
"data": {
"Y": "1",
"N": "2.35",
"A": "-1",
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "-2",
},
},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -634,14 +637,23 @@ class VoteMotionPollAnalog(TestCase):
def test_vote_no_permissions(self):
self.start_poll()
self.make_admin_delegate()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_no_data(self):
self.start_poll()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]), {})
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_missing_data(self):
self.start_poll()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"Y": "4", "N": "22.6"}
reverse("motionpoll-vote", args=[self.poll.pk]),
{"data": {"Y": "4", "N": "22.6"}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -649,7 +661,7 @@ class VoteMotionPollAnalog(TestCase):
def test_vote_wrong_data_format(self):
self.start_poll()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -658,7 +670,7 @@ class VoteMotionPollAnalog(TestCase):
self.start_poll()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{"Y": "some string", "N": "-2", "A": "3"},
{"data": {"Y": "some string", "N": "-2", "A": "3"}},
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -668,12 +680,14 @@ class VoteMotionPollAnalog(TestCase):
self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{
"Y": "3",
"N": "1",
"A": "5",
"votesvalid": "-2",
"votesinvalid": "1",
"votescast": "-1",
"data": {
"Y": "3",
"N": "1",
"A": "5",
"votesvalid": "-2",
"votesinvalid": "1",
"votescast": "-1",
},
},
)
self.poll.state = 3
@ -681,12 +695,14 @@ class VoteMotionPollAnalog(TestCase):
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",
"data": {
"Y": "1",
"N": "2.35",
"A": "-1",
"votesvalid": "4.64",
"votesinvalid": "-2",
"votescast": "3",
},
},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
@ -749,7 +765,7 @@ class VoteMotionPollNamed(TestCase):
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
@ -773,7 +789,7 @@ class VoteMotionPollNamed(TestCase):
self.admin.vote_weight = weight = Decimal("3.5")
self.admin.save()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
@ -799,11 +815,11 @@ class VoteMotionPollNamed(TestCase):
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
poll = MotionPoll.objects.get()
@ -824,38 +840,35 @@ class VoteMotionPollNamed(TestCase):
config["general_system_enable_anonymous"] = True
guest_client = APIClient()
response = guest_client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "Y"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "Y"}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
# TODO: Move to unit tests
def test_not_set_vote_values(self):
with self.assertRaises(ValueError):
self.poll.votesvalid = Decimal("1")
with self.assertRaises(ValueError):
self.poll.votesinvalid = Decimal("1")
with self.assertRaises(ValueError):
self.poll.votescast = Decimal("1")
def test_vote_wrong_state(self):
self.make_admin_present()
self.make_admin_delegate()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_wrong_group(self):
self.start_poll()
self.make_admin_present()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_not_present(self):
self.start_poll()
self.make_admin_delegate()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -863,7 +876,9 @@ class VoteMotionPollNamed(TestCase):
self.start_poll()
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -872,11 +887,118 @@ class VoteMotionPollNamed(TestCase):
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def setup_vote_delegation(self, with_delegation=True):
""" user -> admin """
self.start_poll()
self.make_admin_delegate()
self.make_admin_present()
user, _ = self.create_user()
user.groups.add(GROUP_DELEGATE_PK)
if with_delegation:
user.vote_delegated_to = self.admin
user.save()
inform_changed_data(self.admin) # put the admin into the cache to update
# its vote_delegated_to_id field
self.user = user
def test_vote_delegation(self):
self.setup_vote_delegation()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{"data": "N", "user_id": self.user.pk}, # user not present
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.votesvalid, Decimal("1"))
self.assertEqual(poll.votesinvalid, Decimal("0"))
self.assertEqual(poll.votescast, Decimal("1"))
self.assertEqual(poll.get_votes().count(), 1)
option = poll.options.get()
self.assertEqual(option.yes, Decimal("0"))
self.assertEqual(option.no, Decimal("1"))
self.assertEqual(option.abstain, Decimal("0"))
vote = option.votes.get()
self.assertEqual(vote.user, self.user)
self.assertEqual(vote.delegated_user, self.admin)
autoupdate = self.get_last_autoupdate(user=self.admin)
self.assertIn("motions/motion-poll:1", autoupdate[0])
self.assertEqual(
autoupdate[0]["motions/motion-poll:1"]["user_has_voted_for_delegations"],
[self.user.pk],
)
def test_vote_delegation_and_self_vote(self):
self.test_vote_delegation()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "Y"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.votesvalid, Decimal("2"))
self.assertEqual(poll.votesinvalid, Decimal("0"))
self.assertEqual(poll.votescast, Decimal("2"))
self.assertEqual(poll.get_votes().count(), 2)
option = poll.options.get()
self.assertEqual(option.yes, Decimal("1"))
self.assertEqual(option.no, Decimal("1"))
self.assertEqual(option.abstain, Decimal("0"))
vote = option.votes.get(user_id=self.admin.pk)
self.assertEqual(vote.user, self.admin)
self.assertEqual(vote.delegated_user, self.admin)
def test_vote_delegation_forbidden(self):
self.setup_vote_delegation(False)
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{"data": "N", "user_id": self.user.pk},
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_delegation_not_present(self):
self.setup_vote_delegation()
self.admin.is_present = False
self.admin.save()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{"data": "N", "user_id": self.user.pk},
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_delegation_delegatee_not_in_group(self):
self.setup_vote_delegation()
self.admin.groups.remove(GROUP_DELEGATE_PK)
self.admin.save()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{"data": "N", "user_id": self.user.pk},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
self.assertEqual(poll.get_votes().count(), 1)
vote = poll.get_votes()[0]
self.assertEqual(vote.value, "N")
self.assertEqual(vote.user, self.user)
self.assertEqual(vote.delegated_user, self.admin)
def test_vote_delegation_delegator_not_in_group(self):
self.setup_vote_delegation()
self.user.groups.remove(GROUP_DELEGATE_PK)
self.user.save()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]),
{"data": "N", "user_id": self.user.pk},
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
class VoteMotionPollNamedAutoupdates(TestCase):
"""3 important users:
@ -916,7 +1038,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
def test_vote(self):
response = self.user_client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
@ -942,6 +1064,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
"options_id": [1],
"id": 1,
"user_has_voted": False,
"user_has_voted_for_delegations": [],
"voted_id": [self.user.id],
},
"motions/motion-vote:1": {
@ -950,6 +1073,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
"weight": "1.000000",
"value": "A",
"user_id": self.user.id,
"delegated_user_id": self.user.id,
"option_id": 1,
},
"motions/motion-option:1": {
@ -975,6 +1099,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
"weight": "1.000000",
"value": "A",
"user_id": self.user.id,
"delegated_user_id": self.user.id,
},
)
self.assertEqual(
@ -1001,6 +1126,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
"options_id": [1],
"id": 1,
"user_has_voted": user == self.user,
"user_has_voted_for_delegations": [],
},
)
self.assertEqual(
@ -1055,7 +1181,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
def test_vote(self):
response = self.user_client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
@ -1081,6 +1207,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
"options_id": [1],
"id": 1,
"user_has_voted": False,
"user_has_voted_for_delegations": [],
"voted_id": [self.user.id],
},
"motions/motion-vote:1": {
@ -1090,6 +1217,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
"weight": "1.000000",
"value": "A",
"user_id": None,
"delegated_user_id": None,
},
"motions/motion-option:1": {
"abstain": "1.000000",
@ -1122,6 +1250,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
"options_id": [1],
"id": 1,
"user_has_voted": user == self.user,
"user_has_voted_for_delegations": [],
},
)
@ -1177,7 +1306,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = MotionPoll.objects.get()
@ -1199,11 +1328,11 @@ class VoteMotionPollPseudoanonymous(TestCase):
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
option = MotionPoll.objects.get().options.get()
@ -1219,7 +1348,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
config["general_system_enable_anonymous"] = True
guest_client = APIClient()
response = guest_client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), "Y"
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "Y"}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -1227,21 +1356,27 @@ class VoteMotionPollPseudoanonymous(TestCase):
def test_vote_wrong_state(self):
self.make_admin_present()
self.make_admin_delegate()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_wrong_group(self):
self.start_poll()
self.make_admin_present()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
def test_vote_not_present(self):
self.start_poll()
self.make_admin_delegate()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -1249,7 +1384,9 @@ class VoteMotionPollPseudoanonymous(TestCase):
self.start_poll()
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -1258,7 +1395,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
self.make_admin_delegate()
self.make_admin_present()
response = self.client.post(
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
@ -1344,6 +1481,7 @@ class PublishMotionPoll(TestCase):
"options_id": [1],
"id": 1,
"user_has_voted": False,
"user_has_voted_for_delegations": [],
"voted_id": [],
},
"motions/motion-vote:1": {
@ -1353,6 +1491,7 @@ class PublishMotionPoll(TestCase):
"weight": "2.000000",
"value": "N",
"user_id": None,
"delegated_user_id": None,
},
"motions/motion-option:1": {
"abstain": "0.000000",
@ -1495,3 +1634,45 @@ class ResetMotionPoll(TestCase):
for user in (self.admin, self.user1, self.user2):
self.assertDeletedAutoupdate(self.vote1, user=user)
self.assertDeletedAutoupdate(self.vote2, user=user)
class TestMotionPollWithVoteDelegationAutoupdate(TestCase):
def advancedSetUp(self):
""" Set up user -> other_user delegation. """
self.motion = Motion(
title="test_title_dL91JqhMTiQuQLSDRItZ",
text="test_text_R7nURdXKVEfEnnJBXJYa",
)
self.motion.save()
self.delegate_group = get_group_model().objects.get(pk=GROUP_DELEGATE_PK)
self.other_user, _ = self.create_user()
self.user, user_password = self.create_user()
self.user.groups.add(self.delegate_group)
self.user.is_present = True
self.user.vote_delegated_to = self.other_user
self.user.save()
self.user_client = APIClient()
self.user_client.login(username=self.user.username, password=user_password)
self.poll = MotionPoll.objects.create(
motion=self.motion,
title="test_title_Q3EuRaALSCCPJuQ2tMqj",
pollmethod="YNA",
type=BasePoll.TYPE_NAMED,
onehundred_percent_base="YN",
majority_method="simple",
)
self.poll.create_options()
self.poll.groups.add(self.delegate_group)
self.poll.save()
def test_start_poll(self):
response = self.client.post(reverse("motionpoll-start", args=[self.poll.pk]))
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
# other_user has to receive an autoupdate because he was delegated
autoupdate = self.get_last_autoupdate(user=self.other_user)
assert "motions/motion-poll:1" in autoupdate[0]

View File

@ -24,12 +24,13 @@ def test_user_db_queries():
"""
Tests that only the following db queries are done:
* 2 requests to get the list of all users and
* 1 requests to get the list of all groups.
* 1 request to get all vote delegations
* 1 request to get the list of all groups.
"""
for index in range(10):
User.objects.create(username=f"user{index}")
assert count_queries(User.get_elements)() == 3
assert count_queries(User.get_elements)() == 4
@pytest.mark.django_db(transaction=False)
@ -232,6 +233,188 @@ class UserUpdate(TestCase):
# The user is not allowed to change some other fields (like last_name).
self.assertNotEqual(user.last_name, "New name fae1Bu1Eyeis9eRox4xu")
def test_update_vote_delegation(self):
user = User.objects.create_user(
username="non-admin Yd4ejrJXZi4Wn16ugHgY",
password="non-admin AQ4Dw2tN9byKpGD4f1gs",
)
response = self.client.patch(
reverse("user-detail", args=[user.pk]),
{"vote_delegated_to_id": self.admin.pk},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
user = User.objects.get(pk=user.pk)
self.assertEqual(user.vote_delegated_to_id, self.admin.pk)
admin = User.objects.get(username="admin")
self.assertEqual(
list(admin.vote_delegated_from_users.values_list("id", flat=True)),
[user.pk],
)
def test_update_vote_delegation_non_admin(self):
user = User.objects.create_user(
username="non-admin WpBQRSsCg6qNWNtN6bLP",
password="non-admin IzsDBt1uoqc2wo5BSUF1",
)
client = APIClient()
client.login(
username="non-admin WpBQRSsCg6qNWNtN6bLP",
password="non-admin IzsDBt1uoqc2wo5BSUF1",
)
response = client.patch(
reverse("user-detail", args=[user.pk]),
{"vote_delegated_to_id": self.admin.pk},
)
# self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
self.assertEqual(response.status_code, status.HTTP_200_OK)
user = User.objects.get(pk=user.pk)
self.assertIsNone(user.vote_delegated_to_id)
def test_update_vote_delegated_to_self(self):
response = self.client.patch(
reverse("user-detail", args=[self.admin.pk]),
{"vote_delegated_to_id": self.admin.pk},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
admin = User.objects.get(pk=self.admin.pk)
self.assertIsNone(admin.vote_delegated_to_id)
def test_update_vote_delegation_invalid_id(self):
response = self.client.patch(
reverse("user-detail", args=[self.admin.pk]),
{"vote_delegated_to_id": 42},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
admin = User.objects.get(pk=self.admin.pk)
self.assertIsNone(admin.vote_delegated_to_id)
def test_update_vote_delegated_from_self(self):
response = self.client.patch(
reverse("user-detail", args=[self.admin.pk]),
{"vote_delegated_from_users_id": [self.admin.pk]},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
admin = User.objects.get(pk=self.admin.pk)
self.assertIsNone(admin.vote_delegated_to_id)
def setup_vote_delegation(self):
""" login and setup user -> user2 delegation """
self.user, _ = self.create_user()
self.user2, _ = self.create_user()
self.user.vote_delegated_to = self.user2
self.user.save()
self.assertEqual(self.user.vote_delegated_to_id, self.user2.pk)
def test_update_reset_vote_delegated_to(self):
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.user.pk]),
{"vote_delegated_to_id": None},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
user = User.objects.get(pk=self.user.pk)
self.assertEqual(user.vote_delegated_to_id, None)
def test_update_reset_vote_delegated_from(self):
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.user2.pk]),
{"vote_delegated_from_users_id": None},
)
self.assertEqual(response.status_code, status.HTTP_200_OK)
user = User.objects.get(pk=self.user.pk)
self.assertEqual(user.vote_delegated_to_id, None)
def test_update_nested_vote_delegation_1(self):
""" user -> user2 -> admin """
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.user2.pk]),
{"vote_delegated_to_id": self.admin.pk},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
user2 = User.objects.get(pk=self.user2.pk)
self.assertIsNone(user2.vote_delegated_to_id)
def test_update_nested_vote_delegation_2(self):
""" admin -> user -> user2 """
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.admin.pk]),
{"vote_delegated_to_id": self.user.pk},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
admin = User.objects.get(pk=self.admin.pk)
self.assertIsNone(admin.vote_delegated_to_id)
def test_update_vote_delegation_autoupdate(self):
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.user.pk]),
{"vote_delegated_to_id": self.admin.pk},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
autoupdate = self.get_last_autoupdate(user=self.admin)
user_au = autoupdate[0].get(f"users/user:{self.user.pk}")
self.assertIsNotNone(user_au)
self.assertEqual(user_au["vote_delegated_to_id"], self.admin.pk)
user2_au = autoupdate[0].get(f"users/user:{self.user2.pk}")
self.assertIsNotNone(user2_au)
self.assertEqual(user2_au["vote_delegated_from_users_id"], [])
admin_au = autoupdate[0].get(f"users/user:{self.admin.pk}")
self.assertIsNotNone(admin_au)
self.assertEqual(admin_au["vote_delegated_from_users_id"], [self.user.pk])
self.assertEqual(autoupdate[1], [])
def test_update_vote_delegated_from(self):
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.user2.pk]),
{"vote_delegated_from_users_id": [self.admin.pk]},
)
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
admin = User.objects.get(pk=self.admin.pk)
self.assertEqual(admin.vote_delegated_to_id, self.user2.id)
user = User.objects.get(pk=self.user.pk)
self.assertIsNone(user.vote_delegated_to_id)
def test_update_vote_delegated_from_nested_1(self):
""" admin -> user -> user2 """
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.user.pk]),
{"vote_delegated_from_users_id": [self.admin.pk]},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
admin = User.objects.get(pk=self.admin.pk)
self.assertIsNone(admin.vote_delegated_to_id)
def test_update_vote_delegated_from_nested_2(self):
""" user -> user2 -> admin """
self.setup_vote_delegation()
response = self.client.patch(
reverse("user-detail", args=[self.admin.pk]),
{"vote_delegated_from_users_id": [self.user2.pk]},
)
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
user2 = User.objects.get(pk=self.user2.pk)
self.assertIsNone(user2.vote_delegated_to_id)
class UserDelete(TestCase):
"""

View File

@ -1,6 +1,7 @@
from decimal import Decimal
from unittest import TestCase
from openslides.motions.models import Motion, MotionChangeRecommendation
from openslides.motions.models import Motion, MotionChangeRecommendation, MotionPoll
# TODO: test for MotionPoll.set_options()
@ -50,3 +51,25 @@ class MotionChangeRecommendationTest(TestCase):
other_recommendations
)
self.assertFalse(collides)
class MotionPollAnalogFieldsTest(TestCase):
def setUp(self):
self.motion = Motion(
title="test_title_OoK9IeChe2Jeib9Deeji",
text="test_text_eichui1oobiSeit9aifo",
)
self.poll = MotionPoll(
motion=self.motion,
title="test_title_tho8PhiePh8upaex6phi",
pollmethod="YNA",
type=MotionPoll.TYPE_NAMED,
)
def test_not_set_vote_values(self):
with self.assertRaises(ValueError):
self.poll.votesvalid = Decimal("1")
with self.assertRaises(ValueError):
self.poll.votesinvalid = Decimal("1")
with self.assertRaises(ValueError):
self.poll.votescast = Decimal("1")