Merge pull request #5719 from FinnStutzenstein/minVotesAmount
Minimum amount of votes
This commit is contained in:
commit
1145ae1460
2
.gitignore
vendored
2
.gitignore
vendored
@ -28,6 +28,8 @@ server/.venv
|
|||||||
/haproxy/
|
/haproxy/
|
||||||
/docker/keys/
|
/docker/keys/
|
||||||
/docs/
|
/docs/
|
||||||
|
# OS3+-Submodules
|
||||||
|
/autoupdate/
|
||||||
# Plugin development
|
# Plugin development
|
||||||
openslides_*
|
openslides_*
|
||||||
# Old OS3 stuff
|
# Old OS3 stuff
|
||||||
|
@ -41,7 +41,8 @@ export class AssignmentPoll extends BasePoll<
|
|||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public assignment_id: number;
|
public assignment_id: number;
|
||||||
public votes_amount: number;
|
public min_votes_amount: number;
|
||||||
|
public max_votes_amount: number;
|
||||||
public allow_multiple_votes_per_candidate: boolean;
|
public allow_multiple_votes_per_candidate: boolean;
|
||||||
public global_yes: boolean;
|
public global_yes: boolean;
|
||||||
public global_no: boolean;
|
public global_no: boolean;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
|
import { AbstractControl, FormGroup, ValidationErrors, ValidatorFn } from '@angular/forms';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constant to validate a `duration` field.
|
* Constant to validate a `duration` field.
|
||||||
@ -17,3 +17,13 @@ export const durationValidator: ValidatorFn = (control: FormGroup): ValidationEr
|
|||||||
const regExp = /^\s*([0-9]+)(:)?([0-5][0-9]?)?\s*[h|m]?$/g;
|
const regExp = /^\s*([0-9]+)(:)?([0-5][0-9]?)?\s*[h|m]?$/g;
|
||||||
return regExp.test(control.value) || control.value === '' ? null : { valid: false };
|
return regExp.test(control.value) || control.value === '' ? null : { valid: false };
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isNumberRange(minCtrlName: string, maxCtrlName: string): ValidatorFn {
|
||||||
|
return (formControl: AbstractControl): { [key: string]: any } => {
|
||||||
|
const min = formControl.get(minCtrlName).value;
|
||||||
|
const max = formControl.get(maxCtrlName).value;
|
||||||
|
if (min > max) {
|
||||||
|
return { rangeError: true };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
@ -32,10 +32,26 @@
|
|||||||
<small *ngIf="poll.pollmethod">
|
<small *ngIf="poll.pollmethod">
|
||||||
<span> {{ 'Voting method' | translate }}: {{ poll.pollmethodVerbose | translate }} </span>
|
<span> {{ 'Voting method' | translate }}: {{ poll.pollmethodVerbose | translate }} </span>
|
||||||
<!-- amount of votes -->
|
<!-- amount of votes -->
|
||||||
<span *ngIf="poll.votes_amount > 1"> ({{ poll.votes_amount }} {{ 'Votes' | translate }})</span>
|
<!-- <span *ngIf="poll.max_votes_amount > 1"> ({{ poll.max_votes_amount }} {{ 'Votes' | translate }})</span>
|
||||||
|
<span *ngIf="poll.max_votes_amount > 1"> ({{ poll.max_votes_amount }} {{ 'Votes' | translate }})</span> -->
|
||||||
<br />
|
<br />
|
||||||
</small>
|
</small>
|
||||||
|
|
||||||
|
<!-- Amount of Votes -->
|
||||||
|
<small *ngIf="poll.max_votes_amount > 1">
|
||||||
|
<ng-container *ngIf="poll.max_votes_amount !== poll.min_votes_amount">
|
||||||
|
<span> {{ pollPropertyVerbose.min_votes_amount | translate }}: {{ poll.min_votes_amount }}</span>
|
||||||
|
<br />
|
||||||
|
<span> {{ pollPropertyVerbose.max_votes_amount | translate }}: {{ poll.max_votes_amount }}</span>
|
||||||
|
<br />
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-container *ngIf="poll.max_votes_amount === poll.min_votes_amount">
|
||||||
|
<span> {{ 'Votes' | translate }}: {{ poll.max_votes_amount }} </span>
|
||||||
|
<br />
|
||||||
|
</ng-container>
|
||||||
|
</small>
|
||||||
|
|
||||||
<!-- 100% base -->
|
<!-- 100% base -->
|
||||||
<small *ngIf="poll.onehundred_percent_base">
|
<small *ngIf="poll.onehundred_percent_base">
|
||||||
{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}
|
{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}
|
||||||
|
@ -27,8 +27,7 @@ export class AssignmentPollMetaInfoComponent {
|
|||||||
public constructor() {}
|
public constructor() {}
|
||||||
|
|
||||||
public userCanVoe(): boolean {
|
public userCanVoe(): boolean {
|
||||||
// this.poll.canBeVotedFor
|
return this.poll.canBeVotedFor();
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public getOptionTitle(option: ViewAssignmentOption): string {
|
public getOptionTitle(option: ViewAssignmentOption): string {
|
||||||
|
@ -28,10 +28,14 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Leftover votes -->
|
<!-- Leftover votes -->
|
||||||
<h4 *ngIf="(poll.isMethodY || poll.isMethodN) && poll.votes_amount > 1">
|
<h4 *ngIf="(poll.isMethodY || poll.isMethodN) && poll.max_votes_amount > 1">
|
||||||
{{ 'Available votes' | translate }}:
|
{{ 'Available votes' | translate }}:
|
||||||
|
<b> {{ getVotesAvailable(delegation) }}/{{ poll.max_votes_amount }} </b>
|
||||||
|
|
||||||
<b> {{ getVotesAvailable(delegation) }}/{{ poll.votes_amount }} </b>
|
<span *ngIf="poll.min_votes_amount > 1">
|
||||||
|
({{ 'At least' | translate }} <b>{{ poll.min_votes_amount }}</b
|
||||||
|
>)
|
||||||
|
</span>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<!-- Options and Actions -->
|
<!-- Options and Actions -->
|
||||||
@ -170,7 +174,7 @@
|
|||||||
mat-flat-button
|
mat-flat-button
|
||||||
color="accent"
|
color="accent"
|
||||||
(click)="submitVote(delegation)"
|
(click)="submitVote(delegation)"
|
||||||
[disabled]="getVotesCount(delegation) == 0"
|
[disabled]="getVotesCount(delegation) < minVotes"
|
||||||
>
|
>
|
||||||
<mat-icon> how_to_vote </mat-icon>
|
<mat-icon> how_to_vote </mat-icon>
|
||||||
<span>
|
<span>
|
||||||
|
@ -57,6 +57,10 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
return this.poll.assignment.default_poll_description;
|
return this.poll.assignment.default_poll_description;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get minVotes(): number {
|
||||||
|
return this.poll.min_votes_amount;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
@ -141,7 +145,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
}
|
}
|
||||||
|
|
||||||
public getVotesAvailable(user: ViewUser = this.user): number {
|
public getVotesAvailable(user: ViewUser = this.user): number {
|
||||||
return this.poll.votes_amount - this.getVotesCount(user);
|
return this.poll.max_votes_amount - this.getVotesCount(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isGlobalOptionSelected(user: ViewUser = this.user): boolean {
|
private isGlobalOptionSelected(user: ViewUser = this.user): boolean {
|
||||||
@ -177,12 +181,12 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.poll.isMethodY || this.poll.isMethodN) {
|
if (this.poll.isMethodY || this.poll.isMethodN) {
|
||||||
const votesAmount = this.poll.votes_amount;
|
const maxVotesAmount = this.poll.max_votes_amount;
|
||||||
const tmpVoteRequest = this.poll.options
|
const tmpVoteRequest = this.poll.options
|
||||||
.map(option => option.id)
|
.map(option => option.id)
|
||||||
.reduce((o, n) => {
|
.reduce((o, n) => {
|
||||||
o[n] = 0;
|
o[n] = 0;
|
||||||
if (votesAmount === 1) {
|
if (maxVotesAmount === 1) {
|
||||||
if (n === optionId && this.voteRequestData[user.id].votes[n] !== 1) {
|
if (n === optionId && this.voteRequestData[user.id].votes[n] !== 1) {
|
||||||
o[n] = 1;
|
o[n] = 1;
|
||||||
}
|
}
|
||||||
@ -195,11 +199,11 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
|
|
||||||
// check if you can still vote
|
// check if you can still vote
|
||||||
const countedVotes = Object.keys(tmpVoteRequest).filter(key => tmpVoteRequest[key]).length;
|
const countedVotes = Object.keys(tmpVoteRequest).filter(key => tmpVoteRequest[key]).length;
|
||||||
if (countedVotes <= votesAmount) {
|
if (countedVotes <= maxVotesAmount) {
|
||||||
this.voteRequestData[user.id].votes = tmpVoteRequest;
|
this.voteRequestData[user.id].votes = tmpVoteRequest;
|
||||||
|
|
||||||
// if you have no options anymore, try to send
|
// if you have no options anymore, try to send
|
||||||
if (this.getVotesCount(user) === votesAmount) {
|
if (this.getVotesCount(user) === maxVotesAmount) {
|
||||||
this.submitVote(user);
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -58,6 +58,8 @@ export abstract class BasePollDialogComponent<
|
|||||||
votes: this.getVoteData(),
|
votes: this.getVoteData(),
|
||||||
publish_immediately: this.publishImmediately
|
publish_immediately: this.publishImmediately
|
||||||
};
|
};
|
||||||
|
console.log('answer: ', answer);
|
||||||
|
|
||||||
this.dialogRef.close(answer);
|
this.dialogRef.close(answer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -30,7 +30,7 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Groups entitled to Vote -->
|
<!-- Groups entitled to Vote -->
|
||||||
<mat-form-field *ngIf="contentForm.get('type').value && contentForm.get('type').value !== 'analog'">
|
<mat-form-field *ngIf="isEVotingSelected">
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
formControlName="groups_id"
|
formControlName="groups_id"
|
||||||
[multiple]="true"
|
[multiple]="true"
|
||||||
@ -57,21 +57,39 @@
|
|||||||
<mat-error>{{ 'This field is required.' | translate }}</mat-error>
|
<mat-error>{{ 'This field is required.' | translate }}</mat-error>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Amount of Votes -->
|
<ng-container formGroupName="votes_amount" *ngIf="isEVotingSelected">
|
||||||
<mat-form-field *ngIf="showAmountAndGlobal(data)">
|
<!-- Min Amount of Votes -->
|
||||||
<input
|
<mat-form-field *ngIf="showAmountAndGlobal(data)">
|
||||||
type="number"
|
<input
|
||||||
matInput
|
type="number"
|
||||||
placeholder="{{ PollPropertyVerbose.votes_amount | translate }}"
|
matInput
|
||||||
formControlName="votes_amount"
|
placeholder="{{ PollPropertyVerbose.min_votes_amount | translate }}"
|
||||||
min="1"
|
formControlName="min_votes_amount"
|
||||||
required
|
min="1"
|
||||||
/>
|
required
|
||||||
</mat-form-field>
|
[errorStateMatcher]="parentErrorStateMatcher"
|
||||||
|
/>
|
||||||
|
<mat-error *ngIf="contentForm.controls['votes_amount'].hasError('rangeError')">
|
||||||
|
{{ 'Min votes must be smaller or equal to max votes' | translate }}
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Max Amount of Votes -->
|
||||||
|
<mat-form-field *ngIf="showAmountAndGlobal(data)">
|
||||||
|
<input
|
||||||
|
type="number"
|
||||||
|
matInput
|
||||||
|
placeholder="{{ PollPropertyVerbose.max_votes_amount | translate }}"
|
||||||
|
formControlName="max_votes_amount"
|
||||||
|
min="1"
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</mat-form-field>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Amount of Votes and global options -->
|
<!-- Amount of Votes and global options -->
|
||||||
<div class="global-options" *ngIf="showAmountAndGlobal(data)">
|
<div class="global-options" *ngIf="isEVotingSelected && showAmountAndGlobal(data)">
|
||||||
<mat-checkbox formControlName="global_yes">
|
<mat-checkbox formControlName="global_yes">
|
||||||
{{ PollPropertyVerbose.global_yes | translate }}
|
{{ PollPropertyVerbose.global_yes | translate }}
|
||||||
</mat-checkbox>
|
</mat-checkbox>
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
import { Component, Input, OnInit, ViewEncapsulation } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
|
import { AbstractControl, FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms';
|
||||||
import { MatDialog } from '@angular/material/dialog';
|
import { MatDialog } from '@angular/material/dialog';
|
||||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
@ -13,7 +13,9 @@ import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-priv
|
|||||||
import { AssignmentPollMethod, AssignmentPollPercentBase } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethod, AssignmentPollPercentBase } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { PercentBase } from 'app/shared/models/poll/base-poll';
|
import { PercentBase } from 'app/shared/models/poll/base-poll';
|
||||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { ParentErrorStateMatcher } from 'app/shared/parent-error-state-matcher';
|
||||||
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
|
||||||
|
import { isNumberRange } from 'app/shared/validators/custom-validators';
|
||||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import {
|
import {
|
||||||
@ -39,6 +41,7 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
* The form-group for the meta-info.
|
* The form-group for the meta-info.
|
||||||
*/
|
*/
|
||||||
public contentForm: FormGroup;
|
public contentForm: FormGroup;
|
||||||
|
public parentErrorStateMatcher = new ParentErrorStateMatcher();
|
||||||
|
|
||||||
public PollType = PollType;
|
public PollType = PollType;
|
||||||
public PollPropertyVerbose = PollPropertyVerbose;
|
public PollPropertyVerbose = PollPropertyVerbose;
|
||||||
@ -102,6 +105,10 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isEVotingSelected(): boolean {
|
||||||
|
return this.contentForm.get('type').value && this.contentForm.get('type').value !== 'analog';
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Retrieves necessary metadata from the pollService,
|
* Constructor. Retrieves necessary metadata from the pollService,
|
||||||
* injects the poll itself
|
* injects the poll itself
|
||||||
@ -133,19 +140,15 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.data instanceof ViewAssignmentPoll) {
|
if (this.data instanceof ViewAssignmentPoll) {
|
||||||
if (this.data.assignment && !this.data.votes_amount) {
|
if (this.data.assignment && !this.data.max_votes_amount) {
|
||||||
this.data.votes_amount = this.data.assignment.open_posts;
|
this.data.max_votes_amount = this.data.assignment.open_posts;
|
||||||
}
|
}
|
||||||
if (!this.data.pollmethod) {
|
if (!this.data.pollmethod) {
|
||||||
this.data.pollmethod = this.configService.instant('assignment_poll_method');
|
this.data.pollmethod = this.configService.instant('assignment_poll_method');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Object.keys(this.contentForm.controls).forEach(key => {
|
this.patchForm(this.contentForm);
|
||||||
if (this.data[key]) {
|
|
||||||
this.contentForm.get(key).patchValue(this.data[key]);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
this.updatePollValues(this.contentForm.value);
|
this.updatePollValues(this.contentForm.value);
|
||||||
this.updatePercentBases(this.contentForm.get('pollmethod').value);
|
this.updatePercentBases(this.contentForm.get('pollmethod').value);
|
||||||
@ -173,6 +176,25 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
this.setWarning();
|
this.setWarning();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic recursive helper function to patch the form
|
||||||
|
* will transitive move poll.min_votes_amount and poll.max_votes_amount into
|
||||||
|
* form.votes_amount.min_votes_amount/max_votes_amount
|
||||||
|
* @param formGroup
|
||||||
|
*/
|
||||||
|
private patchForm(formGroup: FormGroup): void {
|
||||||
|
for (const key of Object.keys(formGroup.controls)) {
|
||||||
|
const currentControl = formGroup.controls[key];
|
||||||
|
if (currentControl instanceof FormControl) {
|
||||||
|
if (this.data[key]) {
|
||||||
|
currentControl.patchValue(this.data[key]);
|
||||||
|
}
|
||||||
|
} else if (currentControl instanceof FormGroup) {
|
||||||
|
this.patchForm(currentControl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private disablePollType(): void {
|
private disablePollType(): void {
|
||||||
this.contentForm.get('type').disable();
|
this.contentForm.get('type').disable();
|
||||||
}
|
}
|
||||||
@ -243,8 +265,14 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getValues<V extends ViewBasePoll>(): Partial<V> {
|
public getValues(): Partial<T> {
|
||||||
return { ...this.data, ...this.contentForm.value };
|
return { ...this.data, ...this.serializeForm(this.contentForm) };
|
||||||
|
}
|
||||||
|
|
||||||
|
private serializeForm(formGroup: FormGroup): Partial<T> {
|
||||||
|
const formData = { ...formGroup.value, ...formGroup.value.votes_amount };
|
||||||
|
delete formData.votes_amount;
|
||||||
|
return formData;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -279,6 +307,14 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
if (data.pollmethod === 'Y' || data.pollmethod === 'N') {
|
if (data.pollmethod === 'Y' || data.pollmethod === 'N') {
|
||||||
this.pollValues.push([this.pollService.getVerboseNameForKey('votes_amount'), data.votes_amount]);
|
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_yes'), data.global_yes]);
|
||||||
|
this.pollValues.push([
|
||||||
|
this.pollService.getVerboseNameForKey('max_votes_amount'),
|
||||||
|
data.max_votes_amount
|
||||||
|
]);
|
||||||
|
this.pollValues.push([
|
||||||
|
this.pollService.getVerboseNameForKey('min_votes_amount'),
|
||||||
|
data.min_votes_amount
|
||||||
|
]);
|
||||||
this.pollValues.push([this.pollService.getVerboseNameForKey('global_no'), data.global_no]);
|
this.pollValues.push([this.pollService.getVerboseNameForKey('global_no'), data.global_no]);
|
||||||
this.pollValues.push([this.pollService.getVerboseNameForKey('global_abstain'), data.global_abstain]);
|
this.pollValues.push([this.pollService.getVerboseNameForKey('global_abstain'), data.global_abstain]);
|
||||||
}
|
}
|
||||||
@ -292,7 +328,13 @@ export class PollFormComponent<T extends ViewBasePoll, S extends PollService>
|
|||||||
pollmethod: ['', Validators.required],
|
pollmethod: ['', Validators.required],
|
||||||
onehundred_percent_base: ['', Validators.required],
|
onehundred_percent_base: ['', Validators.required],
|
||||||
majority_method: ['', Validators.required],
|
majority_method: ['', Validators.required],
|
||||||
votes_amount: [1, [Validators.required, Validators.min(1)]],
|
votes_amount: this.fb.group(
|
||||||
|
{
|
||||||
|
max_votes_amount: [1, [Validators.required, Validators.min(1)]],
|
||||||
|
min_votes_amount: [1, [Validators.required, Validators.min(1)]]
|
||||||
|
},
|
||||||
|
{ validator: isNumberRange('min_votes_amount', 'max_votes_amount') }
|
||||||
|
),
|
||||||
groups_id: [],
|
groups_id: [],
|
||||||
global_yes: [false],
|
global_yes: [false],
|
||||||
global_no: [false],
|
global_no: [false],
|
||||||
|
@ -39,16 +39,18 @@ export const PollTypeVerbose = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const PollPropertyVerbose = {
|
export const PollPropertyVerbose = {
|
||||||
majority_method: 'Required majority',
|
majority_method: _('Required majority'),
|
||||||
onehundred_percent_base: '100% base',
|
onehundred_percent_base: _('100% base'),
|
||||||
type: _('Voting type'),
|
type: _('Voting type'),
|
||||||
pollmethod: _('Voting method'),
|
pollmethod: _('Voting method'),
|
||||||
state: 'State',
|
state: _('State'),
|
||||||
groups: _('Entitled to vote'),
|
groups: _('Entitled to vote'),
|
||||||
votes_amount: _('Amount of votes'),
|
votes_amount: _('Amount of votes'),
|
||||||
global_yes: _('General approval'),
|
global_yes: _('General approval'),
|
||||||
global_no: _('General rejection'),
|
global_no: _('General rejection'),
|
||||||
global_abstain: _('General abstain')
|
global_abstain: _('General abstain'),
|
||||||
|
max_votes_amount: _('Maximum amount of votes'),
|
||||||
|
min_votes_amount: _('Minimum amount of votes')
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MajorityMethodVerbose = {
|
export const MajorityMethodVerbose = {
|
||||||
|
@ -9,7 +9,8 @@ export interface AssignmentPollSlideData extends BasePollSlideData {
|
|||||||
title: string;
|
title: string;
|
||||||
type: PollType;
|
type: PollType;
|
||||||
pollmethod: AssignmentPollMethod;
|
pollmethod: AssignmentPollMethod;
|
||||||
votes_amount: number;
|
max_votes_amount: number;
|
||||||
|
min_votes_amount: number;
|
||||||
description: string;
|
description: string;
|
||||||
state: PollState;
|
state: PollState;
|
||||||
onehundred_percent_base: PercentBase;
|
onehundred_percent_base: PercentBase;
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
# Generated by Django 2.2.15 on 2020-11-24 08:12
|
||||||
|
|
||||||
|
import django.core.validators
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("assignments", "0017_vote_to_y"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RenameField(
|
||||||
|
model_name="assignmentpoll",
|
||||||
|
old_name="votes_amount",
|
||||||
|
new_name="max_votes_amount",
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="assignmentpoll",
|
||||||
|
name="min_votes_amount",
|
||||||
|
field=models.IntegerField(
|
||||||
|
default=1, validators=[django.core.validators.MinValueValidator(1)]
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -14,6 +14,7 @@ from openslides.utils.autoupdate import inform_changed_data
|
|||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.manager import BaseManager
|
from openslides.utils.manager import BaseManager
|
||||||
from openslides.utils.models import RESTModelMixin
|
from openslides.utils.models import RESTModelMixin
|
||||||
|
from openslides.utils.rest_api import ValidationError
|
||||||
|
|
||||||
from ..utils.models import CASCADE_AND_AUTOUPDATE, SET_NULL_AND_AUTOUPDATE
|
from ..utils.models import CASCADE_AND_AUTOUPDATE, SET_NULL_AND_AUTOUPDATE
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
@ -377,8 +378,11 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
|
|||||||
decimal_places=6,
|
decimal_places=6,
|
||||||
)
|
)
|
||||||
|
|
||||||
votes_amount = models.IntegerField(default=1, validators=[MinValueValidator(1)])
|
min_votes_amount = models.IntegerField(default=1, validators=[MinValueValidator(1)])
|
||||||
""" For "votes" mode: The amount of votes a voter can give. """
|
""" For "votes" mode: The min amount of votes a voter can give. """
|
||||||
|
|
||||||
|
max_votes_amount = models.IntegerField(default=1, validators=[MinValueValidator(1)])
|
||||||
|
""" For "votes" mode: The max amount of votes a voter can give. """
|
||||||
|
|
||||||
allow_multiple_votes_per_candidate = models.BooleanField(default=False)
|
allow_multiple_votes_per_candidate = models.BooleanField(default=False)
|
||||||
|
|
||||||
@ -447,6 +451,13 @@ class AssignmentPoll(RESTModelMixin, BasePoll):
|
|||||||
get_amount_global_abstain, set_amount_global_abstain
|
get_amount_global_abstain, set_amount_global_abstain
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def save(self, *args, **kwargs):
|
||||||
|
if self.max_votes_amount < self.min_votes_amount:
|
||||||
|
raise ValidationError(
|
||||||
|
{"detail": "max votes must be larger or equal to min votes"}
|
||||||
|
)
|
||||||
|
super().save(*args, **kwargs)
|
||||||
|
|
||||||
def create_options(self, skip_autoupdate=False):
|
def create_options(self, skip_autoupdate=False):
|
||||||
related_users = AssignmentRelatedUser.objects.filter(
|
related_users = AssignmentRelatedUser.objects.filter(
|
||||||
assignment__id=self.assignment.id
|
assignment__id=self.assignment.id
|
||||||
|
@ -60,7 +60,8 @@ async def assignment_poll_slide(
|
|||||||
"title",
|
"title",
|
||||||
"type",
|
"type",
|
||||||
"pollmethod",
|
"pollmethod",
|
||||||
"votes_amount",
|
"min_votes_amount",
|
||||||
|
"max_votes_amount",
|
||||||
"description",
|
"description",
|
||||||
"state",
|
"state",
|
||||||
"onehundred_percent_base",
|
"onehundred_percent_base",
|
||||||
|
@ -93,7 +93,8 @@ class AssignmentPollSerializer(BasePollSerializer):
|
|||||||
"assignment",
|
"assignment",
|
||||||
"description",
|
"description",
|
||||||
"pollmethod",
|
"pollmethod",
|
||||||
"votes_amount",
|
"min_votes_amount",
|
||||||
|
"max_votes_amount",
|
||||||
"allow_multiple_votes_per_candidate",
|
"allow_multiple_votes_per_candidate",
|
||||||
"global_yes",
|
"global_yes",
|
||||||
"amount_global_yes",
|
"amount_global_yes",
|
||||||
|
@ -377,7 +377,7 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
- ids should be integers of valid option ids for this poll
|
- 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
|
- amounts must be 0 or 1, if poll.allow_multiple_votes_per_candidate is False
|
||||||
- if an option is not given, 0 is assumed
|
- if an option is not given, 0 is assumed
|
||||||
- The sum of all amounts must be grater than 0 and <= poll.votes_amount
|
- The sum of all amounts must be >= poll.min_votes_amount and <= poll.max_votes_amount
|
||||||
|
|
||||||
YN/YNA:
|
YN/YNA:
|
||||||
{<option_id>: 'Y' | 'N' [|'A']}
|
{<option_id>: 'Y' | 'N' [|'A']}
|
||||||
@ -471,11 +471,18 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
)
|
)
|
||||||
amount_sum += amount
|
amount_sum += amount
|
||||||
|
|
||||||
if amount_sum > poll.votes_amount:
|
if amount_sum > poll.max_votes_amount:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
{
|
{
|
||||||
"detail": "You can give a maximum of {0} votes",
|
"detail": "You can give a maximum of {0} votes",
|
||||||
"args": [poll.votes_amount],
|
"args": [poll.max_votes_amount],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
if amount_sum < poll.min_votes_amount:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"detail": "You must give a minimum of {0} votes",
|
||||||
|
"args": [poll.min_votes_amount],
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
# return, if there is a global vote, because we dont have to check option presence
|
# return, if there is a global vote, because we dont have to check option presence
|
||||||
|
@ -136,7 +136,8 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
self.assertEqual(poll.amount_global_no, None)
|
self.assertEqual(poll.amount_global_no, None)
|
||||||
self.assertEqual(poll.amount_global_abstain, 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.min_votes_amount, 1)
|
||||||
|
self.assertEqual(poll.max_votes_amount, 1)
|
||||||
self.assertEqual(poll.assignment.id, self.assignment.id)
|
self.assertEqual(poll.assignment.id, self.assignment.id)
|
||||||
self.assertEqual(poll.description, "")
|
self.assertEqual(poll.description, "")
|
||||||
self.assertTrue(poll.options.exists())
|
self.assertTrue(poll.options.exists())
|
||||||
@ -157,7 +158,8 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
"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,
|
"min_votes_amount": 5,
|
||||||
|
"max_votes_amount": 8,
|
||||||
"description": "test_description_ieM8ThuasoSh8aecai8p",
|
"description": "test_description_ieM8ThuasoSh8aecai8p",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -171,7 +173,8 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
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.min_votes_amount, 5)
|
||||||
|
self.assertEqual(poll.max_votes_amount, 8)
|
||||||
self.assertEqual(poll.description, "test_description_ieM8ThuasoSh8aecai8p")
|
self.assertEqual(poll.description, "test_description_ieM8ThuasoSh8aecai8p")
|
||||||
|
|
||||||
def test_no_candidates(self):
|
def test_no_candidates(self):
|
||||||
@ -521,6 +524,44 @@ class CreateAssignmentPoll(TestCase):
|
|||||||
self.assertFalse(AssignmentPoll.objects.exists())
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
|
def test_create_with_unmatched_votes_amount(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_9FP4m2f2k09f4gni2sqq",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_Y,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
|
"min_votes_amount": 5,
|
||||||
|
"max_votes_amount": 4,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertFalse(AssignmentPoll.objects.exists())
|
||||||
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
|
def test_create_with_equal_votes_amount(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-list"),
|
||||||
|
{
|
||||||
|
"title": "test_title_9FP4m2f2k09f4gni2sqq",
|
||||||
|
"pollmethod": AssignmentPoll.POLLMETHOD_Y,
|
||||||
|
"type": "named",
|
||||||
|
"assignment_id": self.assignment.id,
|
||||||
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_YNA,
|
||||||
|
"majority_method": AssignmentPoll.MAJORITY_SIMPLE,
|
||||||
|
"min_votes_amount": 4,
|
||||||
|
"max_votes_amount": 4,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_201_CREATED)
|
||||||
|
self.assertTrue(AssignmentPoll.objects.exists())
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.min_votes_amount, 4)
|
||||||
|
self.assertEqual(poll.max_votes_amount, 4)
|
||||||
|
|
||||||
|
|
||||||
class UpdateAssignmentPoll(TestCase):
|
class UpdateAssignmentPoll(TestCase):
|
||||||
"""
|
"""
|
||||||
@ -697,7 +738,8 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
"global_no": True,
|
"global_no": True,
|
||||||
"global_abstain": False,
|
"global_abstain": False,
|
||||||
"allow_multiple_votes_per_candidate": True,
|
"allow_multiple_votes_per_candidate": True,
|
||||||
"votes_amount": 42,
|
"min_votes_amount": 32,
|
||||||
|
"max_votes_amount": 42,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -711,7 +753,32 @@ class UpdateAssignmentPoll(TestCase):
|
|||||||
self.assertEqual(poll.amount_global_no, Decimal("0"))
|
self.assertEqual(poll.amount_global_no, Decimal("0"))
|
||||||
self.assertEqual(poll.amount_global_abstain, None)
|
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.min_votes_amount, 32)
|
||||||
|
self.assertEqual(poll.max_votes_amount, 42)
|
||||||
|
|
||||||
|
def test_patch_unmatched_votes_amounts(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{
|
||||||
|
"min_votes_amount": 50,
|
||||||
|
"max_votes_amount": 42,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
|
|
||||||
|
def test_patch_equal_votes_amounts(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("assignmentpoll-detail", args=[self.poll.pk]),
|
||||||
|
{
|
||||||
|
"min_votes_amount": 42,
|
||||||
|
"max_votes_amount": 42,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
self.assertTrue(AssignmentPoll.objects.exists())
|
||||||
|
poll = AssignmentPoll.objects.get()
|
||||||
|
self.assertEqual(poll.min_votes_amount, 42)
|
||||||
|
self.assertEqual(poll.max_votes_amount, 42)
|
||||||
|
|
||||||
def test_patch_majority_method_state_not_created(self):
|
def test_patch_majority_method_state_not_created(self):
|
||||||
self.poll.state = 2
|
self.poll.state = 2
|
||||||
@ -1268,7 +1335,7 @@ class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
|
|||||||
|
|
||||||
def setup_for_multiple_votes(self):
|
def setup_for_multiple_votes(self):
|
||||||
self.poll.allow_multiple_votes_per_candidate = True
|
self.poll.allow_multiple_votes_per_candidate = True
|
||||||
self.poll.votes_amount = 3
|
self.poll.max_votes_amount = 3
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.add_candidate()
|
self.add_candidate()
|
||||||
|
|
||||||
@ -1334,7 +1401,7 @@ class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(option2.abstain, Decimal("0"))
|
self.assertEqual(option2.abstain, Decimal("0"))
|
||||||
|
|
||||||
def test_global_yes(self):
|
def test_global_yes(self):
|
||||||
self.poll.votes_amount = 2
|
self.poll.max_votes_amount = 2
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1362,7 +1429,7 @@ class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(AssignmentPoll.objects.get().amount_global_yes, None)
|
self.assertEqual(AssignmentPoll.objects.get().amount_global_yes, None)
|
||||||
|
|
||||||
def test_global_no(self):
|
def test_global_no(self):
|
||||||
self.poll.votes_amount = 2
|
self.poll.max_votes_amount = 2
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1390,7 +1457,7 @@ class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(AssignmentPoll.objects.get().amount_global_no, None)
|
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.max_votes_amount = 2
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1446,7 +1513,7 @@ class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(option2.no, Decimal("0"))
|
self.assertEqual(option2.no, Decimal("0"))
|
||||||
self.assertEqual(option2.abstain, Decimal("0"))
|
self.assertEqual(option2.abstain, Decimal("0"))
|
||||||
|
|
||||||
def test_multiple_votes_wrong_amount(self):
|
def test_multiple_votes_wrong_max_amount(self):
|
||||||
self.setup_for_multiple_votes()
|
self.setup_for_multiple_votes()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1457,6 +1524,19 @@ class VoteAssignmentPollNamedY(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
def test_multiple_votes_wrong_min_amount(self):
|
||||||
|
self.setup_for_multiple_votes()
|
||||||
|
self.poll.min_votes_amount = 2
|
||||||
|
self.poll.save()
|
||||||
|
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_too_many_options(self):
|
def test_too_many_options(self):
|
||||||
self.setup_for_multiple_votes()
|
self.setup_for_multiple_votes()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
@ -1583,7 +1663,7 @@ class VoteAssignmentPollNamedN(VoteAssignmentPollBaseTestClass):
|
|||||||
|
|
||||||
def setup_for_multiple_votes(self):
|
def setup_for_multiple_votes(self):
|
||||||
self.poll.allow_multiple_votes_per_candidate = True
|
self.poll.allow_multiple_votes_per_candidate = True
|
||||||
self.poll.votes_amount = 3
|
self.poll.max_votes_amount = 3
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.add_candidate()
|
self.add_candidate()
|
||||||
|
|
||||||
@ -1649,7 +1729,7 @@ class VoteAssignmentPollNamedN(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(option2.abstain, Decimal("0"))
|
self.assertEqual(option2.abstain, Decimal("0"))
|
||||||
|
|
||||||
def test_global_yes(self):
|
def test_global_yes(self):
|
||||||
self.poll.votes_amount = 2
|
self.poll.max_votes_amount = 2
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1677,7 +1757,7 @@ class VoteAssignmentPollNamedN(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(AssignmentPoll.objects.get().amount_global_yes, None)
|
self.assertEqual(AssignmentPoll.objects.get().amount_global_yes, None)
|
||||||
|
|
||||||
def test_global_no(self):
|
def test_global_no(self):
|
||||||
self.poll.votes_amount = 2
|
self.poll.max_votes_amount = 2
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1705,7 +1785,7 @@ class VoteAssignmentPollNamedN(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertEqual(AssignmentPoll.objects.get().amount_global_no, None)
|
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.max_votes_amount = 2
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -1896,6 +1976,12 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
type=BasePoll.TYPE_PSEUDOANONYMOUS,
|
type=BasePoll.TYPE_PSEUDOANONYMOUS,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def setup_for_multiple_votes(self):
|
||||||
|
self.poll.allow_multiple_votes_per_candidate = True
|
||||||
|
self.poll.max_votes_amount = 3
|
||||||
|
self.poll.save()
|
||||||
|
self.add_candidate()
|
||||||
|
|
||||||
def test_start_poll(self):
|
def test_start_poll(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-start", args=[self.poll.pk])
|
reverse("assignmentpoll-start", args=[self.poll.pk])
|
||||||
@ -2091,7 +2177,7 @@ class VoteAssignmentPollPseudoanonymousY(VoteAssignmentPollBaseTestClass):
|
|||||||
|
|
||||||
def setup_for_multiple_votes(self):
|
def setup_for_multiple_votes(self):
|
||||||
self.poll.allow_multiple_votes_per_candidate = True
|
self.poll.allow_multiple_votes_per_candidate = True
|
||||||
self.poll.votes_amount = 3
|
self.poll.max_votes_amount = 3
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.add_candidate()
|
self.add_candidate()
|
||||||
|
|
||||||
@ -2189,7 +2275,7 @@ class VoteAssignmentPollPseudoanonymousY(VoteAssignmentPollBaseTestClass):
|
|||||||
for vote in poll.get_votes():
|
for vote in poll.get_votes():
|
||||||
self.assertIsNone(vote.user)
|
self.assertIsNone(vote.user)
|
||||||
|
|
||||||
def test_multiple_votes_wrong_amount(self):
|
def test_multiple_votes_wrong_max_amount(self):
|
||||||
self.setup_for_multiple_votes()
|
self.setup_for_multiple_votes()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
@ -2200,6 +2286,19 @@ class VoteAssignmentPollPseudoanonymousY(VoteAssignmentPollBaseTestClass):
|
|||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
def test_multiple_votes_wrong_min_amount(self):
|
||||||
|
self.setup_for_multiple_votes()
|
||||||
|
self.poll.min_votes_amount = 2
|
||||||
|
self.poll.save()
|
||||||
|
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_too_many_options(self):
|
def test_too_many_options(self):
|
||||||
self.setup_for_multiple_votes()
|
self.setup_for_multiple_votes()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
@ -2326,7 +2425,7 @@ class VoteAssignmentPollPseudoanonymousN(VoteAssignmentPollBaseTestClass):
|
|||||||
|
|
||||||
def setup_for_multiple_votes(self):
|
def setup_for_multiple_votes(self):
|
||||||
self.poll.allow_multiple_votes_per_candidate = True
|
self.poll.allow_multiple_votes_per_candidate = True
|
||||||
self.poll.votes_amount = 3
|
self.poll.max_votes_amount = 3
|
||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.add_candidate()
|
self.add_candidate()
|
||||||
|
|
||||||
@ -2627,7 +2726,8 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"type": AssignmentPoll.TYPE_NAMED,
|
"type": AssignmentPoll.TYPE_NAMED,
|
||||||
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
"votes_amount": 1,
|
"min_votes_amount": 1,
|
||||||
|
"max_votes_amount": 1,
|
||||||
"votescast": "1.000000",
|
"votescast": "1.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
@ -2696,7 +2796,8 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"votes_amount": 1,
|
"min_votes_amount": 1,
|
||||||
|
"max_votes_amount": 1,
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
"user_has_voted_for_delegations": [],
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
@ -2751,7 +2852,8 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"state": 4,
|
"state": 4,
|
||||||
"title": self.poll.title,
|
"title": self.poll.title,
|
||||||
"type": "named",
|
"type": "named",
|
||||||
"votes_amount": 1,
|
"min_votes_amount": 1,
|
||||||
|
"max_votes_amount": 1,
|
||||||
"votescast": "1.000000",
|
"votescast": "1.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
@ -2827,7 +2929,8 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
"onehundred_percent_base": AssignmentPoll.PERCENT_BASE_CAST,
|
||||||
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
"majority_method": AssignmentPoll.MAJORITY_TWO_THIRDS,
|
||||||
"votes_amount": 1,
|
"min_votes_amount": 1,
|
||||||
|
"max_votes_amount": 1,
|
||||||
"votescast": "1.000000",
|
"votescast": "1.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
@ -2878,7 +2981,8 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"groups_id": [GROUP_DELEGATE_PK],
|
"groups_id": [GROUP_DELEGATE_PK],
|
||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"votes_amount": 1,
|
"min_votes_amount": 1,
|
||||||
|
"max_votes_amount": 1,
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
"user_has_voted_for_delegations": [],
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
@ -2933,7 +3037,8 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"state": 4,
|
"state": 4,
|
||||||
"title": self.poll.title,
|
"title": self.poll.title,
|
||||||
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
||||||
"votes_amount": 1,
|
"min_votes_amount": 1,
|
||||||
|
"max_votes_amount": 1,
|
||||||
"votescast": "1.000000",
|
"votescast": "1.000000",
|
||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
|
Loading…
Reference in New Issue
Block a user