majorities in polls
This commit is contained in:
parent
5fa8341614
commit
1246dd54ad
@ -7,7 +7,7 @@ export interface AssignmentWithoutNestedModels extends BaseModelWithAgendaItemAn
|
|||||||
description: string;
|
description: string;
|
||||||
open_posts: number;
|
open_posts: number;
|
||||||
phase: number; // see Openslides constants
|
phase: number; // see Openslides constants
|
||||||
poll_description_default: number;
|
default_poll_description: string;
|
||||||
tags_id: number[];
|
tags_id: number[];
|
||||||
attachments_id: number[];
|
attachments_id: number[];
|
||||||
}
|
}
|
||||||
|
@ -280,13 +280,13 @@
|
|||||||
[form]="assignmentForm"
|
[form]="assignmentForm"
|
||||||
></os-agenda-content-object-form>
|
></os-agenda-content-object-form>
|
||||||
|
|
||||||
<!-- poll_description_default -->
|
<!-- default_poll_description -->
|
||||||
<div>
|
<div>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<input
|
<input
|
||||||
matInput
|
matInput
|
||||||
placeholder="{{ 'Default comment on the ballot paper' | translate }}"
|
placeholder="{{ 'Default comment on the ballot paper' | translate }}"
|
||||||
formControlName="poll_description_default"
|
formControlName="default_poll_description"
|
||||||
/>
|
/>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
@ -189,7 +189,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
|||||||
attachments_id: [],
|
attachments_id: [],
|
||||||
title: ['', Validators.required],
|
title: ['', Validators.required],
|
||||||
description: [''],
|
description: [''],
|
||||||
poll_description_default: [''],
|
default_poll_description: [''],
|
||||||
open_posts: [1, [Validators.required, Validators.min(1)]],
|
open_posts: [1, [Validators.required, Validators.min(1)]],
|
||||||
agenda_create: [''],
|
agenda_create: [''],
|
||||||
agenda_parent_id: [],
|
agenda_parent_id: [],
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import json
|
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from ..poll.access_permissions import BaseVoteAccessPermissions
|
from ..poll.access_permissions import (
|
||||||
from ..poll.views import BasePoll
|
BasePollAccessPermissions,
|
||||||
|
BaseVoteAccessPermissions,
|
||||||
|
)
|
||||||
from ..utils.access_permissions import BaseAccessPermissions
|
from ..utils.access_permissions import BaseAccessPermissions
|
||||||
from ..utils.auth import async_has_perm
|
from ..utils.auth import async_has_perm
|
||||||
|
|
||||||
@ -47,39 +48,10 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class AssignmentPollAccessPermissions(BaseAccessPermissions):
|
class AssignmentPollAccessPermissions(BasePollAccessPermissions):
|
||||||
base_permission = "assignments.can_see"
|
base_permission = "assignments.can_see"
|
||||||
|
manage_permission = "assignments.can_manage_polls"
|
||||||
async def get_restricted_data(
|
additional_fields = ["amount_global_no", "amount_global_abstain"]
|
||||||
self, full_data: List[Dict[str, Any]], user_id: int
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Poll-managers have full access, even during an active poll.
|
|
||||||
Non-published polls will be restricted:
|
|
||||||
- Remove votes* values from the poll
|
|
||||||
- Remove yes/no/abstain fields from options
|
|
||||||
- Remove voted_id field from the poll
|
|
||||||
"""
|
|
||||||
|
|
||||||
if await async_has_perm(user_id, "assignments.can_manage_polls"):
|
|
||||||
data = full_data
|
|
||||||
else:
|
|
||||||
data = []
|
|
||||||
for poll in full_data:
|
|
||||||
if poll["state"] != BasePoll.STATE_PUBLISHED:
|
|
||||||
poll = json.loads(
|
|
||||||
json.dumps(poll)
|
|
||||||
) # copy, so we can remove some fields.
|
|
||||||
del poll["votesvalid"]
|
|
||||||
del poll["votesinvalid"]
|
|
||||||
del poll["votescast"]
|
|
||||||
del poll["voted_id"]
|
|
||||||
for option in poll["options"]:
|
|
||||||
del option["yes"]
|
|
||||||
del option["no"]
|
|
||||||
del option["abstain"]
|
|
||||||
data.append(poll)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class AssignmentVoteAccessPermissions(BaseVoteAccessPermissions):
|
class AssignmentVoteAccessPermissions(BaseVoteAccessPermissions):
|
||||||
|
@ -1,7 +1,5 @@
|
|||||||
from django.core.validators import MinValueValidator
|
from openslides.assignments.models import AssignmentPoll
|
||||||
|
|
||||||
from openslides.core.config import ConfigVariable
|
from openslides.core.config import ConfigVariable
|
||||||
from openslides.poll.majority import majorityMethods
|
|
||||||
|
|
||||||
|
|
||||||
def get_config_variables():
|
def get_config_variables():
|
||||||
@ -11,87 +9,47 @@ def get_config_variables():
|
|||||||
They are grouped in 'Ballot and ballot papers' and 'PDF'. The generator has
|
They are grouped in 'Ballot and ballot papers' and 'PDF'. The generator has
|
||||||
to be evaluated during app loading (see apps.py).
|
to be evaluated during app loading (see apps.py).
|
||||||
"""
|
"""
|
||||||
# Ballot and ballot papers
|
# Polls
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="assignments_poll_100_percent_base",
|
name="assignment_poll_default_100_percent_base",
|
||||||
default_value="YES_NO_ABSTAIN",
|
default_value="YNA",
|
||||||
input_type="choice",
|
input_type="choice",
|
||||||
label="The 100-%-base of an election result consists of",
|
label="The 100-%-base of an election result consists of",
|
||||||
choices=(
|
choices=tuple(
|
||||||
{"value": "YES_NO_ABSTAIN", "display_name": "Yes/No/Abstain per candidate"},
|
{"value": base[0], "display_name": base[1]}
|
||||||
{"value": "YES_NO", "display_name": "Yes/No per candidate"},
|
for base in AssignmentPoll.PERCENT_BASES
|
||||||
{"value": "VALID", "display_name": "All valid ballots"},
|
|
||||||
{"value": "CAST", "display_name": "All casted ballots"},
|
|
||||||
{"value": "DISABLED", "display_name": "Disabled (no percents)"},
|
|
||||||
),
|
),
|
||||||
help_text=(
|
weight=400,
|
||||||
"For Yes/No/Abstain per candidate and Yes/No per candidate the 100-%-base "
|
group="Polls",
|
||||||
"depends on the election method: If there is only one option per candidate, "
|
subgroup="Elections",
|
||||||
"the sum of all votes of all candidates is 100 %. Otherwise for each "
|
|
||||||
"candidate the sum of all votes is 100 %."
|
|
||||||
),
|
|
||||||
weight=420,
|
|
||||||
group="Elections",
|
|
||||||
subgroup="Ballot and ballot papers",
|
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Add server side validation of the choices.
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="assignments_poll_default_majority_method",
|
name="assignment_poll_default_majority_method",
|
||||||
default_value=majorityMethods[0]["value"],
|
default_value="simple",
|
||||||
input_type="choice",
|
input_type="choice",
|
||||||
choices=majorityMethods,
|
choices=tuple(
|
||||||
|
{"value": method[0], "display_name": method[1]}
|
||||||
|
for method in AssignmentPoll.MAJORITY_METHODS
|
||||||
|
),
|
||||||
label="Required majority",
|
label="Required majority",
|
||||||
help_text="Default method to check whether a candidate has reached the required majority.",
|
help_text="Default method to check whether a candidate has reached the required majority.",
|
||||||
weight=425,
|
weight=405,
|
||||||
group="Elections",
|
group="Polls",
|
||||||
subgroup="Ballot and ballot papers",
|
subgroup="Elections",
|
||||||
)
|
)
|
||||||
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="assignments_pdf_ballot_papers_selection",
|
name="assignment_poll_add_candidates_to_list_of_speakers",
|
||||||
default_value="CUSTOM_NUMBER",
|
|
||||||
input_type="choice",
|
|
||||||
label="Number of ballot papers (selection)",
|
|
||||||
choices=(
|
|
||||||
{"value": "NUMBER_OF_DELEGATES", "display_name": "Number of all delegates"},
|
|
||||||
{
|
|
||||||
"value": "NUMBER_OF_ALL_PARTICIPANTS",
|
|
||||||
"display_name": "Number of all participants",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "CUSTOM_NUMBER",
|
|
||||||
"display_name": "Use the following custom number",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
weight=430,
|
|
||||||
group="Elections",
|
|
||||||
subgroup="Ballot and ballot papers",
|
|
||||||
)
|
|
||||||
|
|
||||||
yield ConfigVariable(
|
|
||||||
name="assignments_pdf_ballot_papers_number",
|
|
||||||
default_value=8,
|
|
||||||
input_type="integer",
|
|
||||||
label="Custom number of ballot papers",
|
|
||||||
weight=435,
|
|
||||||
group="Elections",
|
|
||||||
subgroup="Ballot and ballot papers",
|
|
||||||
validators=(MinValueValidator(1),),
|
|
||||||
)
|
|
||||||
|
|
||||||
yield ConfigVariable(
|
|
||||||
name="assignments_add_candidates_to_list_of_speakers",
|
|
||||||
default_value=True,
|
default_value=True,
|
||||||
input_type="boolean",
|
input_type="boolean",
|
||||||
label="Put all candidates on the list of speakers",
|
label="Put all candidates on the list of speakers",
|
||||||
weight=440,
|
weight=410,
|
||||||
group="Elections",
|
group="Polls",
|
||||||
subgroup="Ballot and ballot papers",
|
subgroup="Elections",
|
||||||
)
|
)
|
||||||
|
|
||||||
# PDF
|
# PDF
|
||||||
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
name="assignments_pdf_title",
|
name="assignments_pdf_title",
|
||||||
default_value="Elections",
|
default_value="Elections",
|
||||||
|
@ -21,8 +21,6 @@ class Migration(migrations.Migration):
|
|||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
model_name="assignmentoption", old_name="candidate", new_name="user"
|
model_name="assignmentoption", old_name="candidate", new_name="user"
|
||||||
),
|
),
|
||||||
migrations.RemoveField(model_name="assignmentpoll", name="description"),
|
|
||||||
migrations.RemoveField(model_name="assignmentpoll", name="published"),
|
|
||||||
migrations.AddField(
|
migrations.AddField(
|
||||||
model_name="assignmentpoll",
|
model_name="assignmentpoll",
|
||||||
name="global_abstain",
|
name="global_abstain",
|
||||||
@ -99,6 +97,53 @@ class Migration(migrations.Migration):
|
|||||||
name="allow_multiple_votes_per_candidate",
|
name="allow_multiple_votes_per_candidate",
|
||||||
field=models.BooleanField(default=False),
|
field=models.BooleanField(default=False),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="assignmentpoll",
|
||||||
|
name="majority_method",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("simple", "Simple majority"),
|
||||||
|
("two_thirds", "Two-thirds majority"),
|
||||||
|
("three_quarters", "Three-quarters majority"),
|
||||||
|
("disabled", "Disabled"),
|
||||||
|
],
|
||||||
|
default="",
|
||||||
|
max_length=14,
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="assignmentpoll",
|
||||||
|
name="onehundred_percent_base",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("YN", "Yes/No per candidate"),
|
||||||
|
("YNA", "Yes/No/Abstain per candidate"),
|
||||||
|
("votes", "Sum of votes inclusive global ones"),
|
||||||
|
("valid", "All valid ballots"),
|
||||||
|
("cast", "All casted ballots"),
|
||||||
|
("disabled", "Disabled (no percents)"),
|
||||||
|
],
|
||||||
|
default="",
|
||||||
|
max_length=8,
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="assignment",
|
||||||
|
name="poll_description_default",
|
||||||
|
field=models.CharField(blank=True, max_length=255),
|
||||||
|
),
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="assignment",
|
||||||
|
old_name="poll_description_default",
|
||||||
|
new_name="default_poll_description",
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="assignmentpoll",
|
||||||
|
name="description",
|
||||||
|
field=models.CharField(blank=True, max_length=255),
|
||||||
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="assignmentpoll",
|
model_name="assignmentpoll",
|
||||||
name="pollmethod",
|
name="pollmethod",
|
||||||
@ -106,13 +151,6 @@ class Migration(migrations.Migration):
|
|||||||
choices=[("YN", "YN"), ("YNA", "YNA"), ("votes", "votes")], max_length=5
|
choices=[("YN", "YN"), ("YNA", "YNA"), ("votes", "votes")], max_length=5
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
migrations.AlterField(
|
|
||||||
model_name="assignmentvote",
|
|
||||||
name="value",
|
|
||||||
field=models.CharField(
|
|
||||||
choices=[("Y", "Y"), ("N", "N"), ("A", "A")], max_length=1
|
|
||||||
),
|
|
||||||
),
|
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="assignmentvote",
|
model_name="assignmentvote",
|
||||||
name="weight",
|
name="weight",
|
||||||
@ -134,6 +172,4 @@ class Migration(migrations.Migration):
|
|||||||
migrations.RenameField(
|
migrations.RenameField(
|
||||||
model_name="assignmentpoll", old_name="votesvalid", new_name="db_votesvalid"
|
model_name="assignmentpoll", old_name="votesvalid", new_name="db_votesvalid"
|
||||||
),
|
),
|
||||||
migrations.RemoveField(model_name="assignmentpoll", name="votesabstain"),
|
|
||||||
migrations.RemoveField(model_name="assignmentpoll", name="votesno"),
|
|
||||||
]
|
]
|
145
openslides/assignments/migrations/0009_voting_2.py
Normal file
145
openslides/assignments/migrations/0009_voting_2.py
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
# Generated by Finn Stutzenstein on 2019-10-29 10:55
|
||||||
|
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
from django.db import migrations, transaction
|
||||||
|
|
||||||
|
|
||||||
|
def change_pollmethods(apps, schema_editor):
|
||||||
|
""" yn->YN, yna->YNA """
|
||||||
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
|
pollmethod_map = {
|
||||||
|
"yn": "YN",
|
||||||
|
"yna": "YNA",
|
||||||
|
"votes": "votes",
|
||||||
|
}
|
||||||
|
for poll in AssignmentPoll.objects.all():
|
||||||
|
poll.pollmethod = pollmethod_map.get(poll.pollmethod, "YNA")
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_poll_titles(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Sets titles to their indexes
|
||||||
|
"""
|
||||||
|
Assignment = apps.get_model("assignments", "Assignment")
|
||||||
|
for assignment in Assignment.objects.all():
|
||||||
|
for i, poll in enumerate(assignment.polls.order_by("pk").all()):
|
||||||
|
poll.title = str(i + 1)
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_onehunderd_percent_bases(apps, schema_editor):
|
||||||
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
|
ConfigStore = apps.get_model("core", "ConfigStore")
|
||||||
|
base_map = {
|
||||||
|
"YES_NO_ABSTAIN": "YNA",
|
||||||
|
"YES_NO": "YN",
|
||||||
|
"VALID": "valid",
|
||||||
|
"CAST": "cast",
|
||||||
|
"DISABLED": "disabled",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
config = ConfigStore.objects.get(key="assignments_poll_100_percent_base")
|
||||||
|
value = base_map[config.value]
|
||||||
|
except (ConfigStore.DoesNotExist, KeyError):
|
||||||
|
value = "YNA"
|
||||||
|
|
||||||
|
for poll in AssignmentPoll.objects.all():
|
||||||
|
if poll.pollmethod == "votes" and value in ("YN", "YNA"):
|
||||||
|
poll.onehundred_percent_base = "votes"
|
||||||
|
elif poll.pollmethod == "YN" and value == "YNA":
|
||||||
|
poll.onehundred_percent_base = "YN"
|
||||||
|
else:
|
||||||
|
poll.onehundred_percent_base = value
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_majority_methods(apps, schema_editor):
|
||||||
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
|
ConfigStore = apps.get_model("core", "ConfigStore")
|
||||||
|
majority_map = {
|
||||||
|
"simple_majority": "simple",
|
||||||
|
"two-thirds_majority": "two_thirds",
|
||||||
|
"three-quarters_majority": "three_quarters",
|
||||||
|
"disabled": "disabled",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
config = ConfigStore.objects.get(key="assignments_poll_default_majority_method")
|
||||||
|
value = majority_map[config.value]
|
||||||
|
except (ConfigStore.DoesNotExist, KeyError):
|
||||||
|
value = "simple"
|
||||||
|
|
||||||
|
for poll in AssignmentPoll.objects.all():
|
||||||
|
poll.majority_method = value
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_votes(apps, schema_editor):
|
||||||
|
AssignmentVote = apps.get_model("assignments", "AssignmentVote")
|
||||||
|
value_map = {
|
||||||
|
"Yes": "Y",
|
||||||
|
"No": "N",
|
||||||
|
"Abstain": "A",
|
||||||
|
"Votes": "Y",
|
||||||
|
}
|
||||||
|
for vote in AssignmentVote.objects.all():
|
||||||
|
vote.value = value_map[vote.value]
|
||||||
|
vote.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_votesabstain(apps, schema_editor):
|
||||||
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
|
AssignmentVote = apps.get_model("assignments", "AssignmentVote")
|
||||||
|
for poll in AssignmentPoll.objects.all():
|
||||||
|
if poll.votesabstain is not None and poll.votesabstain > Decimal(0):
|
||||||
|
with transaction.atomic():
|
||||||
|
option = poll.options.first()
|
||||||
|
vote = AssignmentVote(
|
||||||
|
option=option, value="A", weight=poll.votesabstain
|
||||||
|
)
|
||||||
|
vote.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_votesno(apps, schema_editor):
|
||||||
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
|
AssignmentVote = apps.get_model("assignments", "AssignmentVote")
|
||||||
|
for poll in AssignmentPoll.objects.all():
|
||||||
|
if poll.votesno is not None and poll.votesno > Decimal(0):
|
||||||
|
with transaction.atomic():
|
||||||
|
option = poll.options.first()
|
||||||
|
vote = AssignmentVote(option=option, value="N", weight=poll.votesno)
|
||||||
|
vote.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_correct_state(apps, schema_editor):
|
||||||
|
""" if poll.published, set state to published """
|
||||||
|
AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
|
||||||
|
AssignmentVote = apps.get_model("assignments", "AssignmentVote")
|
||||||
|
for poll in AssignmentPoll.objects.all():
|
||||||
|
# Polls, that are published (old field) but have no votes, will be
|
||||||
|
# left at the created state...
|
||||||
|
if AssignmentVote.objects.filter(option__poll__pk=poll.pk).exists():
|
||||||
|
if poll.published:
|
||||||
|
poll.state = 4 # published
|
||||||
|
else:
|
||||||
|
poll.state = 3 # finished
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("assignments", "0008_voting_1"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(change_pollmethods),
|
||||||
|
migrations.RunPython(set_poll_titles),
|
||||||
|
migrations.RunPython(set_onehunderd_percent_bases),
|
||||||
|
migrations.RunPython(set_majority_methods),
|
||||||
|
migrations.RunPython(convert_votes),
|
||||||
|
migrations.RunPython(convert_votesabstain),
|
||||||
|
migrations.RunPython(convert_votesno),
|
||||||
|
migrations.RunPython(set_correct_state),
|
||||||
|
]
|
23
openslides/assignments/migrations/0010_voting_3.py
Normal file
23
openslides/assignments/migrations/0010_voting_3.py
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
# Generated by Finn Stutzenstein on 2019-10-29 11:10
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("assignments", "0009_voting_2"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name="assignmentvote",
|
||||||
|
name="value",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[("Y", "Y"), ("N", "N"), ("A", "A")], max_length=1
|
||||||
|
),
|
||||||
|
),
|
||||||
|
migrations.RemoveField(model_name="assignmentpoll", name="votesabstain"),
|
||||||
|
migrations.RemoveField(model_name="assignmentpoll", name="votesno"),
|
||||||
|
migrations.RemoveField(model_name="assignmentpoll", name="published"),
|
||||||
|
]
|
@ -1,5 +1,4 @@
|
|||||||
from collections import OrderedDict
|
from decimal import Decimal
|
||||||
from typing import Any, Dict, List
|
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
@ -120,7 +119,7 @@ class Assignment(RESTModelMixin, AgendaItemWithListOfSpeakersMixin, models.Model
|
|||||||
The number of members to be elected.
|
The number of members to be elected.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
poll_description_default = models.CharField(max_length=79, blank=True)
|
default_poll_description = models.CharField(max_length=255, blank=True)
|
||||||
"""
|
"""
|
||||||
Default text for the poll description.
|
Default text for the poll description.
|
||||||
"""
|
"""
|
||||||
@ -230,40 +229,6 @@ class Assignment(RESTModelMixin, AgendaItemWithListOfSpeakersMixin, models.Model
|
|||||||
|
|
||||||
self.phase = phase
|
self.phase = phase
|
||||||
|
|
||||||
def vote_results(self, only_published):
|
|
||||||
"""
|
|
||||||
Returns a table represented as a list with all candidates from all
|
|
||||||
related polls and their vote results.
|
|
||||||
"""
|
|
||||||
vote_results_dict: Dict[Any, List[AssignmentVote]] = OrderedDict()
|
|
||||||
|
|
||||||
polls = self.polls.all()
|
|
||||||
if only_published:
|
|
||||||
polls = polls.filter(published=True)
|
|
||||||
|
|
||||||
# All PollOption-Objects related to this assignment
|
|
||||||
options: List[AssignmentOption] = []
|
|
||||||
for poll in polls:
|
|
||||||
options += poll.get_options()
|
|
||||||
|
|
||||||
for option in options:
|
|
||||||
candidate = option.candidate
|
|
||||||
if candidate in vote_results_dict:
|
|
||||||
continue
|
|
||||||
vote_results_dict[candidate] = []
|
|
||||||
for poll in polls:
|
|
||||||
votes: Any = {}
|
|
||||||
try:
|
|
||||||
# candidate related to this poll
|
|
||||||
poll_option = poll.get_options().get(candidate=candidate)
|
|
||||||
for vote in poll_option.get_votes():
|
|
||||||
votes[vote.value] = vote.print_weight()
|
|
||||||
except AssignmentOption.DoesNotExist:
|
|
||||||
# candidate not in related to this poll
|
|
||||||
votes = None
|
|
||||||
vote_results_dict[candidate].append(votes)
|
|
||||||
return vote_results_dict
|
|
||||||
|
|
||||||
def get_title_information(self):
|
def get_title_information(self):
|
||||||
return {"title": self.title}
|
return {"title": self.title}
|
||||||
|
|
||||||
@ -330,9 +295,6 @@ class AssignmentPollManager(models.Manager):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Meta-TODO: Is this todo resolved?
|
|
||||||
# TODO: remove the type-ignoring in the next line, after this is solved:
|
|
||||||
# https://github.com/python/mypy/issues/3855
|
|
||||||
class AssignmentPoll(RESTModelMixin, BasePoll):
|
class AssignmentPoll(RESTModelMixin, BasePoll):
|
||||||
access_permissions = AssignmentPollAccessPermissions()
|
access_permissions = AssignmentPollAccessPermissions()
|
||||||
objects = AssignmentPollManager()
|
objects = AssignmentPollManager()
|
||||||
@ -343,12 +305,32 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
|
|||||||
Assignment, on_delete=models.CASCADE, related_name="polls"
|
Assignment, on_delete=models.CASCADE, related_name="polls"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
description = models.CharField(max_length=255, blank=True)
|
||||||
|
|
||||||
POLLMETHOD_YN = "YN"
|
POLLMETHOD_YN = "YN"
|
||||||
POLLMETHOD_YNA = "YNA"
|
POLLMETHOD_YNA = "YNA"
|
||||||
POLLMETHOD_VOTES = "votes"
|
POLLMETHOD_VOTES = "votes"
|
||||||
POLLMETHODS = (("YN", "YN"), ("YNA", "YNA"), ("votes", "votes"))
|
POLLMETHODS = (("YN", "YN"), ("YNA", "YNA"), ("votes", "votes"))
|
||||||
pollmethod = models.CharField(max_length=5, choices=POLLMETHODS)
|
pollmethod = models.CharField(max_length=5, choices=POLLMETHODS)
|
||||||
|
|
||||||
|
PERCENT_BASE_YN = "YN"
|
||||||
|
PERCENT_BASE_YNA = "YNA"
|
||||||
|
PERCENT_BASE_VOTES = "votes"
|
||||||
|
PERCENT_BASE_VALID = "valid"
|
||||||
|
PERCENT_BASE_CAST = "cast"
|
||||||
|
PERCENT_BASE_DISABLED = "disabled"
|
||||||
|
PERCENT_BASES = (
|
||||||
|
(PERCENT_BASE_YN, "Yes/No per candidate"),
|
||||||
|
(PERCENT_BASE_YNA, "Yes/No/Abstain per candidate"),
|
||||||
|
(PERCENT_BASE_VOTES, "Sum of votes inclusive global ones"),
|
||||||
|
(PERCENT_BASE_VALID, "All valid ballots"),
|
||||||
|
(PERCENT_BASE_CAST, "All casted ballots"),
|
||||||
|
(PERCENT_BASE_DISABLED, "Disabled (no percents)"),
|
||||||
|
)
|
||||||
|
onehundred_percent_base = models.CharField(
|
||||||
|
max_length=8, blank=False, null=False, choices=PERCENT_BASES
|
||||||
|
)
|
||||||
|
|
||||||
global_abstain = models.BooleanField(default=True)
|
global_abstain = models.BooleanField(default=True)
|
||||||
global_no = models.BooleanField(default=True)
|
global_no = models.BooleanField(default=True)
|
||||||
|
|
||||||
@ -360,6 +342,27 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
|
|||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount_global_no(self):
|
||||||
|
if self.pollmethod != AssignmentPoll.POLLMETHOD_VOTES or not self.global_no:
|
||||||
|
return None
|
||||||
|
no_sum = Decimal(0)
|
||||||
|
for option in self.options.all():
|
||||||
|
no_sum += option.no
|
||||||
|
return no_sum
|
||||||
|
|
||||||
|
@property
|
||||||
|
def amount_global_abstain(self):
|
||||||
|
if (
|
||||||
|
self.pollmethod != AssignmentPoll.POLLMETHOD_VOTES
|
||||||
|
or not self.global_abstain
|
||||||
|
):
|
||||||
|
return None
|
||||||
|
abstain_sum = Decimal(0)
|
||||||
|
for option in self.options.all():
|
||||||
|
abstain_sum += option.abstain
|
||||||
|
return abstain_sum
|
||||||
|
|
||||||
def create_options(self):
|
def create_options(self):
|
||||||
related_users = AssignmentRelatedUser.objects.filter(
|
related_users = AssignmentRelatedUser.objects.filter(
|
||||||
assignment__id=self.assignment.id
|
assignment__id=self.assignment.id
|
||||||
@ -374,7 +377,7 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
|
|||||||
inform_changed_data(self)
|
inform_changed_data(self)
|
||||||
|
|
||||||
# Add all candidates to list of speakers of related agenda item
|
# Add all candidates to list of speakers of related agenda item
|
||||||
if config["assignments_add_candidates_to_list_of_speakers"]:
|
if config["assignment_poll_add_candidates_to_list_of_speakers"]:
|
||||||
for related_user in related_users:
|
for related_user in related_users:
|
||||||
try:
|
try:
|
||||||
Speaker.objects.add(
|
Speaker.objects.add(
|
||||||
|
@ -2,19 +2,19 @@ from openslides.poll.serializers import (
|
|||||||
BASE_OPTION_FIELDS,
|
BASE_OPTION_FIELDS,
|
||||||
BASE_POLL_FIELDS,
|
BASE_POLL_FIELDS,
|
||||||
BASE_VOTE_FIELDS,
|
BASE_VOTE_FIELDS,
|
||||||
|
BaseOptionSerializer,
|
||||||
|
BasePollSerializer,
|
||||||
|
BaseVoteSerializer,
|
||||||
)
|
)
|
||||||
from openslides.utils.rest_api import (
|
from openslides.utils.rest_api import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
CharField,
|
|
||||||
DecimalField,
|
DecimalField,
|
||||||
IdPrimaryKeyRelatedField,
|
|
||||||
IntegerField,
|
IntegerField,
|
||||||
ModelSerializer,
|
ModelSerializer,
|
||||||
SerializerMethodField,
|
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..utils.auth import get_group_model, has_perm
|
from ..utils.auth import has_perm
|
||||||
from ..utils.autoupdate import inform_changed_data
|
from ..utils.autoupdate import inform_changed_data
|
||||||
from ..utils.validate import validate_html
|
from ..utils.validate import validate_html
|
||||||
from .models import (
|
from .models import (
|
||||||
@ -47,40 +47,29 @@ class AssignmentRelatedUserSerializer(ModelSerializer):
|
|||||||
fields = ("id", "user", "elected", "weight")
|
fields = ("id", "user", "elected", "weight")
|
||||||
|
|
||||||
|
|
||||||
class AssignmentVoteSerializer(ModelSerializer):
|
class AssignmentVoteSerializer(BaseVoteSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for assignment.models.AssignmentVote objects.
|
Serializer for assignment.models.AssignmentVote objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
pollstate = SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssignmentVote
|
model = AssignmentVote
|
||||||
fields = ("pollstate",) + BASE_VOTE_FIELDS
|
fields = BASE_VOTE_FIELDS
|
||||||
read_only_fields = BASE_VOTE_FIELDS
|
read_only_fields = BASE_VOTE_FIELDS
|
||||||
|
|
||||||
def get_pollstate(self, vote):
|
|
||||||
return vote.option.poll.state
|
|
||||||
|
|
||||||
|
class AssignmentOptionSerializer(BaseOptionSerializer):
|
||||||
class AssignmentOptionSerializer(ModelSerializer):
|
|
||||||
"""
|
"""
|
||||||
Serializer for assignment.models.AssignmentOption objects.
|
Serializer for assignment.models.AssignmentOption objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
yes = DecimalField(max_digits=15, decimal_places=6, min_value=-2, read_only=True)
|
|
||||||
no = DecimalField(max_digits=15, decimal_places=6, min_value=-2, read_only=True)
|
|
||||||
abstain = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = AssignmentOption
|
model = AssignmentOption
|
||||||
fields = ("user", "weight") + BASE_OPTION_FIELDS
|
fields = ("user", "weight") + BASE_OPTION_FIELDS
|
||||||
read_only_fields = ("user", "weight") + BASE_OPTION_FIELDS
|
read_only_fields = ("user", "weight") + BASE_OPTION_FIELDS
|
||||||
|
|
||||||
|
|
||||||
class AssignmentPollSerializer(ModelSerializer):
|
class AssignmentPollSerializer(BasePollSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for assignment.models.AssignmentPoll objects.
|
Serializer for assignment.models.AssignmentPoll objects.
|
||||||
|
|
||||||
@ -88,20 +77,10 @@ class AssignmentPollSerializer(ModelSerializer):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
options = AssignmentOptionSerializer(many=True, read_only=True)
|
options = AssignmentOptionSerializer(many=True, read_only=True)
|
||||||
|
amount_global_no = DecimalField(
|
||||||
title = CharField(allow_blank=False, required=True)
|
|
||||||
groups = IdPrimaryKeyRelatedField(
|
|
||||||
many=True, required=False, queryset=get_group_model().objects.all()
|
|
||||||
)
|
|
||||||
voted = IdPrimaryKeyRelatedField(many=True, read_only=True)
|
|
||||||
|
|
||||||
votesvalid = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
||||||
)
|
)
|
||||||
votesinvalid = DecimalField(
|
amount_global_abstain = DecimalField(
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
|
||||||
)
|
|
||||||
votescast = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -109,19 +88,55 @@ class AssignmentPollSerializer(ModelSerializer):
|
|||||||
model = AssignmentPoll
|
model = AssignmentPoll
|
||||||
fields = (
|
fields = (
|
||||||
"assignment",
|
"assignment",
|
||||||
|
"description",
|
||||||
"pollmethod",
|
"pollmethod",
|
||||||
"votes_amount",
|
"votes_amount",
|
||||||
"allow_multiple_votes_per_candidate",
|
"allow_multiple_votes_per_candidate",
|
||||||
"global_no",
|
"global_no",
|
||||||
|
"amount_global_no",
|
||||||
"global_abstain",
|
"global_abstain",
|
||||||
|
"amount_global_abstain",
|
||||||
) + BASE_POLL_FIELDS
|
) + BASE_POLL_FIELDS
|
||||||
read_only_fields = ("state",)
|
read_only_fields = ("state",)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
""" Prevent from updating the assignment """
|
""" Prevent updating the assignment """
|
||||||
validated_data.pop("assignment", None)
|
validated_data.pop("assignment", None)
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
def norm_100_percent_base_to_pollmethod(
|
||||||
|
self, onehundred_percent_base, pollmethod, old_100_percent_base=None
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Returns None, if the 100-%-base must not be changed, otherwise the correct 100-%-base.
|
||||||
|
"""
|
||||||
|
if pollmethod == AssignmentPoll.POLLMETHOD_YN and onehundred_percent_base in (
|
||||||
|
AssignmentPoll.PERCENT_BASE_VOTES,
|
||||||
|
AssignmentPoll.PERCENT_BASE_YNA,
|
||||||
|
):
|
||||||
|
return AssignmentPoll.PERCENT_BASE_YN
|
||||||
|
if (
|
||||||
|
pollmethod == AssignmentPoll.POLLMETHOD_YNA
|
||||||
|
and onehundred_percent_base == AssignmentPoll.PERCENT_BASE_VOTES
|
||||||
|
):
|
||||||
|
if old_100_percent_base is None:
|
||||||
|
return AssignmentPoll.PERCENT_BASE_YNA
|
||||||
|
else:
|
||||||
|
if old_100_percent_base in (
|
||||||
|
AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
AssignmentPoll.PERCENT_BASE_YNA,
|
||||||
|
):
|
||||||
|
return old_100_percent_base
|
||||||
|
else:
|
||||||
|
return pollmethod
|
||||||
|
if (
|
||||||
|
pollmethod == AssignmentPoll.POLLMETHOD_VOTES
|
||||||
|
and onehundred_percent_base
|
||||||
|
in (AssignmentPoll.PERCENT_BASE_YN, AssignmentPoll.PERCENT_BASE_YNA)
|
||||||
|
):
|
||||||
|
return AssignmentPoll.PERCENT_BASE_VOTES
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class AssignmentSerializer(ModelSerializer):
|
class AssignmentSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
@ -146,7 +161,7 @@ class AssignmentSerializer(ModelSerializer):
|
|||||||
"open_posts",
|
"open_posts",
|
||||||
"phase",
|
"phase",
|
||||||
"assignment_related_users",
|
"assignment_related_users",
|
||||||
"poll_description_default",
|
"default_poll_description",
|
||||||
"agenda_item_id",
|
"agenda_item_id",
|
||||||
"list_of_speakers_id",
|
"list_of_speakers_id",
|
||||||
"agenda_create",
|
"agenda_create",
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
import json
|
import json
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from ..poll.access_permissions import BaseVoteAccessPermissions
|
from ..poll.access_permissions import (
|
||||||
from ..poll.views import BasePoll
|
BasePollAccessPermissions,
|
||||||
|
BaseVoteAccessPermissions,
|
||||||
|
)
|
||||||
from ..utils.access_permissions import BaseAccessPermissions
|
from ..utils.access_permissions import BaseAccessPermissions
|
||||||
from ..utils.auth import async_has_perm, async_in_some_groups
|
from ..utils.auth import async_has_perm, async_in_some_groups
|
||||||
|
|
||||||
@ -183,39 +185,9 @@ class StateAccessPermissions(BaseAccessPermissions):
|
|||||||
base_permission = "motions.can_see"
|
base_permission = "motions.can_see"
|
||||||
|
|
||||||
|
|
||||||
class MotionPollAccessPermissions(BaseAccessPermissions):
|
class MotionPollAccessPermissions(BasePollAccessPermissions):
|
||||||
base_permission = "motions.can_see"
|
base_permission = "motions.can_see"
|
||||||
|
manage_permission = "motions.can_manage_polls"
|
||||||
async def get_restricted_data(
|
|
||||||
self, full_data: List[Dict[str, Any]], user_id: int
|
|
||||||
) -> List[Dict[str, Any]]:
|
|
||||||
"""
|
|
||||||
Poll-managers have full access, even during an active poll.
|
|
||||||
Non-published polls will be restricted:
|
|
||||||
- Remove votes* values from the poll
|
|
||||||
- Remove yes/no/abstain fields from options
|
|
||||||
- Remove voted_id field from the poll
|
|
||||||
"""
|
|
||||||
|
|
||||||
if await async_has_perm(user_id, "motions.can_manage_polls"):
|
|
||||||
data = full_data
|
|
||||||
else:
|
|
||||||
data = []
|
|
||||||
for poll in full_data:
|
|
||||||
if poll["state"] != BasePoll.STATE_PUBLISHED:
|
|
||||||
poll = json.loads(
|
|
||||||
json.dumps(poll)
|
|
||||||
) # copy, so we can remove some fields.
|
|
||||||
del poll["votesvalid"]
|
|
||||||
del poll["votesinvalid"]
|
|
||||||
del poll["votescast"]
|
|
||||||
del poll["voted_id"]
|
|
||||||
for option in poll["options"]:
|
|
||||||
del option["yes"]
|
|
||||||
del option["no"]
|
|
||||||
del option["abstain"]
|
|
||||||
data.append(poll)
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
|
||||||
class MotionVoteAccessPermissions(BaseVoteAccessPermissions):
|
class MotionVoteAccessPermissions(BaseVoteAccessPermissions):
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
|
|
||||||
from openslides.core.config import ConfigVariable
|
from openslides.core.config import ConfigVariable
|
||||||
from openslides.poll.majority import majorityMethods
|
from openslides.motions.models import MotionPoll
|
||||||
|
|
||||||
from .models import Workflow
|
from .models import Workflow
|
||||||
|
|
||||||
@ -348,51 +348,6 @@ def get_config_variables():
|
|||||||
subgroup="Voting and ballot papers",
|
subgroup="Voting and ballot papers",
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: Add server side validation of the choices.
|
|
||||||
yield ConfigVariable(
|
|
||||||
name="motions_poll_default_majority_method",
|
|
||||||
default_value=majorityMethods[0]["value"],
|
|
||||||
input_type="choice",
|
|
||||||
choices=majorityMethods,
|
|
||||||
label="Required majority",
|
|
||||||
help_text="Default method to check whether a motion has reached the required majority.",
|
|
||||||
weight=372,
|
|
||||||
group="Motions",
|
|
||||||
subgroup="Voting and ballot papers",
|
|
||||||
)
|
|
||||||
|
|
||||||
yield ConfigVariable(
|
|
||||||
name="motions_pdf_ballot_papers_selection",
|
|
||||||
default_value="CUSTOM_NUMBER",
|
|
||||||
input_type="choice",
|
|
||||||
label="Number of ballot papers (selection)",
|
|
||||||
choices=(
|
|
||||||
{"value": "NUMBER_OF_DELEGATES", "display_name": "Number of all delegates"},
|
|
||||||
{
|
|
||||||
"value": "NUMBER_OF_ALL_PARTICIPANTS",
|
|
||||||
"display_name": "Number of all participants",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"value": "CUSTOM_NUMBER",
|
|
||||||
"display_name": "Use the following custom number",
|
|
||||||
},
|
|
||||||
),
|
|
||||||
weight=374,
|
|
||||||
group="Motions",
|
|
||||||
subgroup="Voting and ballot papers",
|
|
||||||
)
|
|
||||||
|
|
||||||
yield ConfigVariable(
|
|
||||||
name="motions_pdf_ballot_papers_number",
|
|
||||||
default_value=8,
|
|
||||||
input_type="integer",
|
|
||||||
label="Custom number of ballot papers",
|
|
||||||
weight=376,
|
|
||||||
group="Motions",
|
|
||||||
subgroup="Voting and ballot papers",
|
|
||||||
validators=(MinValueValidator(1),),
|
|
||||||
)
|
|
||||||
|
|
||||||
# PDF export
|
# PDF export
|
||||||
|
|
||||||
yield ConfigVariable(
|
yield ConfigVariable(
|
||||||
@ -432,3 +387,33 @@ def get_config_variables():
|
|||||||
group="Motions",
|
group="Motions",
|
||||||
subgroup="PDF export",
|
subgroup="PDF export",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Polls
|
||||||
|
yield ConfigVariable(
|
||||||
|
name="motion_poll_default_100_percent_base",
|
||||||
|
default_value="YNA",
|
||||||
|
input_type="choice",
|
||||||
|
label="The 100-%-base of an election result consists of",
|
||||||
|
choices=tuple(
|
||||||
|
{"value": base[0], "display_name": base[1]}
|
||||||
|
for base in MotionPoll.PERCENT_BASES
|
||||||
|
),
|
||||||
|
weight=420,
|
||||||
|
group="Polls",
|
||||||
|
subgroup="Motions",
|
||||||
|
)
|
||||||
|
|
||||||
|
yield ConfigVariable(
|
||||||
|
name="motion_poll_default_majority_method",
|
||||||
|
default_value="simple",
|
||||||
|
input_type="choice",
|
||||||
|
choices=tuple(
|
||||||
|
{"value": method[0], "display_name": method[1]}
|
||||||
|
for method in MotionPoll.MAJORITY_METHODS
|
||||||
|
),
|
||||||
|
label="Required majority",
|
||||||
|
help_text="Default method to check whether a candidate has reached the required majority.",
|
||||||
|
weight=425,
|
||||||
|
group="Polls",
|
||||||
|
subgroup="Motions",
|
||||||
|
)
|
||||||
|
@ -81,6 +81,37 @@ class Migration(migrations.Migration):
|
|||||||
name="voted",
|
name="voted",
|
||||||
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
|
field=models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL),
|
||||||
),
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="motionpoll",
|
||||||
|
name="majority_method",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("simple", "Simple majority"),
|
||||||
|
("two_thirds", "Two-thirds majority"),
|
||||||
|
("three_quarters", "Three-quarters majority"),
|
||||||
|
("disabled", "Disabled"),
|
||||||
|
],
|
||||||
|
default="",
|
||||||
|
max_length=14,
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="motionpoll",
|
||||||
|
name="onehundred_percent_base",
|
||||||
|
field=models.CharField(
|
||||||
|
choices=[
|
||||||
|
("YN", "Yes/No per candidate"),
|
||||||
|
("YNA", "Yes/No/Abstain per candidate"),
|
||||||
|
("valid", "All valid ballots"),
|
||||||
|
("cast", "All casted ballots"),
|
||||||
|
("disabled", "Disabled (no percents)"),
|
||||||
|
],
|
||||||
|
default="",
|
||||||
|
max_length=8,
|
||||||
|
),
|
||||||
|
preserve_default=False,
|
||||||
|
),
|
||||||
migrations.AlterField(
|
migrations.AlterField(
|
||||||
model_name="motionvote",
|
model_name="motionvote",
|
||||||
name="option",
|
name="option",
|
107
openslides/motions/migrations/0034_voting_2.py
Normal file
107
openslides/motions/migrations/0034_voting_2.py
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
# Generated by Finn Stutzenstein on 2019-10-30 13:43
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def change_pollmethods(apps, schema_editor):
|
||||||
|
""" yn->YN, yna->YNA """
|
||||||
|
MotionPoll = apps.get_model("motions", "MotionPoll")
|
||||||
|
pollmethod_map = {
|
||||||
|
"yn": "YN",
|
||||||
|
"yna": "YNA",
|
||||||
|
}
|
||||||
|
for poll in MotionPoll.objects.all():
|
||||||
|
poll.pollmethod = pollmethod_map.get(poll.pollmethod, "YNA")
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_poll_titles(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Sets titles to their indexes
|
||||||
|
"""
|
||||||
|
Motion = apps.get_model("motions", "Motion")
|
||||||
|
for motion in Motion.objects.all():
|
||||||
|
for i, poll in enumerate(motion.polls.order_by("pk").all()):
|
||||||
|
poll.title = str(i + 1)
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_onehunderd_percent_bases(apps, schema_editor):
|
||||||
|
MotionPoll = apps.get_model("motions", "MotionPoll")
|
||||||
|
ConfigStore = apps.get_model("core", "ConfigStore")
|
||||||
|
base_map = {
|
||||||
|
"YES_NO_ABSTAIN": "YNA",
|
||||||
|
"YES_NO": "YN",
|
||||||
|
"VALID": "valid",
|
||||||
|
"CAST": "cast",
|
||||||
|
"DISABLED": "disabled",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
config = ConfigStore.objects.get(key="motions_poll_100_percent_base")
|
||||||
|
value = base_map[config.value]
|
||||||
|
except (ConfigStore.DoesNotExist, KeyError):
|
||||||
|
value = "YNA"
|
||||||
|
|
||||||
|
for poll in MotionPoll.objects.all():
|
||||||
|
# The pollmethod is new (default is YNA), so we do not need
|
||||||
|
# to check, if the 100% base is valid.
|
||||||
|
poll.onehundred_percent_base = value
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_majority_methods(apps, schema_editor):
|
||||||
|
MotionPoll = apps.get_model("motions", "MotionPoll")
|
||||||
|
ConfigStore = apps.get_model("core", "ConfigStore")
|
||||||
|
majority_map = {
|
||||||
|
"simple_majority": "simple",
|
||||||
|
"two-thirds_majority": "two_thirds",
|
||||||
|
"three-quarters_majority": "three_quarters",
|
||||||
|
"disabled": "disabled",
|
||||||
|
}
|
||||||
|
try:
|
||||||
|
config = ConfigStore.objects.get(key="motions_poll_default_majority_method")
|
||||||
|
value = majority_map[config.value]
|
||||||
|
except (ConfigStore.DoesNotExist, KeyError):
|
||||||
|
value = "simple"
|
||||||
|
|
||||||
|
for poll in MotionPoll.objects.all():
|
||||||
|
poll.majority_method = value
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_votes(apps, schema_editor):
|
||||||
|
MotionVote = apps.get_model("motions", "MotionVote")
|
||||||
|
value_map = {
|
||||||
|
"Yes": "Y",
|
||||||
|
"No": "N",
|
||||||
|
"Abstain": "A",
|
||||||
|
}
|
||||||
|
for vote in MotionVote.objects.all():
|
||||||
|
vote.value = value_map[vote.value]
|
||||||
|
vote.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
def set_correct_state(apps, schema_editor):
|
||||||
|
""" If there are votes, set the state to finished """
|
||||||
|
MotionPoll = apps.get_model("motions", "MotionPoll")
|
||||||
|
MotionVote = apps.get_model("motions", "MotionVote")
|
||||||
|
for poll in MotionPoll.objects.all():
|
||||||
|
if MotionVote.objects.filter(option__poll__pk=poll.pk).exists():
|
||||||
|
poll.state = 3 # finished
|
||||||
|
poll.save(skip_autoupdate=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("motions", "0033_voting_1"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(change_pollmethods),
|
||||||
|
migrations.RunPython(set_poll_titles),
|
||||||
|
migrations.RunPython(set_onehunderd_percent_bases),
|
||||||
|
migrations.RunPython(set_majority_methods),
|
||||||
|
migrations.RunPython(convert_votes),
|
||||||
|
migrations.RunPython(set_correct_state),
|
||||||
|
]
|
@ -923,9 +923,6 @@ class MotionPollManager(models.Manager):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
# Meta-TODO: Is this todo resolved?
|
|
||||||
# TODO: remove the type-ignoring in the next line, after this is solved:
|
|
||||||
# https://github.com/python/mypy/issues/3855
|
|
||||||
class MotionPoll(RESTModelMixin, BasePoll):
|
class MotionPoll(RESTModelMixin, BasePoll):
|
||||||
access_permissions = MotionPollAccessPermissions()
|
access_permissions = MotionPollAccessPermissions()
|
||||||
option_class = MotionOption
|
option_class = MotionOption
|
||||||
|
@ -5,6 +5,9 @@ from openslides.poll.serializers import (
|
|||||||
BASE_OPTION_FIELDS,
|
BASE_OPTION_FIELDS,
|
||||||
BASE_POLL_FIELDS,
|
BASE_POLL_FIELDS,
|
||||||
BASE_VOTE_FIELDS,
|
BASE_VOTE_FIELDS,
|
||||||
|
BaseOptionSerializer,
|
||||||
|
BasePollSerializer,
|
||||||
|
BaseVoteSerializer,
|
||||||
)
|
)
|
||||||
|
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
@ -13,7 +16,6 @@ from ..utils.autoupdate import inform_changed_data
|
|||||||
from ..utils.rest_api import (
|
from ..utils.rest_api import (
|
||||||
BooleanField,
|
BooleanField,
|
||||||
CharField,
|
CharField,
|
||||||
DecimalField,
|
|
||||||
Field,
|
Field,
|
||||||
IdPrimaryKeyRelatedField,
|
IdPrimaryKeyRelatedField,
|
||||||
IntegerField,
|
IntegerField,
|
||||||
@ -224,64 +226,47 @@ class AmendmentParagraphsJSONSerializerField(Field):
|
|||||||
return data
|
return data
|
||||||
|
|
||||||
|
|
||||||
class MotionVoteSerializer(ModelSerializer):
|
class MotionVoteSerializer(BaseVoteSerializer):
|
||||||
pollstate = SerializerMethodField()
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MotionVote
|
model = MotionVote
|
||||||
fields = ("pollstate",) + BASE_VOTE_FIELDS
|
fields = BASE_VOTE_FIELDS
|
||||||
read_only_fields = BASE_VOTE_FIELDS
|
read_only_fields = BASE_VOTE_FIELDS
|
||||||
|
|
||||||
def get_pollstate(self, vote):
|
|
||||||
return vote.option.poll.state
|
|
||||||
|
|
||||||
|
|
||||||
class MotionOptionSerializer(ModelSerializer):
|
|
||||||
yes = DecimalField(max_digits=15, decimal_places=6, min_value=-2, read_only=True)
|
|
||||||
no = DecimalField(max_digits=15, decimal_places=6, min_value=-2, read_only=True)
|
|
||||||
abstain = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
|
class MotionOptionSerializer(BaseOptionSerializer):
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MotionOption
|
model = MotionOption
|
||||||
fields = BASE_OPTION_FIELDS
|
fields = BASE_OPTION_FIELDS
|
||||||
read_only_fields = BASE_OPTION_FIELDS
|
read_only_fields = BASE_OPTION_FIELDS
|
||||||
|
|
||||||
|
|
||||||
class MotionPollSerializer(ModelSerializer):
|
class MotionPollSerializer(BasePollSerializer):
|
||||||
"""
|
"""
|
||||||
Serializer for motion.models.MotionPoll objects.
|
Serializer for motion.models.MotionPoll objects.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
options = MotionOptionSerializer(many=True, read_only=True)
|
options = MotionOptionSerializer(many=True, read_only=True)
|
||||||
|
|
||||||
title = CharField(allow_blank=False, required=True)
|
|
||||||
groups = IdPrimaryKeyRelatedField(
|
|
||||||
many=True, required=False, queryset=get_group_model().objects.all()
|
|
||||||
)
|
|
||||||
voted = IdPrimaryKeyRelatedField(many=True, read_only=True)
|
|
||||||
|
|
||||||
votesvalid = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
|
||||||
)
|
|
||||||
votesinvalid = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
|
||||||
)
|
|
||||||
votescast = DecimalField(
|
|
||||||
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
|
||||||
)
|
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = MotionPoll
|
model = MotionPoll
|
||||||
fields = ("motion", "pollmethod") + BASE_POLL_FIELDS
|
fields = ("motion", "pollmethod") + BASE_POLL_FIELDS
|
||||||
read_only_fields = ("state",)
|
read_only_fields = ("state",)
|
||||||
|
|
||||||
def update(self, instance, validated_data):
|
def update(self, instance, validated_data):
|
||||||
""" Prevent from updating the motion """
|
""" Prevent updating the motion """
|
||||||
validated_data.pop("motion", None)
|
validated_data.pop("motion", None)
|
||||||
return super().update(instance, validated_data)
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
|
def norm_100_percent_base_to_pollmethod(
|
||||||
|
self, onehundred_percent_base, pollmethod, old_100_percent_base=None
|
||||||
|
):
|
||||||
|
if (
|
||||||
|
pollmethod == MotionPoll.POLLMETHOD_YN
|
||||||
|
and onehundred_percent_base == MotionPoll.PERCENT_BASE_YNA
|
||||||
|
):
|
||||||
|
return MotionPoll.PERCENT_BASE_YN
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class MotionChangeRecommendationSerializer(ModelSerializer):
|
class MotionChangeRecommendationSerializer(ModelSerializer):
|
||||||
"""
|
"""
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import json
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from ..poll.views import BasePoll
|
from ..poll.views import BasePoll
|
||||||
@ -5,6 +6,47 @@ from ..utils.access_permissions import BaseAccessPermissions
|
|||||||
from ..utils.auth import async_has_perm
|
from ..utils.auth import async_has_perm
|
||||||
|
|
||||||
|
|
||||||
|
class BasePollAccessPermissions(BaseAccessPermissions):
|
||||||
|
manage_permission = "" # set by subclass
|
||||||
|
|
||||||
|
additional_fields: List[str] = []
|
||||||
|
""" Add fields to be removed from each unpublished poll """
|
||||||
|
|
||||||
|
async def get_restricted_data(
|
||||||
|
self, full_data: List[Dict[str, Any]], user_id: int
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Poll-managers have full access, even during an active poll.
|
||||||
|
Non-published polls will be restricted:
|
||||||
|
- Remove votes* values from the poll
|
||||||
|
- Remove yes/no/abstain fields from options
|
||||||
|
- Remove voted_id field from the poll
|
||||||
|
- Remove fields given in self.assitional_fields from the poll
|
||||||
|
"""
|
||||||
|
|
||||||
|
if await async_has_perm(user_id, self.manage_permission):
|
||||||
|
data = full_data
|
||||||
|
else:
|
||||||
|
data = []
|
||||||
|
for poll in full_data:
|
||||||
|
if poll["state"] != BasePoll.STATE_PUBLISHED:
|
||||||
|
poll = json.loads(
|
||||||
|
json.dumps(poll)
|
||||||
|
) # copy, so we can remove some fields.
|
||||||
|
del poll["votesvalid"]
|
||||||
|
del poll["votesinvalid"]
|
||||||
|
del poll["votescast"]
|
||||||
|
del poll["voted_id"]
|
||||||
|
for field in self.additional_fields:
|
||||||
|
del poll[field]
|
||||||
|
for option in poll["options"]:
|
||||||
|
del option["yes"]
|
||||||
|
del option["no"]
|
||||||
|
del option["abstain"]
|
||||||
|
data.append(poll)
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class BaseVoteAccessPermissions(BaseAccessPermissions):
|
class BaseVoteAccessPermissions(BaseAccessPermissions):
|
||||||
manage_permission = "" # set by subclass
|
manage_permission = "" # set by subclass
|
||||||
|
|
||||||
|
@ -1,7 +0,0 @@
|
|||||||
# Common majority methods for all apps using polls. The first one should be the default.
|
|
||||||
majorityMethods = (
|
|
||||||
{"value": "simple_majority", "display_name": "Simple majority"},
|
|
||||||
{"value": "two-thirds_majority", "display_name": "Two-thirds majority"},
|
|
||||||
{"value": "three-quarters_majority", "display_name": "Three-quarters majority"},
|
|
||||||
{"value": "disabled", "display_name": "Disabled"},
|
|
||||||
)
|
|
@ -1,5 +1,5 @@
|
|||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from typing import Optional, Type
|
from typing import Iterable, Optional, Tuple, Type
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.validators import MinValueValidator
|
from django.core.validators import MinValueValidator
|
||||||
@ -131,6 +131,36 @@ class BasePoll(models.Model):
|
|||||||
decimal_places=6,
|
decimal_places=6,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
PERCENT_BASE_YN = "YN"
|
||||||
|
PERCENT_BASE_YNA = "YNA"
|
||||||
|
PERCENT_BASE_VALID = "valid"
|
||||||
|
PERCENT_BASE_CAST = "cast"
|
||||||
|
PERCENT_BASE_DISABLED = "disabled"
|
||||||
|
PERCENT_BASES: Iterable[Tuple[str, str]] = (
|
||||||
|
(PERCENT_BASE_YN, "Yes/No per candidate"),
|
||||||
|
(PERCENT_BASE_YNA, "Yes/No/Abstain per candidate"),
|
||||||
|
(PERCENT_BASE_VALID, "All valid ballots"),
|
||||||
|
(PERCENT_BASE_CAST, "All casted ballots"),
|
||||||
|
(PERCENT_BASE_DISABLED, "Disabled (no percents)"),
|
||||||
|
) # type: ignore
|
||||||
|
onehundred_percent_base = models.CharField(
|
||||||
|
max_length=8, blank=False, null=False, choices=PERCENT_BASES
|
||||||
|
)
|
||||||
|
|
||||||
|
MAJORITY_SIMPLE = "simple"
|
||||||
|
MAJORITY_TWO_THIRDS = "two_thirds"
|
||||||
|
MAJORITY_THREE_QUARTERS = "three_quarters"
|
||||||
|
MAJORITY_DISABLED = "disabled"
|
||||||
|
MAJORITY_METHODS = (
|
||||||
|
(MAJORITY_SIMPLE, "Simple majority"),
|
||||||
|
(MAJORITY_TWO_THIRDS, "Two-thirds majority"),
|
||||||
|
(MAJORITY_THREE_QUARTERS, "Three-quarters majority"),
|
||||||
|
(MAJORITY_DISABLED, "Disabled"),
|
||||||
|
)
|
||||||
|
majority_method = models.CharField(
|
||||||
|
max_length=14, blank=False, null=False, choices=MAJORITY_METHODS
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
|
||||||
@ -201,8 +231,6 @@ class BasePoll(models.Model):
|
|||||||
def get_votes(self):
|
def get_votes(self):
|
||||||
"""
|
"""
|
||||||
Return a QuerySet with all vote objects related to this poll.
|
Return a QuerySet with all vote objects related to this poll.
|
||||||
|
|
||||||
TODO: This might be a performance issue when used in properties that are serialized.
|
|
||||||
"""
|
"""
|
||||||
return self.get_vote_class().objects.filter(option__poll__id=self.id)
|
return self.get_vote_class().objects.filter(option__poll__id=self.id)
|
||||||
|
|
||||||
|
@ -1,3 +1,34 @@
|
|||||||
|
from ..utils.auth import get_group_model
|
||||||
|
from ..utils.rest_api import (
|
||||||
|
CharField,
|
||||||
|
DecimalField,
|
||||||
|
IdPrimaryKeyRelatedField,
|
||||||
|
ModelSerializer,
|
||||||
|
SerializerMethodField,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
BASE_VOTE_FIELDS = ("id", "weight", "value", "user", "option", "pollstate")
|
||||||
|
|
||||||
|
|
||||||
|
class BaseVoteSerializer(ModelSerializer):
|
||||||
|
pollstate = SerializerMethodField()
|
||||||
|
|
||||||
|
def get_pollstate(self, vote):
|
||||||
|
return vote.option.poll.state
|
||||||
|
|
||||||
|
|
||||||
|
BASE_OPTION_FIELDS = ("id", "yes", "no", "abstain")
|
||||||
|
|
||||||
|
|
||||||
|
class BaseOptionSerializer(ModelSerializer):
|
||||||
|
yes = DecimalField(max_digits=15, decimal_places=6, min_value=-2, read_only=True)
|
||||||
|
no = DecimalField(max_digits=15, decimal_places=6, min_value=-2, read_only=True)
|
||||||
|
abstain = DecimalField(
|
||||||
|
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
BASE_POLL_FIELDS = (
|
BASE_POLL_FIELDS = (
|
||||||
"state",
|
"state",
|
||||||
"type",
|
"type",
|
||||||
@ -9,8 +40,62 @@ BASE_POLL_FIELDS = (
|
|||||||
"options",
|
"options",
|
||||||
"voted",
|
"voted",
|
||||||
"id",
|
"id",
|
||||||
|
"onehundred_percent_base",
|
||||||
|
"majority_method",
|
||||||
)
|
)
|
||||||
|
|
||||||
BASE_OPTION_FIELDS = ("id", "yes", "no", "abstain")
|
|
||||||
|
|
||||||
BASE_VOTE_FIELDS = ("id", "weight", "value", "user", "option")
|
class BasePollSerializer(ModelSerializer):
|
||||||
|
title = CharField(allow_blank=False, required=True)
|
||||||
|
groups = IdPrimaryKeyRelatedField(
|
||||||
|
many=True, required=False, queryset=get_group_model().objects.all()
|
||||||
|
)
|
||||||
|
voted = IdPrimaryKeyRelatedField(many=True, read_only=True)
|
||||||
|
|
||||||
|
votesvalid = DecimalField(
|
||||||
|
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
||||||
|
)
|
||||||
|
votesinvalid = DecimalField(
|
||||||
|
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
||||||
|
)
|
||||||
|
votescast = DecimalField(
|
||||||
|
max_digits=15, decimal_places=6, min_value=-2, read_only=True
|
||||||
|
)
|
||||||
|
|
||||||
|
def create(self, validated_data):
|
||||||
|
"""
|
||||||
|
Match the 100 percent base to the pollmethod. Change the base, if it does not
|
||||||
|
fit to the pollmethod
|
||||||
|
"""
|
||||||
|
new_100_percent_base = self.norm_100_percent_base_to_pollmethod(
|
||||||
|
validated_data["onehundred_percent_base"], validated_data["pollmethod"]
|
||||||
|
)
|
||||||
|
if new_100_percent_base is not None:
|
||||||
|
validated_data["onehundred_percent_base"] = new_100_percent_base
|
||||||
|
return super().create(validated_data)
|
||||||
|
|
||||||
|
def update(self, instance, validated_data):
|
||||||
|
"""
|
||||||
|
Adjusts the 100%-base to the pollmethod. This might be needed,
|
||||||
|
if at least one of them was changed. Wrong comobinations should be
|
||||||
|
also handled by the client, but here we make it sure aswell!
|
||||||
|
|
||||||
|
E.g. the pollmethod is YN, but the 100%-base is YNA, this micht noght be
|
||||||
|
possible (see implementing serializers to see forbidden combinations)
|
||||||
|
"""
|
||||||
|
old_100_percent_base = instance.onehundred_percent_base
|
||||||
|
instance = super().update(instance, validated_data)
|
||||||
|
|
||||||
|
new_100_percent_base = self.norm_100_percent_base_to_pollmethod(
|
||||||
|
instance.onehundred_percent_base, instance.pollmethod, old_100_percent_base
|
||||||
|
)
|
||||||
|
if new_100_percent_base is not None:
|
||||||
|
instance.onehundred_percent_base = new_100_percent_base
|
||||||
|
instance.save()
|
||||||
|
|
||||||
|
return instance
|
||||||
|
|
||||||
|
def norm_100_percent_base_to_pollmethod(
|
||||||
|
self, onehundred_percent_base, pollmethod, old_100_percent_base=None
|
||||||
|
):
|
||||||
|
raise NotImplementedError()
|
||||||
|
@ -91,7 +91,7 @@ def create_assignment_polls():
|
|||||||
class CreateAssignmentPoll(TestCase):
|
class CreateAssignmentPoll(TestCase):
|
||||||
def advancedSetUp(self):
|
def advancedSetUp(self):
|
||||||
self.assignment = Assignment.objects.create(
|
self.assignment = Assignment.objects.create(
|
||||||
title="test_assignment_ohneivoh9caiB8Yiungo", open_posts=1
|
title="test_assignment_ohneivoh9caiB8Yiungo", open_posts=1,
|
||||||
)
|
)
|
||||||
self.assignment.add_candidate(self.admin)
|
self.assignment.add_candidate(self.admin)
|
||||||
|
|
||||||
@ -100,24 +100,28 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
reverse("assignmentpoll-list"),
|
reverse("assignmentpoll-list"),
|
||||||
{
|
{
|
||||||
"title": "test_title_ailai4toogh3eefaa2Vo",
|
"title": "test_title_ailai4toogh3eefaa2Vo",
|
||||||
"pollmethod": "YNA",
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertTrue(AssignmentPoll.objects.exists())
|
self.assertTrue(AssignmentPoll.objects.exists())
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.title, "test_title_ailai4toogh3eefaa2Vo")
|
self.assertEqual(poll.title, "test_title_ailai4toogh3eefaa2Vo")
|
||||||
self.assertEqual(poll.pollmethod, "YNA")
|
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_YNA)
|
||||||
self.assertEqual(poll.type, "named")
|
self.assertEqual(poll.type, "named")
|
||||||
# Check defaults
|
# Check defaults
|
||||||
self.assertTrue(poll.global_no)
|
self.assertTrue(poll.global_no)
|
||||||
self.assertTrue(poll.global_abstain)
|
self.assertTrue(poll.global_abstain)
|
||||||
|
self.assertEqual(poll.amount_global_no, None)
|
||||||
|
self.assertEqual(poll.amount_global_abstain, None)
|
||||||
self.assertFalse(poll.allow_multiple_votes_per_candidate)
|
self.assertFalse(poll.allow_multiple_votes_per_candidate)
|
||||||
self.assertEqual(poll.votes_amount, 1)
|
self.assertEqual(poll.votes_amount, 1)
|
||||||
self.assertEqual(poll.assignment.id, self.assignment.id)
|
self.assertEqual(poll.assignment.id, self.assignment.id)
|
||||||
|
self.assertEqual(poll.description, "")
|
||||||
self.assertTrue(poll.options.exists())
|
self.assertTrue(poll.options.exists())
|
||||||
option = AssignmentOption.objects.get()
|
option = AssignmentOption.objects.get()
|
||||||
self.assertTrue(option.user.id, self.admin.id)
|
self.assertTrue(option.user.id, self.admin.id)
|
||||||
@ -127,26 +131,29 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
reverse("assignmentpoll-list"),
|
reverse("assignmentpoll-list"),
|
||||||
{
|
{
|
||||||
"title": "test_title_ahThai4pae1pi4xoogoo",
|
"title": "test_title_ahThai4pae1pi4xoogoo",
|
||||||
"pollmethod": "YN",
|
"pollmethod": AssignmentPoll.POLLMETHOD_YN,
|
||||||
"type": "pseudoanonymous",
|
"type": "pseudoanonymous",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_THREE_QUARTERS,
|
||||||
"global_no": False,
|
"global_no": False,
|
||||||
"global_abstain": False,
|
"global_abstain": False,
|
||||||
"allow_multiple_votes_per_candidate": True,
|
"allow_multiple_votes_per_candidate": True,
|
||||||
"votes_amount": 5,
|
"votes_amount": 5,
|
||||||
|
"description": "test_description_ieM8ThuasoSh8aecai8p",
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertTrue(AssignmentPoll.objects.exists())
|
self.assertTrue(AssignmentPoll.objects.exists())
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.title, "test_title_ahThai4pae1pi4xoogoo")
|
self.assertEqual(poll.title, "test_title_ahThai4pae1pi4xoogoo")
|
||||||
self.assertEqual(poll.pollmethod, "YN")
|
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_YN)
|
||||||
self.assertEqual(poll.type, "pseudoanonymous")
|
self.assertEqual(poll.type, "pseudoanonymous")
|
||||||
self.assertFalse(poll.global_no)
|
self.assertFalse(poll.global_no)
|
||||||
self.assertFalse(poll.global_abstain)
|
self.assertFalse(poll.global_abstain)
|
||||||
self.assertTrue(poll.allow_multiple_votes_per_candidate)
|
self.assertTrue(poll.allow_multiple_votes_per_candidate)
|
||||||
self.assertEqual(poll.votes_amount, 5)
|
self.assertEqual(poll.votes_amount, 5)
|
||||||
|
self.assertEqual(poll.description, "test_description_ieM8ThuasoSh8aecai8p")
|
||||||
|
|
||||||
def test_no_candidates(self):
|
def test_no_candidates(self):
|
||||||
self.assignment.remove_candidate(self.admin)
|
self.assignment.remove_candidate(self.admin)
|
||||||
@ -154,60 +161,32 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
reverse("assignmentpoll-list"),
|
reverse("assignmentpoll-list"),
|
||||||
{
|
{
|
||||||
"title": "test_title_eing5eipue5cha2Iefai",
|
"title": "test_title_eing5eipue5cha2Iefai",
|
||||||
"pollmethod": "YNA",
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
|
|
||||||
def test_missing_title(self):
|
def test_missing_keys(self):
|
||||||
response = self.client.post(
|
complete_request_data = {
|
||||||
reverse("assignmentpoll-list"),
|
"title": "test_title_keugh8Iu9ciyooGaevoh",
|
||||||
{"pollmethod": "YNA", "type": "named", "assignment_id": self.assignment.id},
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
|
||||||
|
|
||||||
def test_missing_pollmethod(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("assignmentpoll-list"),
|
|
||||||
{
|
|
||||||
"title": "test_title_OoCh9aitaeyaeth8nom1",
|
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
},
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
format="json",
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
)
|
}
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
for key in complete_request_data.keys():
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
request_data = {
|
||||||
|
_key: value
|
||||||
def test_missing_type(self):
|
for _key, value in complete_request_data.items()
|
||||||
response = self.client.post(
|
if _key != key
|
||||||
reverse("assignmentpoll-list"),
|
}
|
||||||
{
|
response = self.client.post(reverse("assignmentpoll-list"), request_data)
|
||||||
"title": "test_title_Ail9Eizohshim0fora6o",
|
|
||||||
"pollmethod": "YNA",
|
|
||||||
"assignment_id": self.assignment.id,
|
|
||||||
},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
|
||||||
|
|
||||||
def test_missing_assignment_id(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("assignmentpoll-list"),
|
|
||||||
{
|
|
||||||
"title": "test_title_eic7ooxaht5mee3quohK",
|
|
||||||
"pollmethod": "YNA",
|
|
||||||
"type": "named",
|
|
||||||
},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
|
|
||||||
@ -218,12 +197,13 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
reverse("assignmentpoll-list"),
|
reverse("assignmentpoll-list"),
|
||||||
{
|
{
|
||||||
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
"pollmethod": "YNA",
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
"groups_id": [1, 2],
|
"groups_id": [1, 2],
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -235,12 +215,13 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
reverse("assignmentpoll-list"),
|
reverse("assignmentpoll-list"),
|
||||||
{
|
{
|
||||||
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
"pollmethod": "YNA",
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
"groups_id": [],
|
"groups_id": [],
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -251,11 +232,12 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
reverse("assignmentpoll-list"),
|
reverse("assignmentpoll-list"),
|
||||||
{
|
{
|
||||||
"title": "test_title_yaiyeighoh0Iraet3Ahc",
|
"title": "test_title_yaiyeighoh0Iraet3Ahc",
|
||||||
"pollmethod": "YNA",
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"type": "not_existing",
|
"type": "not_existing",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
@ -268,12 +250,93 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
"pollmethod": "not_existing",
|
"pollmethod": "not_existing",
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"assignment_id": self.assignment.id,
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
|
|
||||||
|
def test_not_supported_onehundred_percent_base(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": "invalid base",
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
|
|
||||||
|
def test_not_supported_majority_method(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YN,
|
||||||
|
"majority_method": "invalid majority method",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
|
|
||||||
|
def test_wrong_pollmethod_onehundred_percent_base_combination_1(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_VOTES,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_YNA)
|
||||||
|
|
||||||
|
def test_wrong_pollmethod_onehundred_percent_base_combination_2(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_YN,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_VOTES,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_YN)
|
||||||
|
|
||||||
|
def test_wrong_pollmethod_onehundred_percent_base_combination_3(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_Thoo2eiphohhi1eeXoow",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(
|
||||||
|
poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_VOTES
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class UpdateAssignmentPoll(TestCase):
|
class UpdateAssignmentPoll(TestCase):
|
||||||
"""
|
"""
|
||||||
@ -289,8 +352,10 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
self.poll = AssignmentPoll.objects.create(
|
self.poll = AssignmentPoll.objects.create(
|
||||||
assignment=self.assignment,
|
assignment=self.assignment,
|
||||||
title="test_title_beeFaihuNae1vej2ai8m",
|
title="test_title_beeFaihuNae1vej2ai8m",
|
||||||
pollmethod="votes",
|
pollmethod=AssignmentPoll.POLLMETHOD_VOTES,
|
||||||
type=BasePoll.TYPE_NAMED,
|
type=BasePoll.TYPE_NAMED,
|
||||||
|
onehundred_percent_base=AssignmentPoll.PERCENT_BASE_VOTES,
|
||||||
|
majority_method=AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
)
|
)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
self.poll.groups.add(self.group)
|
self.poll.groups.add(self.group)
|
||||||
@ -317,11 +382,13 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
|
|
||||||
def test_patch_pollmethod(self):
|
def test_patch_pollmethod(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("assignmentpoll-detail", args=[self.poll.pk]), {"pollmethod": "YNA"}
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"pollmethod": AssignmentPoll.POLLMETHOD_YNA},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.pollmethod, "YNA")
|
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_YNA)
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_YNA)
|
||||||
|
|
||||||
def test_patch_invalid_pollmethod(self):
|
def test_patch_invalid_pollmethod(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
@ -330,7 +397,7 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.pollmethod, "votes")
|
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_VOTES)
|
||||||
|
|
||||||
def test_patch_type(self):
|
def test_patch_type(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
@ -350,9 +417,7 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
|
|
||||||
def test_patch_groups_to_empty(self):
|
def test_patch_groups_to_empty(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
reverse("assignmentpoll-detail", args=[self.poll.pk]), {"groups_id": []},
|
||||||
{"groups_id": []},
|
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -363,7 +428,6 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
{"groups_id": [group2.id]},
|
{"groups_id": [group2.id]},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -381,12 +445,50 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.title, "test_title_beeFaihuNae1vej2ai8m")
|
self.assertEqual(poll.title, "test_title_beeFaihuNae1vej2ai8m")
|
||||||
|
|
||||||
|
def test_patch_100_percent_base(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"onehundred_percent_base": "cast"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, "cast")
|
||||||
|
|
||||||
|
def test_patch_wrong_100_percent_base(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"onehundred_percent_base": "invalid"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(
|
||||||
|
poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_VOTES
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_patch_majority_method(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.majority_method, AssignmentPoll.MAJORITY_TWO_THIRDS)
|
||||||
|
|
||||||
|
def test_patch_wrong_majority_method(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"majority_method": "invalid majority method"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.majority_method, AssignmentPoll.MAJORITY_SIMPLE)
|
||||||
|
|
||||||
def test_patch_multiple_fields(self):
|
def test_patch_multiple_fields(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"title": "test_title_ees6Tho8ahheen4cieja",
|
"title": "test_title_ees6Tho8ahheen4cieja",
|
||||||
"pollmethod": "votes",
|
"pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
|
||||||
"global_no": True,
|
"global_no": True,
|
||||||
"global_abstain": False,
|
"global_abstain": False,
|
||||||
"allow_multiple_votes_per_candidate": True,
|
"allow_multiple_votes_per_candidate": True,
|
||||||
@ -396,9 +498,11 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
self.assertEqual(poll.title, "test_title_ees6Tho8ahheen4cieja")
|
self.assertEqual(poll.title, "test_title_ees6Tho8ahheen4cieja")
|
||||||
self.assertEqual(poll.pollmethod, "votes")
|
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_VOTES)
|
||||||
self.assertTrue(poll.global_no)
|
self.assertTrue(poll.global_no)
|
||||||
self.assertFalse(poll.global_abstain)
|
self.assertFalse(poll.global_abstain)
|
||||||
|
self.assertEqual(poll.amount_global_no, Decimal("0"))
|
||||||
|
self.assertEqual(poll.amount_global_abstain, None)
|
||||||
self.assertTrue(poll.allow_multiple_votes_per_candidate)
|
self.assertTrue(poll.allow_multiple_votes_per_candidate)
|
||||||
self.assertEqual(poll.votes_amount, 42)
|
self.assertEqual(poll.votes_amount, 42)
|
||||||
|
|
||||||
@ -462,7 +566,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
"votesvalid": "4.64",
|
"votesvalid": "4.64",
|
||||||
"votesinvalid": "-2",
|
"votesinvalid": "-2",
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
self.assertEqual(AssignmentVote.objects.count(), 6)
|
self.assertEqual(AssignmentVote.objects.count(), 6)
|
||||||
@ -490,7 +593,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
"2": {"Y": "1", "N": "2.35", "A": "-1"},
|
"2": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -501,7 +603,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": {"1": {"Y": "1", "N": "2.35", "A": "-1"}}},
|
{"options": {"1": {"Y": "1", "N": "2.35", "A": "-1"}}},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -517,7 +618,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
"3": {"Y": "1", "N": "2.35", "A": "-1"},
|
"3": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -543,9 +643,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
def test_wrong_data_format(self):
|
def test_wrong_data_format(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), [1, 2, 5],
|
||||||
[1, 2, 5],
|
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -555,7 +653,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": [1, "string"]},
|
{"options": [1, "string"]},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -565,7 +662,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": {"string": "some_other_string"}},
|
{"options": {"string": "some_other_string"}},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -575,7 +671,6 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": {"1": [None]}},
|
{"options": {"1": [None]}},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -586,7 +681,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
data = {"options": {"1": {"Y": "1", "N": "3", "A": "-1"}}}
|
data = {"options": {"1": {"Y": "1", "N": "3", "A": "-1"}}}
|
||||||
del data["options"]["1"][value]
|
del data["options"]["1"][value]
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), data, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), data
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -861,6 +956,8 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(option.yes, Decimal("0"))
|
self.assertEqual(option.yes, Decimal("0"))
|
||||||
self.assertEqual(option.no, Decimal("2"))
|
self.assertEqual(option.no, Decimal("2"))
|
||||||
self.assertEqual(option.abstain, Decimal("0"))
|
self.assertEqual(option.abstain, Decimal("0"))
|
||||||
|
self.assertEqual(poll.amount_global_no, Decimal("2"))
|
||||||
|
self.assertEqual(poll.amount_global_abstain, Decimal("0"))
|
||||||
|
|
||||||
def test_global_no_forbidden(self):
|
def test_global_no_forbidden(self):
|
||||||
self.poll.global_no = False
|
self.poll.global_no = False
|
||||||
@ -871,6 +968,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
self.assertEqual(AssignmentPoll.objects.get().amount_global_no, None)
|
||||||
|
|
||||||
def test_global_abstain(self):
|
def test_global_abstain(self):
|
||||||
self.poll.votes_amount = 2
|
self.poll.votes_amount = 2
|
||||||
@ -885,6 +983,8 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(option.yes, Decimal("0"))
|
self.assertEqual(option.yes, Decimal("0"))
|
||||||
self.assertEqual(option.no, Decimal("0"))
|
self.assertEqual(option.no, Decimal("0"))
|
||||||
self.assertEqual(option.abstain, Decimal("2"))
|
self.assertEqual(option.abstain, Decimal("2"))
|
||||||
|
self.assertEqual(poll.amount_global_no, Decimal("0"))
|
||||||
|
self.assertEqual(poll.amount_global_abstain, Decimal("2"))
|
||||||
|
|
||||||
def test_global_abstain_forbidden(self):
|
def test_global_abstain_forbidden(self):
|
||||||
self.poll.global_abstain = False
|
self.poll.global_abstain = False
|
||||||
@ -895,6 +995,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
self.assertEqual(AssignmentPoll.objects.get().amount_global_abstain, None)
|
||||||
|
|
||||||
def test_negative_vote(self):
|
def test_negative_vote(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
@ -1463,12 +1564,16 @@ class VoteAssignmentPollAutoupdatesBaseClass(TestCase):
|
|||||||
title="test_assignment_" + self._get_random_string(), open_posts=1
|
title="test_assignment_" + self._get_random_string(), open_posts=1
|
||||||
)
|
)
|
||||||
self.assignment.add_candidate(self.admin)
|
self.assignment.add_candidate(self.admin)
|
||||||
|
self.description = "test_description_paiquei5ahpie1wu8ohW"
|
||||||
self.poll = AssignmentPoll.objects.create(
|
self.poll = AssignmentPoll.objects.create(
|
||||||
assignment=self.assignment,
|
assignment=self.assignment,
|
||||||
title="test_title_" + self._get_random_string(),
|
title="test_title_" + self._get_random_string(),
|
||||||
pollmethod=AssignmentPoll.POLLMETHOD_YNA,
|
pollmethod=AssignmentPoll.POLLMETHOD_YNA,
|
||||||
type=self.poll_type,
|
type=self.poll_type,
|
||||||
state=AssignmentPoll.STATE_STARTED,
|
state=AssignmentPoll.STATE_STARTED,
|
||||||
|
onehundred_percent_base=AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
|
majority_method=AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
|
description=self.description,
|
||||||
)
|
)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
self.poll.groups.add(self.delegate_group)
|
self.poll.groups.add(self.delegate_group)
|
||||||
@ -1495,6 +1600,8 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"assignment_id": 1,
|
"assignment_id": 1,
|
||||||
"global_abstain": True,
|
"global_abstain": True,
|
||||||
"global_no": True,
|
"global_no": True,
|
||||||
|
"amount_global_abstain": None,
|
||||||
|
"amount_global_no": None,
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"options": [
|
"options": [
|
||||||
@ -1510,7 +1617,10 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"state": AssignmentPoll.STATE_STARTED,
|
"state": AssignmentPoll.STATE_STARTED,
|
||||||
"title": self.poll.title,
|
"title": self.poll.title,
|
||||||
|
"description": self.description,
|
||||||
"type": AssignmentPoll.TYPE_NAMED,
|
"type": AssignmentPoll.TYPE_NAMED,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
"votes_amount": 1,
|
"votes_amount": 1,
|
||||||
"votescast": "1.000000",
|
"votescast": "1.000000",
|
||||||
@ -1558,7 +1668,10 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"state": AssignmentPoll.STATE_STARTED,
|
"state": AssignmentPoll.STATE_STARTED,
|
||||||
"type": AssignmentPoll.TYPE_NAMED,
|
"type": AssignmentPoll.TYPE_NAMED,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
"title": self.poll.title,
|
"title": self.poll.title,
|
||||||
|
"description": self.description,
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"options": [{"id": 1, "user_id": self.admin.id, "weight": 1}],
|
"options": [{"id": 1, "user_id": self.admin.id, "weight": 1}],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
@ -1593,6 +1706,8 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"assignment_id": 1,
|
"assignment_id": 1,
|
||||||
"global_abstain": True,
|
"global_abstain": True,
|
||||||
"global_no": True,
|
"global_no": True,
|
||||||
|
"amount_global_abstain": None,
|
||||||
|
"amount_global_no": None,
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"options": [
|
"options": [
|
||||||
@ -1608,7 +1723,10 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"state": AssignmentPoll.STATE_STARTED,
|
"state": AssignmentPoll.STATE_STARTED,
|
||||||
"title": self.poll.title,
|
"title": self.poll.title,
|
||||||
|
"description": self.description,
|
||||||
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
"votes_amount": 1,
|
"votes_amount": 1,
|
||||||
"votescast": "1.000000",
|
"votescast": "1.000000",
|
||||||
@ -1642,7 +1760,10 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
|
||||||
"state": AssignmentPoll.STATE_STARTED,
|
"state": AssignmentPoll.STATE_STARTED,
|
||||||
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
"title": self.poll.title,
|
"title": self.poll.title,
|
||||||
|
"description": self.description,
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"options": [{"id": 1, "user_id": self.admin.id, "weight": 1}],
|
"options": [{"id": 1, "user_id": self.admin.id, "weight": 1}],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
|
@ -95,8 +95,9 @@ class CreateMotionPoll(TestCase):
|
|||||||
"pollmethod": "YNA",
|
"pollmethod": "YNA",
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"motion_id": self.motion.id,
|
"motion_id": self.motion.id,
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
self.assertTrue(MotionPoll.objects.exists())
|
self.assertTrue(MotionPoll.objects.exists())
|
||||||
@ -107,51 +108,22 @@ class CreateMotionPoll(TestCase):
|
|||||||
self.assertEqual(poll.motion.id, self.motion.id)
|
self.assertEqual(poll.motion.id, self.motion.id)
|
||||||
self.assertTrue(poll.options.exists())
|
self.assertTrue(poll.options.exists())
|
||||||
|
|
||||||
def test_missing_title(self):
|
def test_missing_keys(self):
|
||||||
response = self.client.post(
|
complete_request_data = {
|
||||||
reverse("motionpoll-list"),
|
|
||||||
{"pollmethod": "YNA", "type": "named", "motion_id": self.motion.id},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertFalse(MotionPoll.objects.exists())
|
|
||||||
|
|
||||||
def test_missing_pollmethod(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("motionpoll-list"),
|
|
||||||
{
|
|
||||||
"title": "test_title_OoCh9aitaeyaeth8nom1",
|
"title": "test_title_OoCh9aitaeyaeth8nom1",
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"motion_id": self.motion.id,
|
|
||||||
},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
|
||||||
self.assertFalse(MotionPoll.objects.exists())
|
|
||||||
|
|
||||||
def test_missing_type(self):
|
|
||||||
response = self.client.post(
|
|
||||||
reverse("motionpoll-list"),
|
|
||||||
{
|
|
||||||
"title": "test_title_Ail9Eizohshim0fora6o",
|
|
||||||
"pollmethod": "YNA",
|
"pollmethod": "YNA",
|
||||||
"motion_id": self.motion.id,
|
"motion_id": self.motion.id,
|
||||||
},
|
"onehundred_percent_base": "YN",
|
||||||
format="json",
|
"majority_method": "simple",
|
||||||
)
|
}
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
for key in complete_request_data.keys():
|
||||||
self.assertFalse(MotionPoll.objects.exists())
|
request_data = {
|
||||||
|
_key: value
|
||||||
def test_missing_assignment_id(self):
|
for _key, value in complete_request_data.items()
|
||||||
response = self.client.post(
|
if _key != key
|
||||||
reverse("motionpoll-list"),
|
}
|
||||||
{
|
response = self.client.post(reverse("motionpoll-list"), request_data)
|
||||||
"title": "test_title_eic7ooxaht5mee3quohK",
|
|
||||||
"pollmethod": "YNA",
|
|
||||||
"type": "named",
|
|
||||||
},
|
|
||||||
format="json",
|
|
||||||
)
|
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.exists())
|
self.assertFalse(MotionPoll.objects.exists())
|
||||||
|
|
||||||
@ -165,9 +137,10 @@ class CreateMotionPoll(TestCase):
|
|||||||
"pollmethod": "YNA",
|
"pollmethod": "YNA",
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"motion_id": self.motion.id,
|
"motion_id": self.motion.id,
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [1, 2],
|
"groups_id": [1, 2],
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -182,9 +155,10 @@ class CreateMotionPoll(TestCase):
|
|||||||
"pollmethod": "YNA",
|
"pollmethod": "YNA",
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"motion_id": self.motion.id,
|
"motion_id": self.motion.id,
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [],
|
"groups_id": [],
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -199,7 +173,6 @@ class CreateMotionPoll(TestCase):
|
|||||||
"type": "not_existing",
|
"type": "not_existing",
|
||||||
"motion_id": self.motion.id,
|
"motion_id": self.motion.id,
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.exists())
|
self.assertFalse(MotionPoll.objects.exists())
|
||||||
@ -213,7 +186,6 @@ class CreateMotionPoll(TestCase):
|
|||||||
"type": "named",
|
"type": "named",
|
||||||
"motion_id": self.motion.id,
|
"motion_id": self.motion.id,
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.exists())
|
self.assertFalse(MotionPoll.objects.exists())
|
||||||
@ -236,8 +208,10 @@ class UpdateMotionPoll(TestCase):
|
|||||||
self.poll = MotionPoll.objects.create(
|
self.poll = MotionPoll.objects.create(
|
||||||
motion=self.motion,
|
motion=self.motion,
|
||||||
title="test_title_beeFaihuNae1vej2ai8m",
|
title="test_title_beeFaihuNae1vej2ai8m",
|
||||||
pollmethod="YN",
|
pollmethod="YNA",
|
||||||
type="named",
|
type="named",
|
||||||
|
onehundred_percent_base="YN",
|
||||||
|
majority_method="simple",
|
||||||
)
|
)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
self.poll.groups.add(self.group)
|
self.poll.groups.add(self.group)
|
||||||
@ -266,11 +240,12 @@ class UpdateMotionPoll(TestCase):
|
|||||||
|
|
||||||
def test_patch_pollmethod(self):
|
def test_patch_pollmethod(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("motionpoll-detail", args=[self.poll.pk]), {"pollmethod": "YNA"}
|
reverse("motionpoll-detail", args=[self.poll.pk]), {"pollmethod": "YN"}
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
self.assertEqual(poll.pollmethod, "YNA")
|
self.assertEqual(poll.pollmethod, "YN")
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, "YN")
|
||||||
|
|
||||||
def test_patch_invalid_pollmethod(self):
|
def test_patch_invalid_pollmethod(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
@ -278,7 +253,7 @@ class UpdateMotionPoll(TestCase):
|
|||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
self.assertEqual(poll.pollmethod, "YN")
|
self.assertEqual(poll.pollmethod, "YNA")
|
||||||
|
|
||||||
def test_patch_type(self):
|
def test_patch_type(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
@ -296,11 +271,45 @@ class UpdateMotionPoll(TestCase):
|
|||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
self.assertEqual(poll.type, "named")
|
self.assertEqual(poll.type, "named")
|
||||||
|
|
||||||
def test_patch_groups_to_empty(self):
|
def test_patch_100_percent_base(self):
|
||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("motionpoll-detail", args=[self.poll.pk]),
|
reverse("motionpoll-detail", args=[self.poll.pk]),
|
||||||
{"groups_id": []},
|
{"onehundred_percent_base": "cast"},
|
||||||
format="json",
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, "cast")
|
||||||
|
|
||||||
|
def test_patch_wrong_100_percent_base(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("motionpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"onehundred_percent_base": "invalid"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.onehundred_percent_base, "YN")
|
||||||
|
|
||||||
|
def test_patch_majority_method(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("motionpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"majority_method": "two_thirds"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.majority_method, "two_thirds")
|
||||||
|
|
||||||
|
def test_patch_wrong_majority_method(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("motionpoll-detail", args=[self.poll.pk]),
|
||||||
|
{"majority_method": "invalid majority method"},
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.majority_method, "simple")
|
||||||
|
|
||||||
|
def test_patch_groups_to_empty(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("motionpoll-detail", args=[self.poll.pk]), {"groups_id": []},
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -311,7 +320,6 @@ class UpdateMotionPoll(TestCase):
|
|||||||
response = self.client.patch(
|
response = self.client.patch(
|
||||||
reverse("motionpoll-detail", args=[self.poll.pk]),
|
reverse("motionpoll-detail", args=[self.poll.pk]),
|
||||||
{"groups_id": [group2.id]},
|
{"groups_id": [group2.id]},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -378,7 +386,6 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
"votesvalid": "4.64",
|
"votesvalid": "4.64",
|
||||||
"votesinvalid": "-2",
|
"votesinvalid": "-2",
|
||||||
},
|
},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -403,9 +410,7 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
def test_vote_missing_data(self):
|
def test_vote_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]),
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"Y": "4", "N": "22.6"},
|
||||||
{"Y": "4", "N": "22.6"},
|
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -413,7 +418,7 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
def test_vote_wrong_data_format(self):
|
def test_vote_wrong_data_format(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5], format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -423,7 +428,6 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]),
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
{"Y": "some string", "N": "-2", "A": "3"},
|
{"Y": "some string", "N": "-2", "A": "3"},
|
||||||
format="json",
|
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -477,7 +481,7 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -498,11 +502,11 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -524,7 +528,7 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
config["general_system_enable_anonymous"] = True
|
config["general_system_enable_anonymous"] = True
|
||||||
guest_client = APIClient()
|
guest_client = APIClient()
|
||||||
response = guest_client.post(
|
response = guest_client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "Y", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "Y"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -572,7 +576,7 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5], format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -608,6 +612,8 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
pollmethod="YNA",
|
pollmethod="YNA",
|
||||||
type=BasePoll.TYPE_NAMED,
|
type=BasePoll.TYPE_NAMED,
|
||||||
state=MotionPoll.STATE_STARTED,
|
state=MotionPoll.STATE_STARTED,
|
||||||
|
onehundred_percent_base="YN",
|
||||||
|
majority_method="simple",
|
||||||
)
|
)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
self.poll.groups.add(self.delegate_group)
|
self.poll.groups.add(self.delegate_group)
|
||||||
@ -631,6 +637,8 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
"state": 2,
|
"state": 2,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"title": "test_title_tho8PhiePh8upaex6phi",
|
"title": "test_title_tho8PhiePh8upaex6phi",
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
@ -685,6 +693,8 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
"state": 2,
|
"state": 2,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"title": "test_title_tho8PhiePh8upaex6phi",
|
"title": "test_title_tho8PhiePh8upaex6phi",
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"options": [{"id": 1}],
|
"options": [{"id": 1}],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
@ -726,6 +736,8 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
pollmethod="YNA",
|
pollmethod="YNA",
|
||||||
type=BasePoll.TYPE_PSEUDOANONYMOUS,
|
type=BasePoll.TYPE_PSEUDOANONYMOUS,
|
||||||
state=MotionPoll.STATE_STARTED,
|
state=MotionPoll.STATE_STARTED,
|
||||||
|
onehundred_percent_base="YN",
|
||||||
|
majority_method="simple",
|
||||||
)
|
)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
self.poll.groups.add(self.delegate_group)
|
self.poll.groups.add(self.delegate_group)
|
||||||
@ -749,6 +761,8 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
"state": 2,
|
"state": 2,
|
||||||
"type": "pseudoanonymous",
|
"type": "pseudoanonymous",
|
||||||
"title": "test_title_cahP1umooteehah2jeey",
|
"title": "test_title_cahP1umooteehah2jeey",
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
@ -789,6 +803,8 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
"state": 2,
|
"state": 2,
|
||||||
"type": "pseudoanonymous",
|
"type": "pseudoanonymous",
|
||||||
"title": "test_title_cahP1umooteehah2jeey",
|
"title": "test_title_cahP1umooteehah2jeey",
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"options": [{"id": 1}],
|
"options": [{"id": 1}],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
@ -847,7 +863,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -869,11 +885,11 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
option = MotionPoll.objects.get().options.get()
|
option = MotionPoll.objects.get().options.get()
|
||||||
@ -889,7 +905,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
config["general_system_enable_anonymous"] = True
|
config["general_system_enable_anonymous"] = True
|
||||||
guest_client = APIClient()
|
guest_client = APIClient()
|
||||||
response = guest_client.post(
|
response = guest_client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "Y", format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), "Y"
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -928,7 +944,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5], format="json"
|
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -976,6 +992,8 @@ class PublishMotionPoll(TestCase):
|
|||||||
title="test_title_Nufae0iew7Iorox2thoo",
|
title="test_title_Nufae0iew7Iorox2thoo",
|
||||||
pollmethod="YNA",
|
pollmethod="YNA",
|
||||||
type=BasePoll.TYPE_PSEUDOANONYMOUS,
|
type=BasePoll.TYPE_PSEUDOANONYMOUS,
|
||||||
|
onehundred_percent_base="YN",
|
||||||
|
majority_method="simple",
|
||||||
)
|
)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
option = self.poll.options.get()
|
option = self.poll.options.get()
|
||||||
@ -1003,6 +1021,8 @@ class PublishMotionPoll(TestCase):
|
|||||||
"state": 4,
|
"state": 4,
|
||||||
"type": "pseudoanonymous",
|
"type": "pseudoanonymous",
|
||||||
"title": "test_title_Nufae0iew7Iorox2thoo",
|
"title": "test_title_Nufae0iew7Iorox2thoo",
|
||||||
|
"onehundred_percent_base": "YN",
|
||||||
|
"majority_method": "simple",
|
||||||
"groups_id": [],
|
"groups_id": [],
|
||||||
"votesvalid": "0.000000",
|
"votesvalid": "0.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
|
Loading…
Reference in New Issue
Block a user