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:
parent
ed2c298928
commit
3ac8569712
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
@ -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()
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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"):
|
||||
|
@ -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,
|
||||
),
|
||||
),
|
||||
]
|
@ -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:
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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": {
|
||||
|
@ -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]
|
||||
|
@ -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):
|
||||
"""
|
||||
|
@ -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")
|
||||
|
Loading…
Reference in New Issue
Block a user