@@ -26,13 +33,18 @@
-
-
+
+
- {{ vote.amount | pollPercentBase: poll:'assignment' }}
+ {{ getVoteAmount(vote, row) | pollPercentBase: poll:'assignment' }}
+
+
+ {{ getVoteAmount(vote, row) | parsePollNumber }}
+
+
+ {{ vote.amount | parsePollNumber }}
- {{ vote.amount | parsePollNumber }}
|
diff --git a/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts b/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts
index 03fe1e3c5..d16d898ee 100644
--- a/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts
+++ b/client/src/app/shared/components/assignment-poll-detail-content/assignment-poll-detail-content.component.ts
@@ -20,8 +20,20 @@ export class AssignmentPollDetailContentComponent {
return this.poll.pollmethod;
}
+ public get showYHeader(): boolean {
+ return this.isMethodY || this.isMethodYN || this.isMethodYNA;
+ }
+
+ public get showNHeader(): boolean {
+ return this.isMethodN || this.isMethodYN || this.isMethodYNA;
+ }
+
public get isMethodY(): boolean {
- return this.method === AssignmentPollMethod.Votes;
+ return this.method === AssignmentPollMethod.Y;
+ }
+
+ public get isMethodN(): boolean {
+ return this.method === AssignmentPollMethod.N;
}
public get isMethodYN(): boolean {
@@ -37,19 +49,44 @@ export class AssignmentPollDetailContentComponent {
}
public getVoteClass(votingResult: VotingResult): string {
- return votingResult.vote;
+ const votingClass = votingResult.vote;
+ if (this.isMethodN && votingClass === 'no') {
+ return 'yes';
+ } else {
+ return votingClass;
+ }
+ }
+
+ public filterRelevantResults(votingResult: VotingResult[]): VotingResult[] {
+ return votingResult.filter(result => {
+ return result && this.voteFitsMethod(result);
+ });
+ }
+
+ public getVoteAmount(vote: VotingResult, row: PollTableData): number {
+ if (this.isMethodN && row.class === 'user') {
+ if (vote.amount < 0) {
+ return vote.amount;
+ } else {
+ return this.poll.votesvalid - vote.amount;
+ }
+ } else {
+ return vote.amount;
+ }
}
public voteFitsMethod(result: VotingResult): boolean {
- if (this.isMethodY) {
- if (result.vote === 'abstain' || result.vote === 'no') {
- return false;
- }
- } else if (this.isMethodYN) {
- if (result.vote === 'abstain') {
- return false;
- }
+ if (!result.vote) {
+ return true;
+ }
+ if (this.isMethodY) {
+ return result.vote === 'yes';
+ } else if (this.isMethodN) {
+ return result.vote === 'no';
+ } else if (this.isMethodYN) {
+ return result.vote !== 'abstain';
+ } else {
+ return true;
}
- return true;
}
}
diff --git a/client/src/app/shared/models/assignments/assignment-poll.ts b/client/src/app/shared/models/assignments/assignment-poll.ts
index 7fdda5b29..1381a9ebf 100644
--- a/client/src/app/shared/models/assignments/assignment-poll.ts
+++ b/client/src/app/shared/models/assignments/assignment-poll.ts
@@ -3,15 +3,16 @@ import { AssignmentOption } from './assignment-option';
import { BasePoll } from '../poll/base-poll';
export enum AssignmentPollMethod {
+ Y = 'Y',
YN = 'YN',
YNA = 'YNA',
- Votes = 'votes'
+ N = 'N'
}
export enum AssignmentPollPercentBase {
+ Y = 'Y',
YN = 'YN',
YNA = 'YNA',
- Votes = 'votes',
Valid = 'valid',
Cast = 'cast',
Disabled = 'disabled'
@@ -33,22 +34,29 @@ export class AssignmentPoll extends BasePoll<
'votesvalid',
'votesinvalid',
'votescast',
- 'amount_global_abstain',
- 'amount_global_no'
+ 'amount_global_yes',
+ 'amount_global_no',
+ 'amount_global_abstain'
];
public id: number;
public assignment_id: number;
public votes_amount: number;
public allow_multiple_votes_per_candidate: boolean;
+ public global_yes: boolean;
public global_no: boolean;
public global_abstain: boolean;
+ public amount_global_yes: number;
public amount_global_no: number;
public amount_global_abstain: number;
public description: string;
public get isMethodY(): boolean {
- return this.pollmethod === AssignmentPollMethod.Votes;
+ return this.pollmethod === AssignmentPollMethod.Y;
+ }
+
+ public get isMethodN(): boolean {
+ return this.pollmethod === AssignmentPollMethod.N;
}
public get isMethodYN(): boolean {
@@ -64,7 +72,7 @@ export class AssignmentPoll extends BasePoll<
return ['yes', 'no'];
} else if (this.pollmethod === AssignmentPollMethod.YNA) {
return ['yes', 'no', 'abstain'];
- } else if (this.pollmethod === AssignmentPollMethod.Votes) {
+ } else if (this.pollmethod === AssignmentPollMethod.Y) {
return ['yes'];
}
}
diff --git a/client/src/app/shared/pipes/poll-key-verbose.pipe.ts b/client/src/app/shared/pipes/poll-key-verbose.pipe.ts
index 3a4c7cf48..4b276178e 100644
--- a/client/src/app/shared/pipes/poll-key-verbose.pipe.ts
+++ b/client/src/app/shared/pipes/poll-key-verbose.pipe.ts
@@ -9,8 +9,9 @@ const PollValues = {
yes: 'Yes',
no: 'No',
abstain: 'Abstain',
- amount_global_abstain: 'General Abstain',
- amount_global_no: 'General No'
+ amount_global_yes: 'General Yes',
+ amount_global_no: 'General No',
+ amount_global_abstain: 'General Abstain'
};
/**
diff --git a/client/src/app/site/assignments/models/view-assignment-poll.ts b/client/src/app/site/assignments/models/view-assignment-poll.ts
index 48ea0f612..227b7b34c 100644
--- a/client/src/app/site/assignments/models/view-assignment-poll.ts
+++ b/client/src/app/site/assignments/models/view-assignment-poll.ts
@@ -18,15 +18,16 @@ export interface AssignmentPollTitleInformation {
}
export const AssignmentPollMethodVerbose = {
- votes: _('Yes per candidate'),
+ Y: _('Yes per candidate'),
+ N: _('No per candidate'),
YN: _('Yes/No per candidate'),
YNA: _('Yes/No/Abstain per candidate')
};
export const AssignmentPollPercentBaseVerbose = {
+ Y: _('Sum of votes including general No/Abstain'),
YN: _('Yes/No per candidate'),
YNA: _('Yes/No/Abstain per candidate'),
- votes: _('Sum of votes including general No/Abstain'),
valid: _('All valid ballots'),
cast: _('All casted ballots'),
disabled: _('Disabled (no percents)')
diff --git a/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-dialog/assignment-poll-dialog.component.html b/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-dialog/assignment-poll-dialog.component.html
index 84de94055..b6660d18e 100644
--- a/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-dialog/assignment-poll-dialog.component.html
+++ b/client/src/app/site/assignments/modules/assignment-poll/components/assignment-poll-dialog/assignment-poll-dialog.component.html
@@ -45,9 +45,18 @@
+
+
- 1">
+ 1">
{{ 'Available votes' | translate }}:
{{ getVotesAvailable(delegation) }}/{{ poll.votes_amount }}
@@ -39,9 +39,9 @@
@@ -64,7 +64,7 @@
>
{{ action.icon }}
-
+
{{ action.label | translate }}
@@ -74,9 +74,26 @@
-
+
+
+
+
+ {{ PollPropertyVerbose.global_yes | translate }}
+
+
+
- {{ 'General No' | translate }}
+ {{ PollPropertyVerbose.global_no | translate }}
@@ -100,10 +117,10 @@
[ngClass]="getGlobalAbstainClass(delegation)"
[disabled]="isDeliveringVote(delegation)"
>
- trip_origin
+ trip_origin
- {{ 'General Abstain' | translate }}
+ {{ PollPropertyVerbose.global_abstain | translate }}
@@ -149,7 +166,12 @@
diff --git a/client/src/app/site/polls/components/poll-form/poll-form.component.scss b/client/src/app/site/polls/components/poll-form/poll-form.component.scss
index e219f1795..d1b0a636e 100644
--- a/client/src/app/site/polls/components/poll-form/poll-form.component.scss
+++ b/client/src/app/site/polls/components/poll-form/poll-form.component.scss
@@ -9,31 +9,16 @@
}
}
-.poll-preview-meta-info {
- display: flex;
- justify-content: space-between;
- margin: 10px 0;
-
- .short-description {
- flex: 1;
- padding: 0 5px;
- display: inline-block;
- span {
- display: block;
- }
- &-label {
- font-size: 75%;
- }
- }
-}
-
.poll-preview-meta-info-form {
- display: flex;
- align-items: center;
- flex-wrap: wrap;
+ .suboption {
+ margin-left: 1.5em;
+ }
- & > * {
- flex: 1;
- margin: 0 4px;
+ .mat-checkbox {
+ margin-right: 2em;
+ }
+
+ .global-options {
+ margin-bottom: 1em;
}
}
diff --git a/client/src/app/site/polls/components/poll-form/poll-form.component.ts b/client/src/app/site/polls/components/poll-form/poll-form.component.ts
index bbdcc920d..dab08dbc9 100644
--- a/client/src/app/site/polls/components/poll-form/poll-form.component.ts
+++ b/client/src/app/site/polls/components/poll-form/poll-form.component.ts
@@ -174,6 +174,11 @@ export class PollFormComponent
this.contentForm.get('type').disable();
}
+ public showAmountAndGlobal(data: any): boolean {
+ const selectedPollMethod = this.contentForm.get('pollmethod').value;
+ return (selectedPollMethod === 'Y' || selectedPollMethod === 'N') && (!data || !data.state || data.isCreated);
+ }
+
/**
* updates the available percent bases according to the pollmethod
* @param method the currently chosen pollmethod
@@ -182,10 +187,10 @@ export class PollFormComponent
if (method) {
let forbiddenBases = [];
if (method === AssignmentPollMethod.YN) {
- forbiddenBases = [PercentBase.YNA, AssignmentPollPercentBase.Votes];
+ forbiddenBases = [PercentBase.YNA, AssignmentPollPercentBase.Y];
} else if (method === AssignmentPollMethod.YNA) {
- forbiddenBases = [AssignmentPollPercentBase.Votes];
- } else if (method === AssignmentPollMethod.Votes) {
+ forbiddenBases = [AssignmentPollPercentBase.Y];
+ } else if (method === AssignmentPollMethod.Y || AssignmentPollMethod.N) {
forbiddenBases = [PercentBase.YN, PercentBase.YNA];
}
@@ -209,16 +214,16 @@ export class PollFormComponent
): AssignmentPollPercentBase {
if (
method === AssignmentPollMethod.YN &&
- (base === AssignmentPollPercentBase.YNA || base === AssignmentPollPercentBase.Votes)
+ (base === AssignmentPollPercentBase.YNA || base === AssignmentPollPercentBase.Y)
) {
return AssignmentPollPercentBase.YN;
- } else if (method === AssignmentPollMethod.YNA && base === AssignmentPollPercentBase.Votes) {
+ } else if (method === AssignmentPollMethod.YNA && base === AssignmentPollPercentBase.Y) {
return AssignmentPollPercentBase.YNA;
} else if (
- method === AssignmentPollMethod.Votes &&
+ method === AssignmentPollMethod.Y &&
(base === AssignmentPollPercentBase.YN || base === AssignmentPollPercentBase.YNA)
) {
- return AssignmentPollPercentBase.Votes;
+ return AssignmentPollPercentBase.Y;
}
return base;
}
@@ -267,8 +272,10 @@ export class PollFormComponent
: '---'
]);
}
- if (data.pollmethod === 'votes') {
+
+ if (data.pollmethod === 'Y' || data.pollmethod === 'N') {
this.pollValues.push([this.pollService.getVerboseNameForKey('votes_amount'), data.votes_amount]);
+ this.pollValues.push([this.pollService.getVerboseNameForKey('global_yes'), data.global_yes]);
this.pollValues.push([this.pollService.getVerboseNameForKey('global_no'), data.global_no]);
this.pollValues.push([this.pollService.getVerboseNameForKey('global_abstain'), data.global_abstain]);
}
@@ -284,6 +291,7 @@ export class PollFormComponent
majority_method: ['', Validators.required],
votes_amount: [1, [Validators.required, Validators.min(1)]],
groups_id: [],
+ global_yes: [false],
global_no: [false],
global_abstain: [false]
});
diff --git a/client/src/app/site/polls/models/view-base-poll.ts b/client/src/app/site/polls/models/view-base-poll.ts
index 9652bc47b..20a45d2fd 100644
--- a/client/src/app/site/polls/models/view-base-poll.ts
+++ b/client/src/app/site/polls/models/view-base-poll.ts
@@ -44,6 +44,7 @@ export const PollPropertyVerbose = {
state: 'State',
groups: 'Entitled to vote',
votes_amount: 'Amount of votes',
+ global_yes: 'General Yes',
global_no: 'General No',
global_abstain: 'General Abstain'
};
diff --git a/client/src/app/site/polls/services/poll.service.ts b/client/src/app/site/polls/services/poll.service.ts
index ea31691bd..989433b90 100644
--- a/client/src/app/site/polls/services/poll.service.ts
+++ b/client/src/app/site/polls/services/poll.service.ts
@@ -109,6 +109,7 @@ export interface PollData {
votesvalid: number;
votesinvalid: number;
votescast: number;
+ amount_global_yes?: number;
amount_global_no?: number;
amount_global_abstain?: number;
}
@@ -145,6 +146,7 @@ export interface VotingResult {
| 'votesvalid'
| 'votesinvalid'
| 'votescast'
+ | 'amount_global_yes'
| 'amount_global_no'
| 'amount_global_abstain';
amount?: number;
@@ -340,6 +342,9 @@ export abstract class PollService {
case AssignmentPollMethod.YN: {
return ['yes', 'no'];
}
+ case AssignmentPollMethod.N: {
+ return ['no'];
+ }
default: {
return ['yes'];
}
diff --git a/client/src/app/slides/assignments/assignment-poll/assignment-poll-slide-data.ts b/client/src/app/slides/assignments/assignment-poll/assignment-poll-slide-data.ts
index 4e821c495..de4512813 100644
--- a/client/src/app/slides/assignments/assignment-poll/assignment-poll-slide-data.ts
+++ b/client/src/app/slides/assignments/assignment-poll/assignment-poll-slide-data.ts
@@ -25,6 +25,7 @@ export interface AssignmentPollSlideData extends BasePollSlideData {
}[];
// optional for published polls:
+ amount_global_yes?: number;
amount_global_no?: number;
amount_global_abstain?: number;
votesvalid: number;
diff --git a/server/openslides/assignments/access_permissions.py b/server/openslides/assignments/access_permissions.py
index ee64c404c..e707f9e08 100644
--- a/server/openslides/assignments/access_permissions.py
+++ b/server/openslides/assignments/access_permissions.py
@@ -17,7 +17,11 @@ class AssignmentAccessPermissions(BaseAccessPermissions):
class AssignmentPollAccessPermissions(BasePollAccessPermissions):
base_permission = "assignments.can_see"
manage_permission = "assignments.can_manage"
- additional_fields = ["amount_global_no", "amount_global_abstain"]
+ additional_fields = [
+ "amount_global_yes",
+ "amount_global_no",
+ "amount_global_abstain",
+ ]
class AssignmentOptionAccessPermissions(BaseOptionAccessPermissions):
diff --git a/server/openslides/assignments/config_variables.py b/server/openslides/assignments/config_variables.py
index 87985bb7f..da083956b 100644
--- a/server/openslides/assignments/config_variables.py
+++ b/server/openslides/assignments/config_variables.py
@@ -13,7 +13,7 @@ def get_config_variables():
# Voting
yield ConfigVariable(
name="assignment_poll_method",
- default_value=AssignmentPoll.POLLMETHOD_VOTES,
+ default_value=AssignmentPoll.POLLMETHOD_Y,
input_type="choice",
label="Default election method",
choices=tuple(
diff --git a/server/openslides/assignments/migrations/0016_negative_votes.py b/server/openslides/assignments/migrations/0016_negative_votes.py
new file mode 100644
index 000000000..7406b7760
--- /dev/null
+++ b/server/openslides/assignments/migrations/0016_negative_votes.py
@@ -0,0 +1,74 @@
+# Generated by Django 2.2.15 on 2020-11-24 06:44
+
+from decimal import Decimal
+
+import django.core.validators
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("assignments", "0015_assignmentvote_delegated_user"),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name="assignmentpoll",
+ name="db_amount_global_yes",
+ field=models.DecimalField(
+ blank=True,
+ decimal_places=6,
+ default=Decimal("0"),
+ max_digits=15,
+ null=True,
+ validators=[django.core.validators.MinValueValidator(Decimal("-2"))],
+ ),
+ ),
+ migrations.AddField(
+ model_name="assignmentpoll",
+ name="global_yes",
+ field=models.BooleanField(default=True),
+ ),
+ migrations.AlterField(
+ model_name="assignmentpoll",
+ name="pollmethod",
+ field=models.CharField(
+ choices=[
+ ("votes", "Yes per candidate"),
+ ("N", "No per candidate"),
+ ("YN", "Yes/No per candidate"),
+ ("YNA", "Yes/No/Abstain per candidate"),
+ ],
+ max_length=5,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="assignmentpoll",
+ name="onehundred_percent_base",
+ field=models.CharField(
+ choices=[
+ ("YN", "Yes/No per candidate"),
+ ("YNA", "Yes/No/Abstain per candidate"),
+ ("Y", "Sum of votes including general No/Abstain"),
+ ("valid", "All valid ballots"),
+ ("cast", "All casted ballots"),
+ ("disabled", "Disabled (no percents)"),
+ ],
+ max_length=8,
+ ),
+ ),
+ migrations.AlterField(
+ model_name="assignmentpoll",
+ name="pollmethod",
+ field=models.CharField(
+ choices=[
+ ("Y", "Yes per candidate"),
+ ("N", "No per candidate"),
+ ("YN", "Yes/No per candidate"),
+ ("YNA", "Yes/No/Abstain per candidate"),
+ ],
+ max_length=5,
+ ),
+ ),
+ ]
diff --git a/server/openslides/assignments/migrations/0017_vote_to_y.py b/server/openslides/assignments/migrations/0017_vote_to_y.py
new file mode 100644
index 000000000..e11ae0177
--- /dev/null
+++ b/server/openslides/assignments/migrations/0017_vote_to_y.py
@@ -0,0 +1,30 @@
+# Generated by Finn Stutzenstein on 2020-11-24 06:44
+
+from django.db import migrations
+
+
+def votes_to_y(apps, schema_editor):
+ AssignmentPoll = apps.get_model("assignments", "AssignmentPoll")
+ for poll in AssignmentPoll.objects.all():
+ changed = False
+ if poll.pollmethod == "votes":
+ poll.pollmethod = "Y"
+ changed = True
+
+ if poll.onehundred_percent_base == "votes":
+ poll.onehundred_percent_base = "Y"
+ changed = True
+
+ if changed:
+ poll.save(skip_autoupdate=True)
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ("assignments", "0016_negative_votes"),
+ ]
+
+ operations = [
+ migrations.RunPython(votes_to_y),
+ ]
diff --git a/server/openslides/assignments/models.py b/server/openslides/assignments/models.py
index 8c8202b68..b8fa9e91f 100644
--- a/server/openslides/assignments/models.py
+++ b/server/openslides/assignments/models.py
@@ -319,24 +319,26 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
POLLMETHOD_YN = "YN"
POLLMETHOD_YNA = "YNA"
- POLLMETHOD_VOTES = "votes"
+ POLLMETHOD_Y = "Y"
+ POLLMETHOD_N = "N"
POLLMETHODS = (
- (POLLMETHOD_VOTES, "Yes per candidate"),
+ (POLLMETHOD_Y, "Yes per candidate"),
+ (POLLMETHOD_N, "No per candidate"),
(POLLMETHOD_YN, "Yes/No per candidate"),
(POLLMETHOD_YNA, "Yes/No/Abstain per candidate"),
)
pollmethod = models.CharField(max_length=5, choices=POLLMETHODS)
+ PERCENT_BASE_Y = "Y"
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 including general No/Abstain"),
+ (PERCENT_BASE_Y, "Sum of votes including general No/Abstain"),
(PERCENT_BASE_VALID, "All valid ballots"),
(PERCENT_BASE_CAST, "All casted ballots"),
(PERCENT_BASE_DISABLED, "Disabled (no percents)"),
@@ -345,8 +347,8 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
max_length=8, blank=False, null=False, choices=PERCENT_BASES
)
- global_abstain = models.BooleanField(default=True)
- db_amount_global_abstain = models.DecimalField(
+ global_yes = models.BooleanField(default=True)
+ db_amount_global_yes = models.DecimalField(
null=True,
blank=True,
default=Decimal("0"),
@@ -354,6 +356,7 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
max_digits=15,
decimal_places=6,
)
+
global_no = models.BooleanField(default=True)
db_amount_global_no = models.DecimalField(
null=True,
@@ -364,6 +367,16 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
decimal_places=6,
)
+ global_abstain = models.BooleanField(default=True)
+ db_amount_global_abstain = models.DecimalField(
+ null=True,
+ blank=True,
+ default=Decimal("0"),
+ validators=[MinValueValidator(Decimal("-2"))],
+ max_digits=15,
+ decimal_places=6,
+ )
+
votes_amount = models.IntegerField(default=1, validators=[MinValueValidator(1)])
""" For "votes" mode: The amount of votes a voter can give. """
@@ -372,12 +385,55 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
class Meta:
default_permissions = ()
+ def get_amount_global_yes(self):
+ if not self.global_yes:
+ return None
+ elif self.type == self.TYPE_ANALOG:
+ return self.db_amount_global_yes
+ elif self.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ ):
+ return sum(option.yes for option in self.options.all())
+ else:
+ return None
+
+ def set_amount_global_yes(self, value):
+ if self.type != self.TYPE_ANALOG:
+ raise ValueError("Do not set amount_global_yes for non analog polls")
+ self.db_amount_global_yes = value
+
+ amount_global_yes = property(get_amount_global_yes, set_amount_global_yes)
+
+ def get_amount_global_no(self):
+ if not self.global_no:
+ return None
+ elif self.type == self.TYPE_ANALOG:
+ return self.db_amount_global_no
+ elif self.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ ):
+ return sum(option.no for option in self.options.all())
+ else:
+ return None
+
+ def set_amount_global_no(self, value):
+ if self.type != self.TYPE_ANALOG:
+ raise ValueError("Do not set amount_global_no for non analog polls")
+ self.db_amount_global_no = value
+
+ amount_global_no = property(get_amount_global_no, set_amount_global_no)
+
def get_amount_global_abstain(self):
if not self.global_abstain:
return None
elif self.type == self.TYPE_ANALOG:
return self.db_amount_global_abstain
- elif self.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
+ elif self.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ ):
return sum(option.abstain for option in self.options.all())
else:
return None
@@ -391,23 +447,6 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
get_amount_global_abstain, set_amount_global_abstain
)
- def get_amount_global_no(self):
- if not self.global_no:
- return None
- elif self.type == self.TYPE_ANALOG:
- return self.db_amount_global_no
- elif self.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
- return sum(option.no for option in self.options.all())
- else:
- return None
-
- def set_amount_global_no(self, value):
- if self.type != self.TYPE_ANALOG:
- raise ValueError("Do not set amount_global_no for non analog polls")
- self.db_amount_global_no = value
-
- amount_global_no = property(get_amount_global_no, set_amount_global_no)
-
def create_options(self, skip_autoupdate=False):
related_users = AssignmentRelatedUser.objects.filter(
assignment__id=self.assignment.id
@@ -435,6 +474,7 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
inform_changed_data(self.assignment.list_of_speakers)
def reset(self):
- self.db_amount_global_abstain = Decimal(0)
+ self.db_amount_global_yes = Decimal(0)
self.db_amount_global_no = Decimal(0)
+ self.db_amount_global_abstain = Decimal(0)
super().reset()
diff --git a/server/openslides/assignments/projector.py b/server/openslides/assignments/projector.py
index 97c0be82a..1cd07523d 100644
--- a/server/openslides/assignments/projector.py
+++ b/server/openslides/assignments/projector.py
@@ -86,6 +86,9 @@ async def assignment_poll_slide(
poll_data["options"].append(option_data)
if poll["state"] == AssignmentPoll.STATE_PUBLISHED:
+ poll_data["amount_global_yes"] = (
+ float(poll["amount_global_yes"]) if poll["amount_global_yes"] else None
+ )
poll_data["amount_global_no"] = (
float(poll["amount_global_no"]) if poll["amount_global_no"] else None
)
diff --git a/server/openslides/assignments/serializers.py b/server/openslides/assignments/serializers.py
index 47cae0aed..2ca605e5c 100644
--- a/server/openslides/assignments/serializers.py
+++ b/server/openslides/assignments/serializers.py
@@ -77,6 +77,9 @@ class AssignmentPollSerializer(BasePollSerializer):
Serializes all polls.
"""
+ amount_global_yes = DecimalField(
+ max_digits=15, decimal_places=6, min_value=-2, read_only=True
+ )
amount_global_no = DecimalField(
max_digits=15, decimal_places=6, min_value=-2, read_only=True
)
@@ -92,6 +95,8 @@ class AssignmentPollSerializer(BasePollSerializer):
"pollmethod",
"votes_amount",
"allow_multiple_votes_per_candidate",
+ "global_yes",
+ "amount_global_yes",
"global_no",
"amount_global_no",
"global_abstain",
@@ -111,13 +116,13 @@ class AssignmentPollSerializer(BasePollSerializer):
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_Y,
AssignmentPoll.PERCENT_BASE_YNA,
):
return AssignmentPoll.PERCENT_BASE_YN
if (
pollmethod == AssignmentPoll.POLLMETHOD_YNA
- and onehundred_percent_base == AssignmentPoll.PERCENT_BASE_VOTES
+ and onehundred_percent_base == AssignmentPoll.PERCENT_BASE_Y
):
if old_100_percent_base is None:
return AssignmentPoll.PERCENT_BASE_YNA
@@ -129,12 +134,11 @@ class AssignmentPollSerializer(BasePollSerializer):
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)
+ if pollmethod == AssignmentPoll.POLLMETHOD_Y and onehundred_percent_base in (
+ AssignmentPoll.PERCENT_BASE_YN,
+ AssignmentPoll.PERCENT_BASE_YNA,
):
- return AssignmentPoll.PERCENT_BASE_VOTES
+ return AssignmentPoll.PERCENT_BASE_Y
return None
diff --git a/server/openslides/assignments/views.py b/server/openslides/assignments/views.py
index 3ed5c6bdd..ef072754d 100644
--- a/server/openslides/assignments/views.py
+++ b/server/openslides/assignments/views.py
@@ -268,21 +268,32 @@ class AssignmentPollViewSet(BasePollViewSet):
super().perform_create(serializer)
poll = AssignmentPoll.objects.get(pk=serializer.data["id"])
- poll.db_amount_global_abstain = Decimal(0)
+ poll.db_amount_global_yes = Decimal(0)
poll.db_amount_global_no = Decimal(0)
+ poll.db_amount_global_abstain = Decimal(0)
poll.save()
def handle_analog_vote(self, data, poll):
for field in ["votesvalid", "votesinvalid", "votescast"]:
setattr(poll, field, data[field])
- global_no_enabled = (
- poll.global_no and poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES
+ global_yes_enabled = poll.global_yes and poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ )
+ if global_yes_enabled:
+ poll.amount_global_yes = data.get("amount_global_yes", Decimal(0))
+
+ global_no_enabled = poll.global_no and poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
)
if global_no_enabled:
poll.amount_global_no = data.get("amount_global_no", Decimal(0))
- global_abstain_enabled = (
- poll.global_abstain and poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES
+
+ global_abstain_enabled = poll.global_abstain and poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
)
if global_abstain_enabled:
poll.amount_global_abstain = data.get("amount_global_abstain", Decimal(0))
@@ -293,28 +304,47 @@ class AssignmentPollViewSet(BasePollViewSet):
with transaction.atomic():
for option_id, vote in options_data.items():
option = options.get(pk=int(option_id))
- vote_obj, _ = AssignmentVote.objects.get_or_create(
- option=option, value="Y"
- )
- vote_obj.weight = vote["Y"]
- vote_obj.save()
- if poll.pollmethod in (
- AssignmentPoll.POLLMETHOD_YN,
- AssignmentPoll.POLLMETHOD_YNA,
- ):
+ if poll.pollmethod == AssignmentPoll.POLLMETHOD_N:
vote_obj, _ = AssignmentVote.objects.get_or_create(
option=option, value="N"
)
vote_obj.weight = vote["N"]
vote_obj.save()
- if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA:
+ elif poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_YN,
+ AssignmentPoll.POLLMETHOD_YNA,
+ ):
+ # All three methods have a Y
vote_obj, _ = AssignmentVote.objects.get_or_create(
- option=option, value="A"
+ option=option, value="Y"
)
- vote_obj.weight = vote["A"]
+ vote_obj.weight = vote["Y"]
vote_obj.save()
+
+ if poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_YN,
+ AssignmentPoll.POLLMETHOD_YNA,
+ ):
+ vote_obj, _ = AssignmentVote.objects.get_or_create(
+ option=option, value="N"
+ )
+ vote_obj.weight = vote["N"]
+ vote_obj.save()
+
+ if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA:
+ vote_obj, _ = AssignmentVote.objects.get_or_create(
+ option=option, value="A"
+ )
+ vote_obj.weight = vote["A"]
+ vote_obj.save()
+
+ else:
+ raise NotImplementedError(
+ f"handle_analog_vote not implemented for {poll.pollmethod}"
+ )
inform_changed_data(option)
poll.save()
@@ -326,22 +356,27 @@ class AssignmentPollViewSet(BasePollViewSet):
{
"options": {: {"Y": , ["N": ], ["A": ] }},
["votesvalid": ], ["votesinvalid": ], ["votescast": ],
- ["amount_global_no": ], ["amount_global_abstain": ]
+ ["amount_global_yes": ],
+ ["amount_global_no": ],
+ ["amount_global_abstain": ]
}
All amounts are decimals as strings
required fields per pollmethod:
- votes: Y
- YN: YN
- YNA: YNA
+ - N: N
named|pseudoanonymous:
votes:
- {: } | 'N' | 'A'
+ {: } | 'Y' | 'N' | 'A'
- Exactly one of the three options must be given
+ - 'Y' is only valid if poll.global_yes==True
- 'N' is only valid if poll.global_no==True
- 'A' is only valid if poll.global_abstain==True
- amounts must be integer numbers >= 0.
- ids should be integers of valid option ids for this poll
- amounts must be 0 or 1, if poll.allow_multiple_votes_per_candidate is False
+ - if an option is not given, 0 is assumed
- The sum of all amounts must be grater than 0 and <= poll.votes_amount
YN/YNA:
@@ -361,39 +396,56 @@ class AssignmentPollViewSet(BasePollViewSet):
raise ValidationError({"detail": "Keys must be int"})
if not isinstance(value, dict):
raise ValidationError({"detail": "A dict per option is required"})
- value["Y"] = self.parse_vote_value(value, "Y")
- if poll.pollmethod in (
- AssignmentPoll.POLLMETHOD_YN,
- AssignmentPoll.POLLMETHOD_YNA,
- ):
+ if poll.pollmethod == AssignmentPoll.POLLMETHOD_N:
value["N"] = self.parse_vote_value(value, "N")
- if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA:
- value["A"] = self.parse_vote_value(value, "A")
+ else:
+ value["Y"] = self.parse_vote_value(value, "Y")
+ if poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_YN,
+ AssignmentPoll.POLLMETHOD_YNA,
+ ):
+ value["N"] = self.parse_vote_value(value, "N")
+ if poll.pollmethod == AssignmentPoll.POLLMETHOD_YNA:
+ value["A"] = self.parse_vote_value(value, "A")
for field in ["votesvalid", "votesinvalid", "votescast"]:
data[field] = self.parse_vote_value(data, field)
- global_no_enabled = (
- poll.global_no and poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES
+ global_yes_enabled = poll.global_yes and poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
)
- global_abstain_enabled = (
- poll.global_abstain
- and poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES
- )
- if "amount_global_abstain" in data and global_abstain_enabled:
- data["amount_global_abstain"] = self.parse_vote_value(
- data, "amount_global_abstain"
+ if "amount_global_yes" in data and global_yes_enabled:
+ data["amount_global_yes"] = self.parse_vote_value(
+ data, "amount_global_yes"
)
+
+ global_no_enabled = poll.global_no and poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ )
if "amount_global_no" in data and global_no_enabled:
data["amount_global_no"] = self.parse_vote_value(
data, "amount_global_no"
)
- else:
+ global_abstain_enabled = poll.global_abstain and poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ )
+ if "amount_global_abstain" in data and global_abstain_enabled:
+ data["amount_global_abstain"] = self.parse_vote_value(
+ data, "amount_global_abstain"
+ )
+
+ else: # non-analog polls
if isinstance(data, dict) and len(data) == 0:
raise ValidationError({"details": "Empty ballots are not allowed"})
available_options = poll.get_options()
- if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
+ if poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ ):
if isinstance(data, dict):
amount_sum = 0
for option_id, amount in data.items():
@@ -426,10 +478,13 @@ class AssignmentPollViewSet(BasePollViewSet):
"args": [poll.votes_amount],
}
)
+ # return, if there is a global vote, because we dont have to check option presence
+ elif data == "Y" and poll.global_yes:
+ return
elif data == "N" and poll.global_no:
- return # return because we dont have to check option presence
+ return
elif data == "A" and poll.global_abstain:
- return # return because we dont have to check option presence
+ return
else:
raise ValidationError({"detail": "invalid data."})
@@ -479,12 +534,15 @@ class AssignmentPollViewSet(BasePollViewSet):
weight = Decimal(amount)
if config["users_activate_vote_weight"]:
weight *= vote_weight
+ value = "Y" # POLLMETHOD_Y
+ if poll.pollmethod == AssignmentPoll.POLLMETHOD_N:
+ value = "N"
vote = AssignmentVote.objects.create(
option=option,
user=vote_user,
delegated_user=request_user,
weight=weight,
- value="Y",
+ value=value,
)
inform_changed_data(vote, no_delete_on_restriction=True)
else: # global_no or global_abstain
@@ -529,7 +587,10 @@ class AssignmentPollViewSet(BasePollViewSet):
VotedModel.objects.create(assignmentpoll=poll, user=user)
def handle_named_vote(self, data, poll, vote_user, request_user):
- if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
+ if poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ ):
self.create_votes_type_votes(
data, poll, vote_user.vote_weight, vote_user, request_user
)
@@ -540,16 +601,22 @@ class AssignmentPollViewSet(BasePollViewSet):
self.create_votes_types_yn_yna(
data, poll, vote_user.vote_weight, vote_user, request_user
)
+ else:
+ raise NotImplementedError(f"The method {poll.pollmethod} is not supported!")
def handle_pseudoanonymous_vote(self, data, poll, user):
- if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
+ if poll.pollmethod in (
+ AssignmentPoll.POLLMETHOD_Y,
+ AssignmentPoll.POLLMETHOD_N,
+ ):
self.create_votes_type_votes(data, poll, user.vote_weight, None, None)
-
elif poll.pollmethod in (
AssignmentPoll.POLLMETHOD_YN,
AssignmentPoll.POLLMETHOD_YNA,
):
self.create_votes_types_yn_yna(data, poll, user.vote_weight, None, None)
+ else:
+ raise NotImplementedError(f"The method {poll.pollmethod} is not supported!")
def convert_option_data(self, poll, data):
poll_options = poll.get_options()
diff --git a/server/tests/integration/assignments/test_polls.py b/server/tests/integration/assignments/test_polls.py
index 14519daff..2c642f2b9 100644
--- a/server/tests/integration/assignments/test_polls.py
+++ b/server/tests/integration/assignments/test_polls.py
@@ -129,8 +129,10 @@ class CreateAssignmentPoll(TestCase):
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_YNA)
self.assertEqual(poll.type, "named")
# Check defaults
+ self.assertTrue(poll.global_yes)
self.assertTrue(poll.global_no)
self.assertTrue(poll.global_abstain)
+ self.assertEqual(poll.amount_global_yes, None)
self.assertEqual(poll.amount_global_no, None)
self.assertEqual(poll.amount_global_abstain, None)
self.assertFalse(poll.allow_multiple_votes_per_candidate)
@@ -151,6 +153,7 @@ class CreateAssignmentPoll(TestCase):
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
"majority_method": AssignmentPoll.MAJORITY_THREE_QUARTERS,
+ "global_yes": False,
"global_no": False,
"global_abstain": False,
"allow_multiple_votes_per_candidate": True,
@@ -164,6 +167,7 @@ class CreateAssignmentPoll(TestCase):
self.assertEqual(poll.title, "test_title_ahThai4pae1pi4xoogoo")
self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_YN)
self.assertEqual(poll.type, "pseudoanonymous")
+ self.assertFalse(poll.global_yes)
self.assertFalse(poll.global_no)
self.assertFalse(poll.global_abstain)
self.assertTrue(poll.allow_multiple_votes_per_candidate)
@@ -327,7 +331,7 @@ class CreateAssignmentPoll(TestCase):
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
"type": "named",
"assignment_id": self.assignment.id,
- "onehundred_percent_base": AssignmentPoll.PERCENT_BASE_VOTES,
+ "onehundred_percent_base": AssignmentPoll.PERCENT_BASE_Y,
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
},
)
@@ -343,7 +347,7 @@ class CreateAssignmentPoll(TestCase):
"pollmethod": AssignmentPoll.POLLMETHOD_YN,
"type": "named",
"assignment_id": self.assignment.id,
- "onehundred_percent_base": AssignmentPoll.PERCENT_BASE_VOTES,
+ "onehundred_percent_base": AssignmentPoll.PERCENT_BASE_Y,
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
},
)
@@ -356,7 +360,7 @@ class CreateAssignmentPoll(TestCase):
reverse("assignmentpoll-list"),
{
"title": "test_title_Thoo2eiphohhi1eeXoow",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
"type": "named",
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
@@ -365,16 +369,14 @@ class CreateAssignmentPoll(TestCase):
)
self.assertHttpStatusVerbose(response, status.HTTP_201_CREATED)
poll = AssignmentPoll.objects.get()
- self.assertEqual(
- poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_VOTES
- )
+ self.assertEqual(poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_Y)
def test_create_with_votes(self):
response = self.client.post(
reverse("assignmentpoll-list"),
{
"title": "test_title_dKbv5tV47IzY1oGHXdSz",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
"type": AssignmentPoll.TYPE_ANALOG,
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
@@ -400,7 +402,7 @@ class CreateAssignmentPoll(TestCase):
reverse("assignmentpoll-list"),
{
"title": "test_title_dKbv5tV47IzY1oGHXdSz",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
"type": AssignmentPoll.TYPE_ANALOG,
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
@@ -408,7 +410,7 @@ class CreateAssignmentPoll(TestCase):
"votes": {
"options": {"2": {"Y": 1}},
"votesvalid": "-2",
- "votesinvalid": "-2",
+ "votesinvalid": "11",
"votescast": "-2",
},
},
@@ -418,12 +420,12 @@ class CreateAssignmentPoll(TestCase):
self.assertEqual(poll.state, AssignmentPoll.STATE_FINISHED)
self.assertTrue(AssignmentVote.objects.exists())
- def test_create_with_votes_publish_immediately(self):
+ def test_create_with_votes_publish_immediately_method_y(self):
response = self.client.post(
reverse("assignmentpoll-list"),
{
"title": "test_title_dKbv5tV47IzY1oGHXdSz",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
"type": AssignmentPoll.TYPE_ANALOG,
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
@@ -442,12 +444,46 @@ class CreateAssignmentPoll(TestCase):
self.assertEqual(poll.state, AssignmentPoll.STATE_PUBLISHED)
self.assertTrue(AssignmentVote.objects.exists())
+ def test_create_with_votes_publish_immediately_method_n(self):
+ response = self.client.post(
+ reverse("assignmentpoll-list"),
+ {
+ "title": "test_title_greoGKPO3FeBAfwpefl3",
+ "pollmethod": AssignmentPoll.POLLMETHOD_N,
+ "type": AssignmentPoll.TYPE_ANALOG,
+ "assignment_id": self.assignment.id,
+ "onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
+ "majority_method": AssignmentPoll.MAJORITY_SIMPLE,
+ "votes": {
+ "options": {"1": {"N": 1}},
+ "votesvalid": "-2",
+ "votesinvalid": "-2",
+ "votescast": "-2",
+ "amount_global_yes": 1,
+ "amount_global_no": 2,
+ "amount_global_abstain": 3,
+ },
+ "publish_immediately": "1",
+ },
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_201_CREATED)
+ poll = AssignmentPoll.objects.get()
+ self.assertEqual(poll.state, AssignmentPoll.STATE_PUBLISHED)
+ self.assertTrue(AssignmentVote.objects.exists())
+ self.assertEquals(poll.amount_global_yes, Decimal("1"))
+ self.assertEquals(poll.amount_global_no, Decimal("2"))
+ self.assertEquals(poll.amount_global_abstain, Decimal("3"))
+ option = poll.options.get(pk=1)
+ self.assertEqual(option.yes, Decimal("0"))
+ self.assertEqual(option.no, Decimal("1"))
+ self.assertEqual(option.abstain, Decimal("0"))
+
def test_create_with_invalid_votes(self):
response = self.client.post(
reverse("assignmentpoll-list"),
{
"title": "test_title_dKbv5tV47IzY1oGHXdSz",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
"type": AssignmentPoll.TYPE_ANALOG,
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
@@ -468,7 +504,7 @@ class CreateAssignmentPoll(TestCase):
reverse("assignmentpoll-list"),
{
"title": "test_title_dKbv5tV47IzY1oGHXdSz",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
"type": AssignmentPoll.TYPE_NAMED,
"assignment_id": self.assignment.id,
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
@@ -500,9 +536,9 @@ class UpdateAssignmentPoll(TestCase):
self.poll = AssignmentPoll.objects.create(
assignment=self.assignment,
title="test_title_beeFaihuNae1vej2ai8m",
- pollmethod=AssignmentPoll.POLLMETHOD_VOTES,
+ pollmethod=AssignmentPoll.POLLMETHOD_Y,
type=BasePoll.TYPE_NAMED,
- onehundred_percent_base=AssignmentPoll.PERCENT_BASE_VOTES,
+ onehundred_percent_base=AssignmentPoll.PERCENT_BASE_Y,
majority_method=AssignmentPoll.MAJORITY_SIMPLE,
)
self.poll.create_options()
@@ -545,7 +581,7 @@ class UpdateAssignmentPoll(TestCase):
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
poll = AssignmentPoll.objects.get()
- self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_VOTES)
+ self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_Y)
def test_patch_type(self):
response = self.client.patch(
@@ -631,9 +667,7 @@ class UpdateAssignmentPoll(TestCase):
)
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
poll = AssignmentPoll.objects.get()
- self.assertEqual(
- poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_VOTES
- )
+ self.assertEqual(poll.onehundred_percent_base, AssignmentPoll.PERCENT_BASE_Y)
def test_patch_majority_method(self):
response = self.client.patch(
@@ -658,7 +692,8 @@ class UpdateAssignmentPoll(TestCase):
reverse("assignmentpoll-detail", args=[self.poll.pk]),
{
"title": "test_title_ees6Tho8ahheen4cieja",
- "pollmethod": AssignmentPoll.POLLMETHOD_VOTES,
+ "pollmethod": AssignmentPoll.POLLMETHOD_Y,
+ "global_yes": True,
"global_no": True,
"global_abstain": False,
"allow_multiple_votes_per_candidate": True,
@@ -668,9 +703,11 @@ class UpdateAssignmentPoll(TestCase):
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
poll = AssignmentPoll.objects.get()
self.assertEqual(poll.title, "test_title_ees6Tho8ahheen4cieja")
- self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_VOTES)
+ self.assertEqual(poll.pollmethod, AssignmentPoll.POLLMETHOD_Y)
+ self.assertTrue(poll.global_yes)
self.assertTrue(poll.global_no)
self.assertFalse(poll.global_abstain)
+ self.assertEqual(poll.amount_global_yes, Decimal("0"))
self.assertEqual(poll.amount_global_no, Decimal("0"))
self.assertEqual(poll.amount_global_abstain, None)
self.assertTrue(poll.allow_multiple_votes_per_candidate)
@@ -1220,12 +1257,12 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
self.assertFalse(AssignmentVote.objects.exists())
-class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
+class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
def create_poll(self):
return AssignmentPoll.objects.create(
assignment=self.assignment,
title="test_title_Zrvh146QAdq7t6iSDwZk",
- pollmethod=AssignmentPoll.POLLMETHOD_VOTES,
+ pollmethod=AssignmentPoll.POLLMETHOD_Y,
type=BasePoll.TYPE_NAMED,
)
@@ -1296,6 +1333,34 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.assertEqual(option2.no, Decimal("0"))
self.assertEqual(option2.abstain, Decimal("0"))
+ def test_global_yes(self):
+ self.poll.votes_amount = 2
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "Y"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ option = poll.options.get(pk=1)
+ self.assertEqual(option.yes, Decimal("1"))
+ self.assertEqual(option.no, Decimal("0"))
+ self.assertEqual(option.abstain, Decimal("0"))
+ self.assertEqual(poll.amount_global_yes, Decimal("1"))
+ self.assertEqual(poll.amount_global_no, Decimal("0"))
+ self.assertEqual(poll.amount_global_abstain, Decimal("0"))
+
+ def test_global_yes_forbidden(self):
+ self.poll.global_yes = False
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "Y"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+ self.assertEqual(AssignmentPoll.objects.get().amount_global_yes, None)
+
def test_global_no(self):
self.poll.votes_amount = 2
self.poll.save()
@@ -1309,6 +1374,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.assertEqual(option.yes, Decimal("0"))
self.assertEqual(option.no, Decimal("1"))
self.assertEqual(option.abstain, Decimal("0"))
+ self.assertEqual(poll.amount_global_yes, Decimal("0"))
self.assertEqual(poll.amount_global_no, Decimal("1"))
self.assertEqual(poll.amount_global_abstain, Decimal("0"))
@@ -1336,6 +1402,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.assertEqual(option.yes, Decimal("0"))
self.assertEqual(option.no, Decimal("0"))
self.assertEqual(option.abstain, Decimal("1"))
+ self.assertEqual(poll.amount_global_yes, Decimal("0"))
self.assertEqual(poll.amount_global_no, Decimal("0"))
self.assertEqual(poll.amount_global_abstain, Decimal("1"))
@@ -1505,6 +1572,321 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
self.assertFalse(AssignmentVote.objects.exists())
+class VoteAssignmentPollNamedN(VoteAssignmentPollBaseTestClass):
+ def create_poll(self):
+ return AssignmentPoll.objects.create(
+ assignment=self.assignment,
+ title="test_title_4oi49ckKFk39SDIfj30s",
+ pollmethod=AssignmentPoll.POLLMETHOD_N,
+ type=BasePoll.TYPE_NAMED,
+ )
+
+ def setup_for_multiple_votes(self):
+ self.poll.allow_multiple_votes_per_candidate = True
+ self.poll.votes_amount = 3
+ self.poll.save()
+ self.add_candidate()
+
+ def test_start_poll(self):
+ response = self.client.post(
+ reverse("assignmentpoll-start", args=[self.poll.pk])
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ self.assertEqual(poll.state, AssignmentPoll.STATE_STARTED)
+ self.assertEqual(poll.votesvalid, Decimal("0"))
+ self.assertEqual(poll.votesinvalid, Decimal("0"))
+ self.assertEqual(poll.votescast, Decimal("0"))
+ self.assertFalse(poll.get_votes().exists())
+
+ def test_vote(self):
+ self.add_candidate()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1, "2": 0}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ self.assertEqual(AssignmentVote.objects.count(), 1)
+ poll = AssignmentPoll.objects.get()
+ self.assertEqual(poll.votesvalid, Decimal("1"))
+ self.assertEqual(poll.votesinvalid, Decimal("0"))
+ self.assertEqual(poll.votescast, Decimal("1"))
+ self.assertEqual(poll.state, AssignmentPoll.STATE_STARTED)
+ self.assertTrue(self.admin in poll.voted.all())
+ option1 = poll.options.get(pk=1)
+ option2 = poll.options.get(pk=2)
+ self.assertEqual(option1.yes, Decimal("0"))
+ self.assertEqual(option1.no, Decimal("1"))
+ self.assertEqual(option1.abstain, Decimal("0"))
+ self.assertEqual(option2.yes, Decimal("0"))
+ self.assertEqual(option2.no, Decimal("0"))
+ self.assertEqual(option2.abstain, Decimal("0"))
+
+ def test_change_vote(self):
+ self.add_candidate()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1, "2": 0}},
+ format="json",
+ )
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 0, "2": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ poll = AssignmentPoll.objects.get()
+ option1 = poll.options.get(pk=1)
+ option2 = poll.options.get(pk=2)
+ self.assertEqual(option1.yes, Decimal("0"))
+ self.assertEqual(option1.no, Decimal("1"))
+ self.assertEqual(option1.abstain, Decimal("0"))
+ self.assertEqual(option2.yes, Decimal("0"))
+ self.assertEqual(option2.no, Decimal("0"))
+ self.assertEqual(option2.abstain, Decimal("0"))
+
+ def test_global_yes(self):
+ self.poll.votes_amount = 2
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "Y"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ option = poll.options.get(pk=1)
+ self.assertEqual(option.yes, Decimal("1"))
+ self.assertEqual(option.no, Decimal("0"))
+ self.assertEqual(option.abstain, Decimal("0"))
+ self.assertEqual(poll.amount_global_yes, Decimal("1"))
+ self.assertEqual(poll.amount_global_no, Decimal("0"))
+ self.assertEqual(poll.amount_global_abstain, Decimal("0"))
+
+ def test_global_yes_forbidden(self):
+ self.poll.global_yes = False
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "Y"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+ self.assertEqual(AssignmentPoll.objects.get().amount_global_yes, None)
+
+ def test_global_no(self):
+ self.poll.votes_amount = 2
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "N"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ option = poll.options.get(pk=1)
+ self.assertEqual(option.yes, Decimal("0"))
+ self.assertEqual(option.no, Decimal("1"))
+ self.assertEqual(option.abstain, Decimal("0"))
+ self.assertEqual(poll.amount_global_yes, Decimal("0"))
+ self.assertEqual(poll.amount_global_no, Decimal("1"))
+ self.assertEqual(poll.amount_global_abstain, Decimal("0"))
+
+ def test_global_no_forbidden(self):
+ self.poll.global_no = False
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "N"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+ self.assertEqual(AssignmentPoll.objects.get().amount_global_no, None)
+
+ def test_global_abstain(self):
+ self.poll.votes_amount = 2
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "A"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ option = poll.options.get(pk=1)
+ self.assertEqual(option.yes, Decimal("0"))
+ self.assertEqual(option.no, Decimal("0"))
+ self.assertEqual(option.abstain, Decimal("1"))
+ self.assertEqual(poll.amount_global_yes, Decimal("0"))
+ self.assertEqual(poll.amount_global_no, Decimal("0"))
+ self.assertEqual(poll.amount_global_abstain, Decimal("1"))
+
+ def test_global_abstain_forbidden(self):
+ self.poll.global_abstain = False
+ self.poll.save()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "A"}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+ self.assertEqual(AssignmentPoll.objects.get().amount_global_abstain, None)
+
+ def test_negative_vote(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": -1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_multiple_votes(self):
+ self.setup_for_multiple_votes()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 2, "2": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ option1 = poll.options.get(pk=1)
+ option2 = poll.options.get(pk=2)
+ self.assertEqual(option1.yes, Decimal("0"))
+ self.assertEqual(option1.no, Decimal("2"))
+ self.assertEqual(option1.abstain, Decimal("0"))
+ self.assertEqual(option2.yes, Decimal("0"))
+ self.assertEqual(option2.no, Decimal("1"))
+ self.assertEqual(option2.abstain, Decimal("0"))
+
+ def test_multiple_votes_wrong_amount(self):
+ self.setup_for_multiple_votes()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 2, "2": 2}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_too_many_options(self):
+ self.setup_for_multiple_votes()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1, "2": 1, "3": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_wrong_options(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"2": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_no_permissions(self):
+ self.start_poll()
+ self.make_admin_delegate()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_anonymous(self):
+ self.start_poll()
+ gclient = self.create_guest_client()
+ response = gclient.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_vote_not_present(self):
+ self.start_poll()
+ self.admin.is_present = False
+ self.admin.save()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_wrong_state(self):
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_missing_data(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+ poll = AssignmentPoll.objects.get()
+ self.assertNotIn(self.admin.id, poll.voted.all())
+
+ def test_wrong_data_format(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": [1, 2, 5]},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_wrong_option_format(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": "string"}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_wrong_option_id_type(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"id": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_wrong_vote_data(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": [None]}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+
class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
def create_poll(self):
return AssignmentPoll.objects.create(
@@ -1698,12 +2080,12 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
self.assertFalse(AssignmentVote.objects.exists())
-class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
+class VoteAssignmentPollPseudoanonymousY(VoteAssignmentPollBaseTestClass):
def create_poll(self):
return AssignmentPoll.objects.create(
assignment=self.assignment,
title="test_title_Zrvh146QAdq7t6iSDwZk",
- pollmethod=AssignmentPoll.POLLMETHOD_VOTES,
+ pollmethod=AssignmentPoll.POLLMETHOD_Y,
type=BasePoll.TYPE_PSEUDOANONYMOUS,
)
@@ -1933,6 +2315,241 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
self.assertFalse(AssignmentVote.objects.exists())
+class VoteAssignmentPollPseudoanonymousN(VoteAssignmentPollBaseTestClass):
+ def create_poll(self):
+ return AssignmentPoll.objects.create(
+ assignment=self.assignment,
+ title="test_title_wWPOVJgL9afm83eamf3e",
+ pollmethod=AssignmentPoll.POLLMETHOD_N,
+ type=BasePoll.TYPE_PSEUDOANONYMOUS,
+ )
+
+ def setup_for_multiple_votes(self):
+ self.poll.allow_multiple_votes_per_candidate = True
+ self.poll.votes_amount = 3
+ self.poll.save()
+ self.add_candidate()
+
+ def test_start_poll(self):
+ response = self.client.post(
+ reverse("assignmentpoll-start", args=[self.poll.pk])
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ self.assertEqual(poll.state, AssignmentPoll.STATE_STARTED)
+ self.assertEqual(poll.votesvalid, Decimal("0"))
+ self.assertEqual(poll.votesinvalid, Decimal("0"))
+ self.assertEqual(poll.votescast, Decimal("0"))
+ self.assertFalse(poll.get_votes().exists())
+
+ def test_vote(self):
+ self.add_candidate()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1, "2": 0}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ self.assertEqual(AssignmentVote.objects.count(), 1)
+ poll = AssignmentPoll.objects.get()
+ self.assertEqual(poll.votesvalid, Decimal("1"))
+ self.assertEqual(poll.votesinvalid, Decimal("0"))
+ self.assertEqual(poll.votescast, Decimal("1"))
+ self.assertEqual(poll.state, AssignmentPoll.STATE_STARTED)
+ self.assertTrue(self.admin in poll.voted.all())
+ option1 = poll.options.get(pk=1)
+ option2 = poll.options.get(pk=2)
+ self.assertEqual(option1.yes, Decimal("0"))
+ self.assertEqual(option1.no, Decimal("1"))
+ self.assertEqual(option1.abstain, Decimal("0"))
+ self.assertEqual(option2.yes, Decimal("0"))
+ self.assertEqual(option2.no, Decimal("0"))
+ self.assertEqual(option2.abstain, Decimal("0"))
+ for vote in poll.get_votes():
+ self.assertIsNone(vote.user)
+
+ def test_change_vote(self):
+ self.add_candidate()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1, "2": 0}},
+ format="json",
+ )
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 0, "2": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ poll = AssignmentPoll.objects.get()
+ option1 = poll.options.get(pk=1)
+ option2 = poll.options.get(pk=2)
+ self.assertEqual(option1.yes, Decimal("0"))
+ self.assertEqual(option1.no, Decimal("1"))
+ self.assertEqual(option1.abstain, Decimal("0"))
+ self.assertEqual(option2.yes, Decimal("0"))
+ self.assertEqual(option2.no, Decimal("0"))
+ self.assertEqual(option2.abstain, Decimal("0"))
+
+ def test_negative_vote(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": -1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_multiple_votes(self):
+ self.setup_for_multiple_votes()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 2, "2": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
+ poll = AssignmentPoll.objects.get()
+ option1 = poll.options.get(pk=1)
+ option2 = poll.options.get(pk=2)
+ self.assertEqual(option1.yes, Decimal("0"))
+ self.assertEqual(option1.no, Decimal("2"))
+ self.assertEqual(option1.abstain, Decimal("0"))
+ self.assertEqual(option2.yes, Decimal("0"))
+ self.assertEqual(option2.no, Decimal("1"))
+ self.assertEqual(option2.abstain, Decimal("0"))
+ for vote in poll.get_votes():
+ self.assertIsNone(vote.user)
+
+ def test_multiple_votes_wrong_amount(self):
+ self.setup_for_multiple_votes()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 2, "2": 2}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_too_many_options(self):
+ self.setup_for_multiple_votes()
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1, "2": 1, "3": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_wrong_options(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"2": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_no_permissions(self):
+ self.start_poll()
+ self.make_admin_delegate()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_anonymous(self):
+ self.start_poll()
+ gclient = self.create_guest_client()
+ response = gclient.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_vote_not_present(self):
+ self.start_poll()
+ self.admin.is_present = False
+ self.admin.save()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_wrong_state(self):
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_missing_data(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+ poll = AssignmentPoll.objects.get()
+ self.assertNotIn(self.admin.id, poll.voted.all())
+
+ def test_wrong_data_format(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"data": [1, 2, 5]}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_wrong_option_format(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": "string"}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
+
+ def test_wrong_option_id_type(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"id": 1}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+ def test_wrong_vote_data(self):
+ self.start_poll()
+ response = self.client.post(
+ reverse("assignmentpoll-vote", args=[self.poll.pk]),
+ {"data": {"1": [None]}},
+ format="json",
+ )
+ self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
+ self.assertFalse(AssignmentVote.objects.exists())
+
+
# test autoupdates
class VoteAssignmentPollAutoupdatesBaseClass(TestCase):
poll_type = "" # set by subclass, defines which poll type we use
@@ -1994,10 +2611,12 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
"assignments/assignment-poll:1": {
"allow_multiple_votes_per_candidate": False,
"assignment_id": 1,
- "global_abstain": True,
+ "global_yes": True,
"global_no": True,
- "amount_global_abstain": None,
+ "global_abstain": True,
+ "amount_global_yes": None,
"amount_global_no": None,
+ "amount_global_abstain": None,
"groups_id": [GROUP_DELEGATE_PK],
"id": 1,
"options_id": [1],
@@ -2064,8 +2683,9 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
{
"allow_multiple_votes_per_candidate": False,
"assignment_id": 1,
- "global_abstain": True,
+ "global_yes": True,
"global_no": True,
+ "global_abstain": True,
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
"state": AssignmentPoll.STATE_STARTED,
"type": AssignmentPoll.TYPE_NAMED,
@@ -2114,12 +2734,14 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
autoupdate[0]["assignments/assignment-poll:1"],
{
"allow_multiple_votes_per_candidate": False,
- "amount_global_abstain": None,
+ "amount_global_yes": None,
"amount_global_no": None,
+ "amount_global_abstain": None,
"assignment_id": 1,
"description": "test_description_paiquei5ahpie1wu8ohW",
- "global_abstain": True,
+ "global_yes": True,
"global_no": True,
+ "global_abstain": True,
"groups_id": [GROUP_DELEGATE_PK],
"id": 1,
"majority_method": "two_thirds",
@@ -2186,10 +2808,12 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
"assignments/assignment-poll:1": {
"allow_multiple_votes_per_candidate": False,
"assignment_id": 1,
- "global_abstain": True,
+ "global_yes": True,
"global_no": True,
- "amount_global_abstain": None,
+ "global_abstain": True,
+ "amount_global_yes": None,
"amount_global_no": None,
+ "amount_global_abstain": None,
"groups_id": [GROUP_DELEGATE_PK],
"id": 1,
"options_id": [1],
@@ -2241,8 +2865,9 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
{
"allow_multiple_votes_per_candidate": False,
"assignment_id": 1,
- "global_abstain": True,
+ "global_yes": True,
"global_no": True,
+ "global_abstain": True,
"pollmethod": AssignmentPoll.POLLMETHOD_YNA,
"state": AssignmentPoll.STATE_STARTED,
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
@@ -2291,12 +2916,14 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
{
"assignments/assignment-poll:1": {
"allow_multiple_votes_per_candidate": False,
- "amount_global_abstain": None,
+ "amount_global_yes": None,
"amount_global_no": None,
+ "amount_global_abstain": None,
"assignment_id": 1,
"description": "test_description_paiquei5ahpie1wu8ohW",
- "global_abstain": True,
+ "global_yes": True,
"global_no": True,
+ "global_abstain": True,
"groups_id": [GROUP_DELEGATE_PK],
"id": 1,
"majority_method": "two_thirds",
|