Merge pull request #5541 from jsangmeister/vote-delegation
Add vote delegation
This commit is contained in:
commit
8d2a7f1b12
@ -7,8 +7,8 @@ import { HttpService } from 'app/core/core-services/http.service';
|
|||||||
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
|
||||||
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPoll } from 'app/shared/models/assignments/assignment-poll';
|
||||||
|
import { UserVote } from 'app/shared/models/poll/base-vote';
|
||||||
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
import { ViewAssignment } from 'app/site/assignments/models/view-assignment';
|
||||||
import { ViewAssignmentOption } from 'app/site/assignments/models/view-assignment-option';
|
import { ViewAssignmentOption } from 'app/site/assignments/models/view-assignment-option';
|
||||||
import { AssignmentPollTitleInformation, ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { AssignmentPollTitleInformation, ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
@ -97,7 +97,6 @@ export class AssignmentPollRepositoryService extends BasePollRepositoryService<
|
|||||||
viewModelStoreService: ViewModelStoreService,
|
viewModelStoreService: ViewModelStoreService,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
relationManager: RelationManagerService,
|
relationManager: RelationManagerService,
|
||||||
votingService: VotingService,
|
|
||||||
http: HttpService
|
http: HttpService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -110,7 +109,6 @@ export class AssignmentPollRepositoryService extends BasePollRepositoryService<
|
|||||||
AssignmentPoll,
|
AssignmentPoll,
|
||||||
AssignmentPollRelations,
|
AssignmentPollRelations,
|
||||||
{},
|
{},
|
||||||
votingService,
|
|
||||||
http
|
http
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -123,14 +121,11 @@ export class AssignmentPollRepositoryService extends BasePollRepositoryService<
|
|||||||
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
||||||
};
|
};
|
||||||
|
|
||||||
public vote(data: VotingData, poll_id: number): Promise<void> {
|
public vote(data: VotingData, poll_id: number, userId?: number): Promise<void> {
|
||||||
let requestData;
|
const requestData: UserVote = {
|
||||||
if (data.global) {
|
data: data.global ?? data.votes,
|
||||||
requestData = `"${data.global}"`;
|
user_id: userId ?? undefined
|
||||||
} else {
|
};
|
||||||
requestData = data.votes;
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.http.post(`/rest/assignments/assignment-poll/${poll_id}/vote/`, requestData);
|
return this.http.post(`/rest/assignments/assignment-poll/${poll_id}/vote/`, requestData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,9 +7,8 @@ import { HttpService } from 'app/core/core-services/http.service';
|
|||||||
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
import { RelationManagerService } from 'app/core/core-services/relation-manager.service';
|
||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
|
||||||
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
import { MotionPoll } from 'app/shared/models/motions/motion-poll';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { UserVote, VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
||||||
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
@ -66,7 +65,6 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
|||||||
viewModelStoreService: ViewModelStoreService,
|
viewModelStoreService: ViewModelStoreService,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
relationManager: RelationManagerService,
|
relationManager: RelationManagerService,
|
||||||
votingService: VotingService,
|
|
||||||
http: HttpService
|
http: HttpService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -79,7 +77,6 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
|||||||
MotionPoll,
|
MotionPoll,
|
||||||
MotionPollRelations,
|
MotionPollRelations,
|
||||||
{},
|
{},
|
||||||
votingService,
|
|
||||||
http
|
http
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -92,7 +89,11 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
|
|||||||
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
return this.translate.instant(plural ? 'Polls' : 'Poll');
|
||||||
};
|
};
|
||||||
|
|
||||||
public vote(vote: VoteValue, poll_id: number): Promise<void> {
|
public vote(vote: VoteValue, poll_id: number, userId?: number): Promise<void> {
|
||||||
return this.http.post(`/rest/motions/motion-poll/${poll_id}/vote/`, JSON.stringify(vote));
|
const requestData: UserVote = {
|
||||||
|
data: vote,
|
||||||
|
user_id: userId ?? undefined
|
||||||
|
};
|
||||||
|
return this.http.post(`/rest/motions/motion-poll/${poll_id}/vote/`, requestData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,18 @@ const UserRelations: RelationDefinition[] = [
|
|||||||
ownIdKey: 'groups_id',
|
ownIdKey: 'groups_id',
|
||||||
ownKey: 'groups',
|
ownKey: 'groups',
|
||||||
foreignViewModel: ViewGroup
|
foreignViewModel: ViewGroup
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2O',
|
||||||
|
ownIdKey: 'vote_delegated_to_id',
|
||||||
|
ownKey: 'voteDelegatedTo',
|
||||||
|
foreignViewModel: ViewUser
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2M',
|
||||||
|
ownIdKey: 'vote_delegated_from_users_id',
|
||||||
|
ownKey: 'voteDelegationsFrom',
|
||||||
|
foreignViewModel: ViewUser
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@ -255,6 +267,8 @@ export class UserRepositoryService extends BaseRepository<ViewUser, User, UserTi
|
|||||||
|
|
||||||
public async update(update: Partial<User>, viewModel: ViewUser): Promise<void> {
|
public async update(update: Partial<User>, viewModel: ViewUser): Promise<void> {
|
||||||
this.preventAlterationOnDemoUsers(viewModel);
|
this.preventAlterationOnDemoUsers(viewModel);
|
||||||
|
console.log('update: ', update);
|
||||||
|
|
||||||
return super.update(update, viewModel);
|
return super.update(update, viewModel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
|
|
||||||
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
import { OperatorService } from '../core-services/operator.service';
|
import { OperatorService } from '../core-services/operator.service';
|
||||||
|
|
||||||
export enum VotingError {
|
export enum VotingError {
|
||||||
@ -9,19 +12,21 @@ export enum VotingError {
|
|||||||
POLL_WRONG_TYPE,
|
POLL_WRONG_TYPE,
|
||||||
USER_HAS_NO_PERMISSION,
|
USER_HAS_NO_PERMISSION,
|
||||||
USER_IS_ANONYMOUS,
|
USER_IS_ANONYMOUS,
|
||||||
USER_NOT_PRESENT
|
USER_NOT_PRESENT,
|
||||||
|
USER_HAS_DELEGATED_RIGHT
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* TODO: It appears that the only message that makes sense for the user to see it the last one.
|
* TODO: It appears that the only message that makes sense for the user to see it the last one.
|
||||||
*/
|
*/
|
||||||
export const VotingErrorVerbose = {
|
const VotingErrorVerbose = {
|
||||||
1: "You can't vote on this poll right now because it's not in the 'Started' state.",
|
1: _("You can not vote on this poll right now because it is not in the 'Started' state."),
|
||||||
2: "You can't vote on this poll because its type is set to analog voting.",
|
2: _('You can not vote on this poll because its type is set to analog voting.'),
|
||||||
3: "You don't have permission to vote on this poll.",
|
3: _('You do not not have the permission to vote on this poll.'),
|
||||||
4: 'You have to be logged in to be able to vote.',
|
4: _('You have to be logged in to be able to vote.'),
|
||||||
5: 'You have to be present to vote on a poll.',
|
5: _('You have to be present to vote on a poll.'),
|
||||||
6: "You have already voted on this poll. You can't change your vote in a pseudoanonymous poll."
|
6: _('Your right to vote was delegated to another user.'),
|
||||||
|
7: _('You have already voted on this poll. You can not change your vote in a pseudoanonymous poll.')
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable({
|
@Injectable({
|
||||||
@ -33,8 +38,8 @@ export class VotingService {
|
|||||||
/**
|
/**
|
||||||
* checks whether the operator can vote on the given poll
|
* checks whether the operator can vote on the given poll
|
||||||
*/
|
*/
|
||||||
public canVote(poll: ViewBasePoll): boolean {
|
public canVote(poll: ViewBasePoll, user?: ViewUser): boolean {
|
||||||
const error = this.getVotePermissionError(poll);
|
const error = this.getVotePermissionError(poll, user);
|
||||||
return !error;
|
return !error;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -42,12 +47,11 @@ export class VotingService {
|
|||||||
* checks whether the operator can vote on the given poll
|
* checks whether the operator can vote on the given poll
|
||||||
* @returns null if no errors exist (= user can vote) or else a VotingError
|
* @returns null if no errors exist (= user can vote) or else a VotingError
|
||||||
*/
|
*/
|
||||||
public getVotePermissionError(poll: ViewBasePoll): VotingError | void {
|
public getVotePermissionError(poll: ViewBasePoll, user: ViewUser = this.operator.viewUser): VotingError | void {
|
||||||
if (this.operator.isAnonymous) {
|
if (this.operator.isAnonymous) {
|
||||||
return VotingError.USER_IS_ANONYMOUS;
|
return VotingError.USER_IS_ANONYMOUS;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = this.operator.user;
|
|
||||||
if (!poll.groups_id.intersect(user.groups_id).length) {
|
if (!poll.groups_id.intersect(user.groups_id).length) {
|
||||||
return VotingError.USER_HAS_NO_PERMISSION;
|
return VotingError.USER_HAS_NO_PERMISSION;
|
||||||
}
|
}
|
||||||
@ -57,13 +61,16 @@ export class VotingService {
|
|||||||
if (poll.state !== PollState.Started) {
|
if (poll.state !== PollState.Started) {
|
||||||
return VotingError.POLL_WRONG_STATE;
|
return VotingError.POLL_WRONG_STATE;
|
||||||
}
|
}
|
||||||
if (!user.is_present) {
|
if (!user.is_present && !this.operator.viewUser.canVoteFor(user)) {
|
||||||
return VotingError.USER_NOT_PRESENT;
|
return VotingError.USER_NOT_PRESENT;
|
||||||
}
|
}
|
||||||
|
if (user.isVoteRightDelegated && this.operator.user.id === user.id) {
|
||||||
|
return VotingError.USER_HAS_DELEGATED_RIGHT;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVotePermissionErrorVerbose(poll: ViewBasePoll): string | void {
|
public getVotePermissionErrorVerbose(poll: ViewBasePoll, user: ViewUser = this.operator.viewUser): string | void {
|
||||||
const error = this.getVotePermissionError(poll);
|
const error = this.getVotePermissionError(poll, user);
|
||||||
if (error) {
|
if (error) {
|
||||||
return VotingErrorVerbose[error];
|
return VotingErrorVerbose[error];
|
||||||
}
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
</button>
|
</button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
<ng-container *ngIf="!multiple && includeNone">
|
<ng-container *ngIf="!multiple && includeNone">
|
||||||
<mat-option>
|
<mat-option [value]="null">
|
||||||
{{ noneTitle | translate }}
|
{{ noneTitle | translate }}
|
||||||
</mat-option>
|
</mat-option>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
@ -56,8 +56,9 @@ export abstract class BasePoll<
|
|||||||
public votescast: number;
|
public votescast: number;
|
||||||
public groups_id: number[];
|
public groups_id: number[];
|
||||||
public majority_method: MajorityMethod;
|
public majority_method: MajorityMethod;
|
||||||
|
public voted_id: number[];
|
||||||
public user_has_voted: boolean;
|
public user_has_voted: boolean;
|
||||||
|
public user_has_voted_for_delegations: number[];
|
||||||
public pollmethod: PM;
|
public pollmethod: PM;
|
||||||
public onehundred_percent_base: PB;
|
public onehundred_percent_base: PB;
|
||||||
|
|
||||||
|
@ -16,6 +16,13 @@ export const GeneralValueVerbose = {
|
|||||||
votesabstain: 'Votes abstain'
|
votesabstain: 'Votes abstain'
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export interface UserVote {
|
||||||
|
// the voting payload is hard to describe.
|
||||||
|
// Can be "VoteValue" or any userID-Number sequence in combination with any VoteValue
|
||||||
|
data: Object;
|
||||||
|
user_id?: number;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class BaseVote<T = any> extends BaseDecimalModel<T> {
|
export abstract class BaseVote<T = any> extends BaseDecimalModel<T> {
|
||||||
public weight: number;
|
public weight: number;
|
||||||
public value: VoteValue;
|
public value: VoteValue;
|
||||||
|
@ -30,6 +30,8 @@ export class User extends BaseDecimalModel<User> {
|
|||||||
public is_present: boolean;
|
public is_present: boolean;
|
||||||
public is_committee: boolean;
|
public is_committee: boolean;
|
||||||
public email?: string;
|
public email?: string;
|
||||||
|
public vote_delegated_to_id: number;
|
||||||
|
public vote_delegated_from_users_id: number[];
|
||||||
public last_email_send?: string; // ISO datetime string
|
public last_email_send?: string; // ISO datetime string
|
||||||
public comment?: string;
|
public comment?: string;
|
||||||
public is_active?: boolean;
|
public is_active?: boolean;
|
||||||
@ -41,6 +43,10 @@ export class User extends BaseDecimalModel<User> {
|
|||||||
return this.vote_weight === 1;
|
return this.vote_weight === 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get isVoteRightDelegated(): boolean {
|
||||||
|
return !!this.vote_delegated_to_id;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(input?: Partial<User>) {
|
public constructor(input?: Partial<User>) {
|
||||||
super(User.COLLECTIONSTRING, input);
|
super(User.COLLECTIONSTRING, input);
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,27 @@
|
|||||||
<ng-container *ngIf="poll">
|
<ng-container *ngIf="poll">
|
||||||
<ng-container *ngIf="vmanager.canVote(poll) && !alreadyVoted && !deliveringVote; else cannotVote">
|
<!-- own voting -->
|
||||||
|
<ng-container [ngTemplateOutlet]="votingArea"></ng-container>
|
||||||
|
|
||||||
|
<!-- Delegations -->
|
||||||
|
<ng-container *ngIf="user.is_present">
|
||||||
|
<div class="assignment-vote-delegation" *ngFor="let delegation of delegations">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="votingArea"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
></ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #votingArea let-delegation="delegation">
|
||||||
|
<h4 *ngIf="delegation" class="assignment-delegation-title">
|
||||||
|
<span>{{ 'Vote delegation for' | translate }}</span>
|
||||||
|
<span> {{ delegation.getFullName() }}</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<ng-container *ngIf="canVote(delegation)">
|
||||||
<!-- Poll hint -->
|
<!-- Poll hint -->
|
||||||
<p *ngIf="pollHint">
|
<p *ngIf="pollHint">
|
||||||
<i>{{ pollHint }}</i>
|
<i>{{ pollHint }}</i>
|
||||||
@ -9,7 +31,7 @@
|
|||||||
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
|
<h4 *ngIf="poll.pollmethod === AssignmentPollMethod.Votes && poll.votes_amount > 1">
|
||||||
{{ 'Available votes' | translate }}:
|
{{ 'Available votes' | translate }}:
|
||||||
|
|
||||||
<b> {{ getVotesAvailable() }}/{{ poll.votes_amount }} </b>
|
<b> {{ getVotesAvailable(delegation) }}/{{ poll.votes_amount }} </b>
|
||||||
</h4>
|
</h4>
|
||||||
|
|
||||||
<!-- Options and Actions -->
|
<!-- Options and Actions -->
|
||||||
@ -36,14 +58,9 @@
|
|||||||
<button
|
<button
|
||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveSingleVote(option.id, action.vote)"
|
(click)="saveSingleVote(option.id, action.vote, delegation)"
|
||||||
[disabled]="deliveringVote"
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
[ngClass]="
|
[ngClass]="getActionButtonClass(action, option, delegation)"
|
||||||
voteRequestData.votes[option.id] === action.vote ||
|
|
||||||
voteRequestData.votes[option.id] === 1
|
|
||||||
? action.css
|
|
||||||
: ''
|
|
||||||
"
|
|
||||||
>
|
>
|
||||||
<mat-icon> {{ action.icon }}</mat-icon>
|
<mat-icon> {{ action.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -64,9 +81,9 @@
|
|||||||
<button
|
<button
|
||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveGlobalVote('N')"
|
(click)="saveGlobalVote('N', delegation)"
|
||||||
[ngClass]="voteRequestData.global === 'N' ? 'voted-no' : ''"
|
[ngClass]="getGlobalNoClass(delegation)"
|
||||||
[disabled]="deliveringVote"
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
>
|
>
|
||||||
<mat-icon> thumb_down </mat-icon>
|
<mat-icon> thumb_down </mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -79,8 +96,9 @@
|
|||||||
<button
|
<button
|
||||||
class="vote-button"
|
class="vote-button"
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveGlobalVote('A')"
|
(click)="saveGlobalVote('A', delegation)"
|
||||||
[ngClass]="voteRequestData.global === 'A' ? 'voted-abstain' : ''"
|
[ngClass]="getGlobalAbstainClass(delegation)"
|
||||||
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
>
|
>
|
||||||
<mat-icon> trip_origin</mat-icon>
|
<mat-icon> trip_origin</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -92,39 +110,47 @@
|
|||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Submit Vote -->
|
<!-- Submit Vote -->
|
||||||
<ng-container [ngTemplateOutlet]="sendNow"></ng-container>
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="sendNow"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
></ng-container>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Shows the permission error -->
|
<ng-container
|
||||||
<ng-container *ngIf="!vmanager.canVote(poll)">
|
*ngIf="!canVote(delegation)"
|
||||||
<span>{{ vmanager.getVotePermissionErrorVerbose(poll) | translate }}</span>
|
[ngTemplateOutlet]="cannotVote"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-container>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #cannotVote>
|
<ng-template #cannotVote let-delegation="delegation">
|
||||||
<div class="centered-button-wrapper">
|
<div class="centered-button-wrapper">
|
||||||
<div *ngIf="!deliveringVote">
|
<!-- Success -->
|
||||||
<mat-icon class="vote-submitted">
|
<div *ngIf="hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)">
|
||||||
check_circle
|
<mat-icon class="vote-submitted"> check_circle </mat-icon>
|
||||||
</mat-icon>
|
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Voting successful.' | translate }}</span>
|
<span>{{ 'Voting successful.' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="deliveringVote" class="submit-vote-indicator">
|
<!-- Delivering -->
|
||||||
|
<div *ngIf="isDeliveringVote(delegation)" class="submit-vote-indicator">
|
||||||
<mat-spinner class="small-spinner"></mat-spinner>
|
<mat-spinner class="small-spinner"></mat-spinner>
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Permission error error -->
|
||||||
|
<div *ngIf="!hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)">
|
||||||
|
<span>{{ getVotingError(delegation) | translate }}</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #sendNow>
|
<ng-template #sendNow let-delegation="delegation">
|
||||||
<div class="centered-button-wrapper">
|
<div class="centered-button-wrapper">
|
||||||
<button mat-flat-button color="accent" (click)="submitVote()">
|
<button mat-flat-button color="accent" (click)="submitVote(delegation)">
|
||||||
<mat-icon>
|
<mat-icon> how_to_vote </mat-icon>
|
||||||
how_to_vote
|
|
||||||
</mat-icon>
|
|
||||||
<span>
|
<span>
|
||||||
{{ 'Submit vote now' | translate }}
|
{{ 'Submit vote now' | translate }}
|
||||||
</span>
|
</span>
|
||||||
|
@ -7,6 +7,14 @@
|
|||||||
margin: 20px 0;
|
margin: 20px 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.assignment-vote-delegation {
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
.assignment-delegation-title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.yn-grid {
|
.yn-grid {
|
||||||
@extend %vote-grid-base;
|
@extend %vote-grid-base;
|
||||||
grid-template-areas:
|
grid-template-areas:
|
||||||
|
@ -7,24 +7,17 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import {
|
import {
|
||||||
AssignmentPollRepositoryService,
|
AssignmentPollRepositoryService,
|
||||||
GlobalVote,
|
GlobalVote
|
||||||
VotingData
|
|
||||||
} from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
} from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||||
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { PollType } from 'app/shared/models/poll/base-poll';
|
import { PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
|
import { ViewAssignmentOption } from 'app/site/assignments/models/view-assignment-option';
|
||||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
import { BasePollVoteComponentDirective } from 'app/site/polls/components/base-poll-vote.component';
|
import { BasePollVoteComponentDirective, VoteOption } from 'app/site/polls/components/base-poll-vote.component';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
// TODO: Duplicate
|
|
||||||
interface VoteActions {
|
|
||||||
vote: VoteValue;
|
|
||||||
css: string;
|
|
||||||
icon: string;
|
|
||||||
label: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-assignment-poll-vote',
|
selector: 'os-assignment-poll-vote',
|
||||||
@ -35,37 +28,60 @@ interface VoteActions {
|
|||||||
export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<ViewAssignmentPoll> implements OnInit {
|
export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<ViewAssignmentPoll> implements OnInit {
|
||||||
public AssignmentPollMethod = AssignmentPollMethod;
|
public AssignmentPollMethod = AssignmentPollMethod;
|
||||||
public PollType = PollType;
|
public PollType = PollType;
|
||||||
public voteActions: VoteActions[] = [];
|
public voteActions: VoteOption[] = [];
|
||||||
public voteRequestData: VotingData = {
|
|
||||||
votes: {}
|
public get pollHint(): string {
|
||||||
};
|
return this.poll.assignment.default_poll_description;
|
||||||
public alreadyVoted: boolean;
|
}
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
protected translate: TranslateService,
|
protected translate: TranslateService,
|
||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
public vmanager: VotingService,
|
votingService: VotingService,
|
||||||
private pollRepo: AssignmentPollRepositoryService,
|
private pollRepo: AssignmentPollRepositoryService,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, operator);
|
super(title, translate, matSnackbar, operator, votingService);
|
||||||
|
|
||||||
|
// observe user updates to refresh the view on dynamic changes
|
||||||
|
this.subscriptions.push(
|
||||||
|
operator.getViewUserObservable().subscribe(() => {
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ngOnInit(): void {
|
public ngOnInit(): void {
|
||||||
if (this.poll && !this.poll.user_has_voted) {
|
this.createVotingDataObjects();
|
||||||
this.alreadyVoted = false;
|
this.defineVoteOptions();
|
||||||
this.defineVoteOptions();
|
this.cd.markForCheck();
|
||||||
} else {
|
|
||||||
this.alreadyVoted = true;
|
|
||||||
this.cd.markForCheck();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public get pollHint(): string {
|
public getActionButtonClass(actions: VoteOption, option: ViewAssignmentOption, user: ViewUser = this.user): string {
|
||||||
return this.poll.assignment.default_poll_description;
|
if (
|
||||||
|
this.voteRequestData[user.id]?.votes[option.id] === actions.vote ||
|
||||||
|
this.voteRequestData[user.id]?.votes[option.id] === 1
|
||||||
|
) {
|
||||||
|
return actions.css;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalAbstainClass(user: ViewUser = this.user): string {
|
||||||
|
if (this.voteRequestData[user.id]?.global === 'A') {
|
||||||
|
return 'voted-abstain';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public getGlobalNoClass(user: ViewUser = this.user): string {
|
||||||
|
if (this.voteRequestData[user.id]?.global === 'N') {
|
||||||
|
return 'voted-no';
|
||||||
|
}
|
||||||
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
private defineVoteOptions(): void {
|
private defineVoteOptions(): void {
|
||||||
@ -76,7 +92,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
label: 'Yes'
|
label: 'Yes'
|
||||||
});
|
});
|
||||||
|
|
||||||
if (this.poll.pollmethod !== AssignmentPollMethod.Votes) {
|
if (this.poll?.pollmethod !== AssignmentPollMethod.Votes) {
|
||||||
this.voteActions.push({
|
this.voteActions.push({
|
||||||
vote: 'N',
|
vote: 'N',
|
||||||
css: 'voted-no',
|
css: 'voted-no',
|
||||||
@ -85,7 +101,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.poll.pollmethod === AssignmentPollMethod.YNA) {
|
if (this.poll?.pollmethod === AssignmentPollMethod.YNA) {
|
||||||
this.voteActions.push({
|
this.voteActions.push({
|
||||||
vote: 'A',
|
vote: 'A',
|
||||||
css: 'voted-abstain',
|
css: 'voted-abstain',
|
||||||
@ -95,40 +111,48 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVotesCount(): number {
|
public getVotesCount(user: ViewUser = this.user): number {
|
||||||
return Object.keys(this.voteRequestData.votes).filter(key => this.voteRequestData.votes[key]).length;
|
if (this.voteRequestData[user.id]) {
|
||||||
|
return Object.keys(this.voteRequestData[user.id].votes).filter(
|
||||||
|
key => this.voteRequestData[user.id].votes[key]
|
||||||
|
).length;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public getVotesAvailable(): number {
|
public getVotesAvailable(user: ViewUser = this.user): number {
|
||||||
return this.poll.votes_amount - this.getVotesCount();
|
return this.poll.votes_amount - this.getVotesCount(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
private isGlobalOptionSelected(): boolean {
|
private isGlobalOptionSelected(user: ViewUser = this.user): boolean {
|
||||||
return !!this.voteRequestData.global;
|
return !!this.voteRequestData[user.id]?.global;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async submitVote(): Promise<void> {
|
public async submitVote(user: ViewUser = this.user): Promise<void> {
|
||||||
const title = this.translate.instant('Submit selection now?');
|
const title = this.translate.instant('Submit selection now?');
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
||||||
const confirmed = await this.promptService.open(title, content);
|
const confirmed = await this.promptService.open(title, content);
|
||||||
if (confirmed) {
|
if (confirmed) {
|
||||||
this.deliveringVote = true;
|
this.deliveringVote[user.id] = true;
|
||||||
this.cd.markForCheck();
|
this.cd.markForCheck();
|
||||||
this.pollRepo
|
this.pollRepo
|
||||||
.vote(this.voteRequestData, this.poll.id)
|
.vote(this.voteRequestData[user.id], this.poll.id, user.id)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.alreadyVoted = true;
|
this.alreadyVoted[user.id] = true;
|
||||||
})
|
})
|
||||||
.catch(this.raiseError)
|
.catch(this.raiseError)
|
||||||
.finally(() => {
|
.finally(() => {
|
||||||
this.deliveringVote = false;
|
this.deliveringVote[user.id] = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveSingleVote(optionId: number, vote: VoteValue): void {
|
public saveSingleVote(optionId: number, vote: VoteValue, user: ViewUser = this.user): void {
|
||||||
if (this.isGlobalOptionSelected()) {
|
if (!this.voteRequestData[user.id]) {
|
||||||
delete this.voteRequestData.global;
|
throw new Error('The user for your voting request does not exist');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isGlobalOptionSelected(user)) {
|
||||||
|
delete this.voteRequestData[user.id].global;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.poll.pollmethod === AssignmentPollMethod.Votes) {
|
if (this.poll.pollmethod === AssignmentPollMethod.Votes) {
|
||||||
@ -138,10 +162,10 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
.reduce((o, n) => {
|
.reduce((o, n) => {
|
||||||
o[n] = 0;
|
o[n] = 0;
|
||||||
if (votesAmount === 1) {
|
if (votesAmount === 1) {
|
||||||
if (n === optionId && this.voteRequestData.votes[n] !== 1) {
|
if (n === optionId && this.voteRequestData[user.id].votes[n] !== 1) {
|
||||||
o[n] = 1;
|
o[n] = 1;
|
||||||
}
|
}
|
||||||
} else if ((n === optionId) !== (this.voteRequestData.votes[n] === 1)) {
|
} else if ((n === optionId) !== (this.voteRequestData[user.id].votes[n] === 1)) {
|
||||||
o[n] = 1;
|
o[n] = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -151,11 +175,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 <= votesAmount) {
|
||||||
this.voteRequestData.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() === votesAmount) {
|
if (this.getVotesCount(user) === votesAmount) {
|
||||||
this.submitVote();
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
this.raiseError(
|
this.raiseError(
|
||||||
@ -164,26 +188,29 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponentDirective<
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// YN/YNA
|
// YN/YNA
|
||||||
if (this.voteRequestData.votes[optionId] && this.voteRequestData.votes[optionId] === vote) {
|
if (
|
||||||
delete this.voteRequestData.votes[optionId];
|
this.voteRequestData[user.id].votes[optionId] &&
|
||||||
|
this.voteRequestData[user.id].votes[optionId] === vote
|
||||||
|
) {
|
||||||
|
delete this.voteRequestData[user.id].votes[optionId];
|
||||||
} else {
|
} else {
|
||||||
this.voteRequestData.votes[optionId] = vote;
|
this.voteRequestData[user.id].votes[optionId] = vote;
|
||||||
}
|
}
|
||||||
|
|
||||||
// if you filled out every option, try to send
|
// if you filled out every option, try to send
|
||||||
if (Object.keys(this.voteRequestData.votes).length === this.poll.options.length) {
|
if (Object.keys(this.voteRequestData[user.id].votes).length === this.poll.options.length) {
|
||||||
this.submitVote();
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public saveGlobalVote(globalVote: GlobalVote): void {
|
public saveGlobalVote(globalVote: GlobalVote, user: ViewUser = this.user): void {
|
||||||
this.voteRequestData.votes = {};
|
this.voteRequestData[user.id].votes = {};
|
||||||
if (this.voteRequestData.global && this.voteRequestData.global === globalVote) {
|
if (this.voteRequestData[user.id].global && this.voteRequestData[user.id].global === globalVote) {
|
||||||
delete this.voteRequestData.global;
|
delete this.voteRequestData[user.id].global;
|
||||||
} else {
|
} else {
|
||||||
this.voteRequestData.global = globalVote;
|
this.voteRequestData[user.id].global = globalVote;
|
||||||
this.submitVote();
|
this.submitVote(user);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -72,6 +72,8 @@ export class AssignmentPollComponent
|
|||||||
this.descriptionForm = this.formBuilder.group({
|
this.descriptionForm = this.formBuilder.group({
|
||||||
description: this.poll ? this.poll.description : ''
|
description: this.poll ? this.poll.description : ''
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log('the poll: ', this.poll);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,10 +4,10 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div *ngIf="poll.pollClassType === 'motion'">
|
<div *ngIf="poll.pollClassType === 'motion'">
|
||||||
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
<os-motion-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-motion-poll-vote>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="poll.pollClassType === 'assignment'">
|
<div *ngIf="poll.pollClassType === 'assignment'">
|
||||||
<os-assignment-poll-vote [poll]="poll"></os-assignment-poll-vote>
|
<os-assignment-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-assignment-poll-vote>
|
||||||
</div>
|
</div>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
|
@ -1,12 +1,34 @@
|
|||||||
<ng-container *ngIf="poll && !poll.user_has_voted; else userHasVotes">
|
<ng-container *ngIf="poll">
|
||||||
<div *ngIf="vmanager.canVote(poll) && !deliveringVote" class="vote-button-grid">
|
<!-- own voting -->
|
||||||
|
<ng-container [ngTemplateOutlet]="votingArea"></ng-container>
|
||||||
|
|
||||||
|
<!-- Delegations -->
|
||||||
|
<ng-container *ngIf="user.is_present">
|
||||||
|
<div class="motion-vote-delegation" *ngFor="let delegation of delegations">
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<ng-container
|
||||||
|
[ngTemplateOutlet]="votingArea"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
></ng-container>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<ng-template #votingArea let-delegation="delegation">
|
||||||
|
<h4 *ngIf="delegation" class="motion-delegation-title">
|
||||||
|
<span>{{ 'Vote delegation for' | translate }}</span>
|
||||||
|
<span> {{ delegation.getFullName() }}</span>
|
||||||
|
</h4>
|
||||||
|
|
||||||
|
<div *ngIf="canVote(delegation)" class="vote-button-grid">
|
||||||
<!-- Voting -->
|
<!-- Voting -->
|
||||||
<div class="vote-button" *ngFor="let option of voteOptions">
|
<div class="vote-button" *ngFor="let option of voteOptions">
|
||||||
<button
|
<button
|
||||||
mat-raised-button
|
mat-raised-button
|
||||||
(click)="saveVote(option.vote)"
|
(click)="saveVote(option.vote, delegation)"
|
||||||
[ngClass]="currentVote && currentVote.vote === option.vote ? option.css : ''"
|
[ngClass]="getActionButtonClass(option, delegation)"
|
||||||
[disabled]="deliveringVote"
|
[disabled]="isDeliveringVote(delegation)"
|
||||||
>
|
>
|
||||||
<mat-icon> {{ option.icon }}</mat-icon>
|
<mat-icon> {{ option.icon }}</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
@ -14,21 +36,33 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="deliveringVote" class="submit-vote-indicator">
|
<ng-container
|
||||||
|
*ngIf="!canVote(delegation)"
|
||||||
|
[ngTemplateOutlet]="cannotVote"
|
||||||
|
[ngTemplateOutletContext]="{ delegation: delegation }"
|
||||||
|
>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<!-- Delivering -->
|
||||||
|
<div *ngIf="isDeliveringVote(delegation)" class="submit-vote-indicator">
|
||||||
<mat-spinner class="small-spinner"></mat-spinner>
|
<mat-spinner class="small-spinner"></mat-spinner>
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
<span>{{ 'Delivering vote... Please wait!' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-template>
|
||||||
|
|
||||||
<ng-template #userHasVotes>
|
<ng-template #cannotVote let-delegation="delegation">
|
||||||
<div class="user-has-voted">
|
<!-- Success -->
|
||||||
|
<div *ngIf="hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)" class="user-has-voted">
|
||||||
<div>
|
<div>
|
||||||
<mat-icon class="vote-submitted">
|
<mat-icon class="vote-submitted"> check_circle </mat-icon>
|
||||||
check_circle
|
|
||||||
</mat-icon>
|
|
||||||
<br />
|
<br />
|
||||||
<span>{{ 'Voting successful.' | translate }}</span>
|
<span>{{ 'Voting successful.' | translate }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Error -->
|
||||||
|
<div *ngIf="!hasAlreadyVoted(delegation) && !isDeliveringVote(delegation)">
|
||||||
|
<span>{{ getVotingError(delegation) | translate }}</span>
|
||||||
|
</div>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
@ -8,6 +8,14 @@
|
|||||||
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
grid-template-columns: repeat(auto-fit, minmax(100px, 1fr));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.motion-vote-delegation {
|
||||||
|
margin-top: 1em;
|
||||||
|
|
||||||
|
.motion-delegation-title {
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.submit-vote-indicator {
|
.submit-vote-indicator {
|
||||||
margin-top: 1em;
|
margin-top: 1em;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
@ -26,6 +34,10 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.mat-divider-horizontal {
|
||||||
|
position: initial;
|
||||||
|
}
|
||||||
|
|
||||||
.user-has-voted {
|
.user-has-voted {
|
||||||
display: flex;
|
display: flex;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { ChangeDetectionStrategy, ChangeDetectorRef, Component } from '@angular/core';
|
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, OnInit } from '@angular/core';
|
||||||
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';
|
||||||
|
|
||||||
@ -10,14 +10,8 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
import { VotingService } from 'app/core/ui-services/voting.service';
|
||||||
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { BasePollVoteComponentDirective } from 'app/site/polls/components/base-poll-vote.component';
|
import { BasePollVoteComponentDirective, VoteOption } from 'app/site/polls/components/base-poll-vote.component';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
interface VoteOption {
|
|
||||||
vote?: VoteValue;
|
|
||||||
css?: string;
|
|
||||||
icon?: string;
|
|
||||||
label?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-motion-poll-vote',
|
selector: 'os-motion-poll-vote',
|
||||||
@ -25,8 +19,7 @@ interface VoteOption {
|
|||||||
styleUrls: ['./motion-poll-vote.component.scss'],
|
styleUrls: ['./motion-poll-vote.component.scss'],
|
||||||
changeDetection: ChangeDetectionStrategy.OnPush
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
})
|
})
|
||||||
export class MotionPollVoteComponent extends BasePollVoteComponentDirective<ViewMotionPoll> {
|
export class MotionPollVoteComponent extends BasePollVoteComponentDirective<ViewMotionPoll> implements OnInit {
|
||||||
public currentVote: VoteOption = {};
|
|
||||||
public voteOptions: VoteOption[] = [
|
public voteOptions: VoteOption[] = [
|
||||||
{
|
{
|
||||||
vote: 'Y',
|
vote: 'Y',
|
||||||
@ -53,30 +46,56 @@ export class MotionPollVoteComponent extends BasePollVoteComponentDirective<View
|
|||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
operator: OperatorService,
|
operator: OperatorService,
|
||||||
public vmanager: VotingService,
|
public votingService: VotingService,
|
||||||
private pollRepo: MotionPollRepositoryService,
|
private pollRepo: MotionPollRepositoryService,
|
||||||
private promptService: PromptService,
|
private promptService: PromptService,
|
||||||
private cd: ChangeDetectorRef
|
private cd: ChangeDetectorRef
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, operator);
|
super(title, translate, matSnackbar, operator, votingService);
|
||||||
|
|
||||||
|
// observe user updates to refresh the view on dynamic changes
|
||||||
|
this.subscriptions.push(
|
||||||
|
operator.getViewUserObservable().subscribe(() => {
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async saveVote(vote: VoteValue): Promise<void> {
|
public ngOnInit(): void {
|
||||||
this.currentVote.vote = vote;
|
this.createVotingDataObjects();
|
||||||
const title = this.translate.instant('Submit selection now?');
|
this.cd.markForCheck();
|
||||||
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
}
|
||||||
const confirmed = await this.promptService.open(title, content);
|
|
||||||
|
|
||||||
if (confirmed) {
|
public getActionButtonClass(voteOption: VoteOption, user: ViewUser = this.user): string {
|
||||||
this.deliveringVote = true;
|
if (this.voteRequestData[user.id]?.vote === voteOption.vote) {
|
||||||
this.cd.markForCheck();
|
return voteOption.css;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
this.pollRepo
|
public async saveVote(vote: VoteValue, user: ViewUser = this.user): Promise<void> {
|
||||||
.vote(vote, this.poll.id)
|
if (this.voteRequestData[user.id]) {
|
||||||
.catch(this.raiseError)
|
this.voteRequestData[user.id].vote = vote;
|
||||||
.finally(() => {
|
|
||||||
this.deliveringVote = false;
|
const title = this.translate.instant('Submit selection now?');
|
||||||
});
|
const content = this.translate.instant('Your decision cannot be changed afterwards.');
|
||||||
|
const confirmed = await this.promptService.open(title, content);
|
||||||
|
|
||||||
|
if (confirmed) {
|
||||||
|
this.deliveringVote[user.id] = true;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
|
||||||
|
this.pollRepo
|
||||||
|
.vote(vote, this.poll.id, user.id)
|
||||||
|
.then(() => {
|
||||||
|
this.alreadyVoted[user.id] = true;
|
||||||
|
})
|
||||||
|
.catch(this.raiseError)
|
||||||
|
.finally(() => {
|
||||||
|
this.deliveringVote[user.id] = false;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -60,7 +60,7 @@
|
|||||||
<div *osPerms="'motions.can_manage_polls'; and: poll && poll.isStarted">
|
<div *osPerms="'motions.can_manage_polls'; and: poll && poll.isStarted">
|
||||||
<os-poll-progress [poll]="poll"></os-poll-progress>
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
</div>
|
</div>
|
||||||
<os-motion-poll-vote [poll]="poll"></os-motion-poll-vote>
|
<os-motion-poll-vote [poll]="poll" *ngIf="poll.canBeVotedFor()"></os-motion-poll-vote>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- Detail link -->
|
<!-- Detail link -->
|
||||||
|
@ -5,11 +5,20 @@ import { Title } from '@angular/platform-browser';
|
|||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
|
||||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||||
import { VotingError } from 'app/core/ui-services/voting.service';
|
import { VotingData } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
|
import { VotingError, VotingService } from 'app/core/ui-services/voting.service';
|
||||||
|
import { VoteValue } from 'app/shared/models/poll/base-vote';
|
||||||
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
import { BaseViewComponentDirective } from 'app/site/base/base-view';
|
||||||
import { ViewUser } from 'app/site/users/models/view-user';
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
|
export interface VoteOption {
|
||||||
|
vote?: VoteValue;
|
||||||
|
css?: string;
|
||||||
|
icon?: string;
|
||||||
|
label?: string;
|
||||||
|
}
|
||||||
|
|
||||||
@Directive()
|
@Directive()
|
||||||
export abstract class BasePollVoteComponentDirective<V extends ViewBasePoll> extends BaseViewComponentDirective {
|
export abstract class BasePollVoteComponentDirective<V extends ViewBasePoll> extends BaseViewComponentDirective {
|
||||||
@Input()
|
@Input()
|
||||||
@ -17,22 +26,71 @@ export abstract class BasePollVoteComponentDirective<V extends ViewBasePoll> ext
|
|||||||
|
|
||||||
public votingErrors = VotingError;
|
public votingErrors = VotingError;
|
||||||
|
|
||||||
public deliveringVote = false;
|
protected voteRequestData = {};
|
||||||
|
|
||||||
|
protected alreadyVoted = {};
|
||||||
|
|
||||||
|
protected deliveringVote = {};
|
||||||
|
|
||||||
protected user: ViewUser;
|
protected user: ViewUser;
|
||||||
|
|
||||||
|
protected delegations: ViewUser[];
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
matSnackbar: MatSnackBar,
|
matSnackbar: MatSnackBar,
|
||||||
protected operator: OperatorService
|
operator: OperatorService,
|
||||||
|
protected votingService: VotingService
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar);
|
super(title, translate, matSnackbar);
|
||||||
|
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
this.operator.getViewUserObservable().subscribe(user => {
|
operator.getViewUserObservable().subscribe(user => {
|
||||||
this.user = user;
|
if (user) {
|
||||||
|
this.user = user;
|
||||||
|
this.delegations = user.voteDelegationsFrom;
|
||||||
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected createVotingDataObjects(): void {
|
||||||
|
if (this.user) {
|
||||||
|
this.voteRequestData[this.user.id] = {
|
||||||
|
votes: {}
|
||||||
|
} as VotingData;
|
||||||
|
this.alreadyVoted[this.user.id] = this.poll.user_has_voted;
|
||||||
|
this.deliveringVote[this.user.id] = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.delegations) {
|
||||||
|
for (const delegation of this.delegations) {
|
||||||
|
this.voteRequestData[delegation.id] = {
|
||||||
|
votes: {}
|
||||||
|
} as VotingData;
|
||||||
|
this.alreadyVoted[delegation.id] = this.poll.hasVotedId(delegation.id);
|
||||||
|
this.deliveringVote[delegation.id] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isDeliveringVote(user: ViewUser = this.user): boolean {
|
||||||
|
return this.deliveringVote[user.id] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasAlreadyVoted(user: ViewUser = this.user): boolean {
|
||||||
|
return this.alreadyVoted[user.id] === true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public canVote(user: ViewUser = this.user): boolean {
|
||||||
|
return (
|
||||||
|
this.votingService.canVote(this.poll, user) && !this.isDeliveringVote(user) && !this.hasAlreadyVoted(user)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public getVotingError(user: ViewUser = this.user): string | void {
|
||||||
|
console.log('error ', this.votingService.getVotePermissionErrorVerbose(this.poll, user));
|
||||||
|
return this.votingService.getVotePermissionErrorVerbose(this.poll, user);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,8 +39,15 @@ export class PollProgressComponent extends BaseViewComponentDirective implements
|
|||||||
.getViewModelListObservable()
|
.getViewModelListObservable()
|
||||||
.pipe(
|
.pipe(
|
||||||
map(users =>
|
map(users =>
|
||||||
|
/**
|
||||||
|
* Filter the users who would be able to vote:
|
||||||
|
* They are present or have their right to vote delegated
|
||||||
|
* They are in one of the voting groups
|
||||||
|
*/
|
||||||
users.filter(
|
users.filter(
|
||||||
user => user.is_present && this.poll.groups_id.intersect(user.groups_id).length
|
user =>
|
||||||
|
(user.is_present || user.isVoteRightDelegated) &&
|
||||||
|
this.poll.groups_id.intersect(user.groups_id).length
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
@ -114,6 +114,10 @@ export abstract class ViewBasePoll<
|
|||||||
|
|
||||||
public canBeVotedFor: () => boolean;
|
public canBeVotedFor: () => boolean;
|
||||||
|
|
||||||
|
public hasVotedId(userId: number): boolean {
|
||||||
|
return this.user_has_voted_for_delegations?.includes(userId);
|
||||||
|
}
|
||||||
|
|
||||||
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
public abstract getSlide(): ProjectorElementBuildDeskriptor;
|
||||||
|
|
||||||
public abstract getContentObject(): BaseViewModel;
|
public abstract getContentObject(): BaseViewModel;
|
||||||
|
@ -8,7 +8,6 @@ import { RelationManagerService } from 'app/core/core-services/relation-manager.
|
|||||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { BaseRepository, NestedModelDescriptors } from 'app/core/repositories/base-repository';
|
import { BaseRepository, NestedModelDescriptors } from 'app/core/repositories/base-repository';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
|
||||||
import { ModelConstructor } from 'app/shared/models/base/base-model';
|
import { ModelConstructor } from 'app/shared/models/base/base-model';
|
||||||
import { BasePoll, PollState } from 'app/shared/models/poll/base-poll';
|
import { BasePoll, PollState } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewModel, TitleInformation } from 'app/site/base/base-view-model';
|
import { BaseViewModel, TitleInformation } from 'app/site/base/base-view-model';
|
||||||
@ -30,7 +29,6 @@ export abstract class BasePollRepositoryService<
|
|||||||
protected baseModelCtor: ModelConstructor<M>,
|
protected baseModelCtor: ModelConstructor<M>,
|
||||||
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = [],
|
protected relationDefinitions: RelationDefinition<BaseViewModel>[] = [],
|
||||||
protected nestedModelDescriptors: NestedModelDescriptors = {},
|
protected nestedModelDescriptors: NestedModelDescriptors = {},
|
||||||
private votingService: VotingService,
|
|
||||||
protected http: HttpService
|
protected http: HttpService
|
||||||
) {
|
) {
|
||||||
super(
|
super(
|
||||||
@ -53,7 +51,7 @@ export abstract class BasePollRepositoryService<
|
|||||||
protected createViewModelWithTitles(model: M): V {
|
protected createViewModelWithTitles(model: M): V {
|
||||||
const viewModel = super.createViewModelWithTitles(model);
|
const viewModel = super.createViewModelWithTitles(model);
|
||||||
Object.defineProperty(viewModel, 'canBeVotedFor', {
|
Object.defineProperty(viewModel, 'canBeVotedFor', {
|
||||||
value: () => this.votingService.canVote(viewModel)
|
value: () => viewModel.isStarted
|
||||||
});
|
});
|
||||||
return viewModel;
|
return viewModel;
|
||||||
}
|
}
|
||||||
|
@ -164,6 +164,16 @@
|
|||||||
[inputListValues]="groups"
|
[inputListValues]="groups"
|
||||||
></os-search-value-selector>
|
></os-search-value-selector>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
|
<!-- Delegate Vote -->
|
||||||
|
<mat-form-field>
|
||||||
|
<os-search-value-selector
|
||||||
|
formControlName="vote_delegated_from_users_id"
|
||||||
|
[multiple]="true"
|
||||||
|
placeholder="{{ 'Transferred voting rights from' | translate }}"
|
||||||
|
[inputListValues]="users"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isAllowed('manage')">
|
<div *ngIf="isAllowed('manage')">
|
||||||
@ -303,6 +313,18 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div *ngIf="isAllowed('manage')">
|
<div *ngIf="isAllowed('manage')">
|
||||||
|
<!-- Own Vote delegations -->
|
||||||
|
<div *ngIf="user.voteDelegatedTo">
|
||||||
|
<h4>{{ 'Vote right delegated to:' | translate }}</h4>
|
||||||
|
<span>{{ user.voteDelegatedTo }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Received Vote delegations -->
|
||||||
|
<div *ngIf="user.voteDelegationsFrom && user.voteDelegationsFrom.length">
|
||||||
|
<h4>{{ 'Vote delegations from' | translate }}</h4>
|
||||||
|
<span>{{ user.voteDelegationsFrom }}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Vote weight -->
|
<!-- Vote weight -->
|
||||||
<div *ngIf="user.vote_weight && showVoteWeight">
|
<div *ngIf="user.vote_weight && showVoteWeight">
|
||||||
<h4>{{ 'Vote weight' | translate }}</h4>
|
<h4>{{ 'Vote weight' | translate }}</h4>
|
||||||
|
@ -71,6 +71,8 @@ export class UserDetailComponent extends BaseViewComponentDirective implements O
|
|||||||
*/
|
*/
|
||||||
public readonly groups: BehaviorSubject<ViewGroup[]> = new BehaviorSubject<ViewGroup[]>([]);
|
public readonly groups: BehaviorSubject<ViewGroup[]> = new BehaviorSubject<ViewGroup[]>([]);
|
||||||
|
|
||||||
|
public readonly users: BehaviorSubject<ViewUser[]> = new BehaviorSubject<ViewUser[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Hold the list of genders (sexes) publicly to dynamically iterate in the view
|
* Hold the list of genders (sexes) publicly to dynamically iterate in the view
|
||||||
*/
|
*/
|
||||||
@ -124,6 +126,7 @@ export class UserDetailComponent extends BaseViewComponentDirective implements O
|
|||||||
.subscribe(active => (this.isVoteWeightActive = active));
|
.subscribe(active => (this.isVoteWeightActive = active));
|
||||||
|
|
||||||
this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
|
this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
|
||||||
|
this.users = this.repo.getViewModelListBehaviorSubject();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,6 +176,7 @@ export class UserDetailComponent extends BaseViewComponentDirective implements O
|
|||||||
vote_weight: [],
|
vote_weight: [],
|
||||||
about_me: [''],
|
about_me: [''],
|
||||||
groups_id: [''],
|
groups_id: [''],
|
||||||
|
vote_delegated_from_users_id: [''],
|
||||||
is_present: [true],
|
is_present: [true],
|
||||||
is_committee: [false],
|
is_committee: [false],
|
||||||
email: ['', Validators.email],
|
email: ['', Validators.email],
|
||||||
|
@ -62,7 +62,7 @@
|
|||||||
>
|
>
|
||||||
<div class="groupsCell">
|
<div class="groupsCell">
|
||||||
<div *ngIf="user.groups && user.groups.length">
|
<div *ngIf="user.groups && user.groups.length">
|
||||||
<os-icon-container icon="people">
|
<os-icon-container icon="people" noWrap="true">
|
||||||
<span *ngFor="let group of user.groups; let last = last">
|
<span *ngFor="let group of user.groups; let last = last">
|
||||||
{{ group.getTitle() | translate }}<span *ngIf="!last">,</span>
|
{{ group.getTitle() | translate }}<span *ngIf="!last">,</span>
|
||||||
</span>
|
</span>
|
||||||
@ -74,6 +74,10 @@
|
|||||||
<div *ngIf="user.number" class="spacer-top-5">
|
<div *ngIf="user.number" class="spacer-top-5">
|
||||||
<os-icon-container icon="perm_identity">{{ user.number }}</os-icon-container>
|
<os-icon-container icon="perm_identity">{{ user.number }}</os-icon-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="user.vote_delegated_to_id" class="spacer-top-5">
|
||||||
|
<os-icon-container icon="forward" noWrap="true">{{ user.voteDelegatedTo }}</os-icon-container>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -90,9 +94,7 @@
|
|||||||
</mat-icon>
|
</mat-icon>
|
||||||
|
|
||||||
<!-- Has comment indicator -->
|
<!-- Has comment indicator -->
|
||||||
<mat-icon inline *ngIf="!!user.comment" matTooltip="{{ user.comment }}">
|
<mat-icon inline *ngIf="!!user.comment" matTooltip="{{ user.comment }}"> comment </mat-icon>
|
||||||
comment
|
|
||||||
</mat-icon>
|
|
||||||
|
|
||||||
<os-icon-container *ngIf="user.isSamlUser" icon="device_hub"
|
<os-icon-container *ngIf="user.isSamlUser" icon="device_hub"
|
||||||
><span>{{ 'Is SAML user' | translate }}</span></os-icon-container
|
><span>{{ 'Is SAML user' | translate }}</span></os-icon-container
|
||||||
@ -270,6 +272,14 @@
|
|||||||
</mat-option>
|
</mat-option>
|
||||||
</mat-select>
|
</mat-select>
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
<mat-form-field>
|
||||||
|
<os-search-value-selector
|
||||||
|
[(ngModel)]="infoDialog.vote_delegated_from_users_id"
|
||||||
|
[multiple]="true"
|
||||||
|
placeholder="{{ 'Transferred voting rights from' | translate }}"
|
||||||
|
[inputListValues]="users"
|
||||||
|
></os-search-value-selector>
|
||||||
|
</mat-form-field>
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select placeholder="{{ 'Gender' | translate }}" [(ngModel)]="infoDialog.gender">
|
<mat-select placeholder="{{ 'Gender' | translate }}" [(ngModel)]="infoDialog.gender">
|
||||||
<mat-option>-</mat-option>
|
<mat-option>-</mat-option>
|
||||||
@ -300,6 +310,7 @@
|
|||||||
color="accent"
|
color="accent"
|
||||||
[mat-dialog-close]="{
|
[mat-dialog-close]="{
|
||||||
groups_id: infoDialog.groups_id,
|
groups_id: infoDialog.groups_id,
|
||||||
|
vote_delegated_from_users_id: infoDialog.vote_delegated_from_users_id,
|
||||||
gender: infoDialog.gender,
|
gender: infoDialog.gender,
|
||||||
number: infoDialog.number,
|
number: infoDialog.number,
|
||||||
structure_level: infoDialog.structure_level
|
structure_level: infoDialog.structure_level
|
||||||
|
@ -7,6 +7,7 @@ import { ActivatedRoute, Router } from '@angular/router';
|
|||||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||||
|
import { BehaviorSubject } from 'rxjs';
|
||||||
|
|
||||||
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
import { OperatorService, Permission } from 'app/core/core-services/operator.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
@ -55,6 +56,11 @@ interface InfoDialog {
|
|||||||
* Structure level for one user.
|
* Structure level for one user.
|
||||||
*/
|
*/
|
||||||
structure_level: string;
|
structure_level: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Transfer voting rights
|
||||||
|
*/
|
||||||
|
vote_delegated_from_users_id: number[];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -82,6 +88,8 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
*/
|
*/
|
||||||
public groups: ViewGroup[];
|
public groups: ViewGroup[];
|
||||||
|
|
||||||
|
public readonly users: BehaviorSubject<ViewUser[]> = new BehaviorSubject<ViewUser[]>([]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The list of all genders.
|
* The list of all genders.
|
||||||
*/
|
*/
|
||||||
@ -187,6 +195,7 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
|
|
||||||
// enable multiSelect for this listView
|
// enable multiSelect for this listView
|
||||||
this.canMultiSelect = true;
|
this.canMultiSelect = true;
|
||||||
|
this.users = this.repo.getViewModelListBehaviorSubject();
|
||||||
config.get<boolean>('users_enable_presence_view').subscribe(state => (this._presenceViewConfigured = state));
|
config.get<boolean>('users_enable_presence_view').subscribe(state => (this._presenceViewConfigured = state));
|
||||||
config.get<boolean>('users_activate_vote_weight').subscribe(active => (this.isVoteWeightActive = active));
|
config.get<boolean>('users_activate_vote_weight').subscribe(active => (this.isVoteWeightActive = active));
|
||||||
config.get<boolean>(this.selfPresentConfStr).subscribe(allowed => (this.allowSelfSetPresent = allowed));
|
config.get<boolean>(this.selfPresentConfStr).subscribe(allowed => (this.allowSelfSetPresent = allowed));
|
||||||
@ -203,9 +212,12 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
|
|
||||||
// Initialize the groups
|
// Initialize the groups
|
||||||
this.groups = this.groupRepo.getViewModelList().filter(group => group.id !== 1);
|
this.groups = this.groupRepo.getViewModelList().filter(group => group.id !== 1);
|
||||||
this.groupRepo
|
|
||||||
.getViewModelListObservable()
|
this.subscriptions.push(
|
||||||
.subscribe(groups => (this.groups = groups.filter(group => group.id !== 1)));
|
this.groupRepo
|
||||||
|
.getViewModelListObservable()
|
||||||
|
.subscribe(groups => (this.groups = groups.filter(group => group.id !== 1)))
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -242,7 +254,8 @@ export class UserListComponent extends BaseListViewComponent<ViewUser> implement
|
|||||||
groups_id: user.groups_id,
|
groups_id: user.groups_id,
|
||||||
gender: user.gender,
|
gender: user.gender,
|
||||||
structure_level: user.structure_level,
|
structure_level: user.structure_level,
|
||||||
number: user.number
|
number: user.number,
|
||||||
|
vote_delegated_from_users_id: user.vote_delegated_from_users_id
|
||||||
};
|
};
|
||||||
|
|
||||||
const dialogRef = this.dialog.open(this.userInfoDialog, infoDialogSettings);
|
const dialogRef = this.dialog.open(this.userInfoDialog, infoDialogSettings);
|
||||||
|
@ -82,9 +82,15 @@ export class ViewUser extends BaseProjectableViewModel<User> implements UserTitl
|
|||||||
getDialogTitle: () => this.getTitle()
|
getDialogTitle: () => this.getTitle()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public canVoteFor(user: ViewUser): boolean {
|
||||||
|
return this.vote_delegated_from_users_id.includes(user.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
interface IUserRelations {
|
interface IUserRelations {
|
||||||
groups: ViewGroup[];
|
groups: ViewGroup[];
|
||||||
|
voteDelegatedTo: ViewUser;
|
||||||
|
voteDelegationsFrom: ViewUser[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewUser extends User, IUserRelations {}
|
export interface ViewUser extends User, IUserRelations {}
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 2.2.16 on 2020-09-10 11:02
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("assignments", "0014_remove_deprecated_slides"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="assignmentvote",
|
||||||
|
name="delegated_user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="assignmentvote_delegated_votes",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -289,7 +289,7 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
poll.db_amount_global_no = Decimal(0)
|
poll.db_amount_global_no = Decimal(0)
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
def handle_analog_vote(self, data, poll, user):
|
def handle_analog_vote(self, data, poll):
|
||||||
for field in ["votesvalid", "votesinvalid", "votescast"]:
|
for field in ["votesvalid", "votesinvalid", "votescast"]:
|
||||||
setattr(poll, field, data[field])
|
setattr(poll, field, data[field])
|
||||||
|
|
||||||
@ -336,7 +336,7 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
|
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
def validate_vote_data(self, data, poll, user):
|
def validate_vote_data(self, data, poll):
|
||||||
"""
|
"""
|
||||||
Request data:
|
Request data:
|
||||||
analog:
|
analog:
|
||||||
@ -478,10 +478,12 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
|
|
||||||
options_data = data
|
options_data = data
|
||||||
|
|
||||||
def create_votes_type_votes(self, data, poll, vote_weight, vote_user):
|
def create_votes_type_votes(self, data, poll, vote_weight, vote_user, request_user):
|
||||||
"""
|
"""
|
||||||
Helper function for handle_(named|pseudoanonymous)_vote
|
Helper function for handle_(named|pseudoanonymous)_vote
|
||||||
Assumes data is already validated
|
Assumes data is already validated
|
||||||
|
vote_user is the user whose vote is given
|
||||||
|
request_user is the user who gives the vote, may be a delegate
|
||||||
"""
|
"""
|
||||||
options = poll.get_options()
|
options = poll.get_options()
|
||||||
if isinstance(data, dict):
|
if isinstance(data, dict):
|
||||||
@ -495,30 +497,46 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
if config["users_activate_vote_weight"]:
|
if config["users_activate_vote_weight"]:
|
||||||
weight *= vote_weight
|
weight *= vote_weight
|
||||||
vote = AssignmentVote.objects.create(
|
vote = AssignmentVote.objects.create(
|
||||||
option=option, user=vote_user, weight=weight, value="Y"
|
option=option,
|
||||||
|
user=vote_user,
|
||||||
|
delegated_user=request_user,
|
||||||
|
weight=weight,
|
||||||
|
value="Y",
|
||||||
)
|
)
|
||||||
inform_changed_data(vote, no_delete_on_restriction=True)
|
inform_changed_data(vote, no_delete_on_restriction=True)
|
||||||
else: # global_no or global_abstain
|
else: # global_no or global_abstain
|
||||||
option = options[0]
|
option = options[0]
|
||||||
weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1)
|
weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1)
|
||||||
vote = AssignmentVote.objects.create(
|
vote = AssignmentVote.objects.create(
|
||||||
option=option, user=vote_user, weight=weight, value=data
|
option=option,
|
||||||
|
user=vote_user,
|
||||||
|
delegated_user=request_user,
|
||||||
|
weight=weight,
|
||||||
|
value=data,
|
||||||
)
|
)
|
||||||
inform_changed_data(vote, no_delete_on_restriction=True)
|
inform_changed_data(vote, no_delete_on_restriction=True)
|
||||||
inform_changed_data(option)
|
inform_changed_data(option)
|
||||||
inform_changed_data(poll)
|
inform_changed_data(poll)
|
||||||
|
|
||||||
def create_votes_types_yn_yna(self, data, poll, vote_weight, vote_user):
|
def create_votes_types_yn_yna(
|
||||||
|
self, data, poll, vote_weight, vote_user, request_user
|
||||||
|
):
|
||||||
"""
|
"""
|
||||||
Helper function for handle_(named|pseudoanonymous)_vote
|
Helper function for handle_(named|pseudoanonymous)_vote
|
||||||
Assumes data is already validated
|
Assumes data is already validated
|
||||||
|
vote_user is the user whose vote is given
|
||||||
|
request_user is the user who gives the vote, may be a delegate
|
||||||
"""
|
"""
|
||||||
options = poll.get_options()
|
options = poll.get_options()
|
||||||
weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1)
|
weight = vote_weight if config["users_activate_vote_weight"] else Decimal(1)
|
||||||
for option_id, result in data.items():
|
for option_id, result in data.items():
|
||||||
option = options.get(pk=option_id)
|
option = options.get(pk=option_id)
|
||||||
vote = AssignmentVote.objects.create(
|
vote = AssignmentVote.objects.create(
|
||||||
option=option, user=vote_user, value=result, weight=weight
|
option=option,
|
||||||
|
user=vote_user,
|
||||||
|
delegated_user=request_user,
|
||||||
|
value=result,
|
||||||
|
weight=weight,
|
||||||
)
|
)
|
||||||
inform_changed_data(vote, no_delete_on_restriction=True)
|
inform_changed_data(vote, no_delete_on_restriction=True)
|
||||||
inform_changed_data(option, no_delete_on_restriction=True)
|
inform_changed_data(option, no_delete_on_restriction=True)
|
||||||
@ -527,24 +545,28 @@ class AssignmentPollViewSet(BasePollViewSet):
|
|||||||
VotedModel = AssignmentPoll.voted.through
|
VotedModel = AssignmentPoll.voted.through
|
||||||
VotedModel.objects.create(assignmentpoll=poll, user=user)
|
VotedModel.objects.create(assignmentpoll=poll, user=user)
|
||||||
|
|
||||||
def handle_named_vote(self, data, poll, user):
|
def handle_named_vote(self, data, poll, vote_user, request_user):
|
||||||
if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
|
if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
|
||||||
self.create_votes_type_votes(data, poll, user.vote_weight, user)
|
self.create_votes_type_votes(
|
||||||
|
data, poll, vote_user.vote_weight, vote_user, request_user
|
||||||
|
)
|
||||||
elif poll.pollmethod in (
|
elif poll.pollmethod in (
|
||||||
AssignmentPoll.POLLMETHOD_YN,
|
AssignmentPoll.POLLMETHOD_YN,
|
||||||
AssignmentPoll.POLLMETHOD_YNA,
|
AssignmentPoll.POLLMETHOD_YNA,
|
||||||
):
|
):
|
||||||
self.create_votes_types_yn_yna(data, poll, user.vote_weight, user)
|
self.create_votes_types_yn_yna(
|
||||||
|
data, poll, vote_user.vote_weight, vote_user, request_user
|
||||||
|
)
|
||||||
|
|
||||||
def handle_pseudoanonymous_vote(self, data, poll, user):
|
def handle_pseudoanonymous_vote(self, data, poll, user):
|
||||||
if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
|
if poll.pollmethod == AssignmentPoll.POLLMETHOD_VOTES:
|
||||||
self.create_votes_type_votes(data, poll, user.vote_weight, None)
|
self.create_votes_type_votes(data, poll, user.vote_weight, None, None)
|
||||||
|
|
||||||
elif poll.pollmethod in (
|
elif poll.pollmethod in (
|
||||||
AssignmentPoll.POLLMETHOD_YN,
|
AssignmentPoll.POLLMETHOD_YN,
|
||||||
AssignmentPoll.POLLMETHOD_YNA,
|
AssignmentPoll.POLLMETHOD_YNA,
|
||||||
):
|
):
|
||||||
self.create_votes_types_yn_yna(data, poll, user.vote_weight, None)
|
self.create_votes_types_yn_yna(data, poll, user.vote_weight, None, None)
|
||||||
|
|
||||||
def convert_option_data(self, poll, data):
|
def convert_option_data(self, poll, data):
|
||||||
poll_options = poll.get_options()
|
poll_options = poll.get_options()
|
||||||
|
@ -0,0 +1,29 @@
|
|||||||
|
# Generated by Django 2.2.16 on 2020-09-10 11:02
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||||
|
("motions", "0036_rename_verbose_poll_types"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="motionvote",
|
||||||
|
name="delegated_user",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="motionvote_delegated_votes",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -1190,7 +1190,7 @@ class MotionPollViewSet(BasePollViewSet):
|
|||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
def handle_analog_vote(self, data, poll, user):
|
def handle_analog_vote(self, data, poll):
|
||||||
option = poll.options.get()
|
option = poll.options.get()
|
||||||
vote, _ = MotionVote.objects.get_or_create(option=option, value="Y")
|
vote, _ = MotionVote.objects.get_or_create(option=option, value="Y")
|
||||||
vote.weight = data["Y"]
|
vote.weight = data["Y"]
|
||||||
@ -1209,7 +1209,7 @@ class MotionPollViewSet(BasePollViewSet):
|
|||||||
|
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
def validate_vote_data(self, data, poll, user):
|
def validate_vote_data(self, data, poll):
|
||||||
"""
|
"""
|
||||||
Request data for analog:
|
Request data for analog:
|
||||||
{ "Y": <amount>, "N": <amount>, ["A": <amount>],
|
{ "Y": <amount>, "N": <amount>, ["A": <amount>],
|
||||||
@ -1240,15 +1240,25 @@ class MotionPollViewSet(BasePollViewSet):
|
|||||||
VotedModel = MotionPoll.voted.through
|
VotedModel = MotionPoll.voted.through
|
||||||
VotedModel.objects.create(motionpoll=poll, user=user)
|
VotedModel.objects.create(motionpoll=poll, user=user)
|
||||||
|
|
||||||
def handle_named_vote(self, data, poll, user):
|
def handle_named_vote(self, data, poll, vote_user, request_user):
|
||||||
self.handle_named_and_pseudoanonymous_vote(data, user, user, poll)
|
self.handle_named_and_pseudoanonymous_vote(
|
||||||
|
data,
|
||||||
|
poll,
|
||||||
|
weight_user=vote_user,
|
||||||
|
vote_user=vote_user,
|
||||||
|
request_user=request_user,
|
||||||
|
)
|
||||||
|
|
||||||
def handle_pseudoanonymous_vote(self, data, poll, user):
|
def handle_pseudoanonymous_vote(self, data, poll, user):
|
||||||
self.handle_named_and_pseudoanonymous_vote(data, user, None, poll)
|
self.handle_named_and_pseudoanonymous_vote(data, poll, user, None, None)
|
||||||
|
|
||||||
def handle_named_and_pseudoanonymous_vote(self, data, weight_user, vote_user, poll):
|
def handle_named_and_pseudoanonymous_vote(
|
||||||
|
self, data, poll, weight_user, vote_user, request_user
|
||||||
|
):
|
||||||
option = poll.options.get()
|
option = poll.options.get()
|
||||||
vote = MotionVote.objects.create(user=vote_user, option=option)
|
vote = MotionVote.objects.create(
|
||||||
|
user=vote_user, delegated_user=request_user, option=option
|
||||||
|
)
|
||||||
vote.value = data
|
vote.value = data
|
||||||
vote.weight = (
|
vote.weight = (
|
||||||
weight_user.vote_weight
|
weight_user.vote_weight
|
||||||
|
@ -2,8 +2,13 @@ import json
|
|||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from ..poll.views import BasePoll
|
from ..poll.views import BasePoll
|
||||||
|
from ..utils import logging
|
||||||
from ..utils.access_permissions import BaseAccessPermissions
|
from ..utils.access_permissions import BaseAccessPermissions
|
||||||
from ..utils.auth import async_has_perm
|
from ..utils.auth import async_has_perm, user_collection_string
|
||||||
|
from ..utils.cache import element_cache
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class BaseVoteAccessPermissions(BaseAccessPermissions):
|
class BaseVoteAccessPermissions(BaseAccessPermissions):
|
||||||
@ -26,6 +31,7 @@ class BaseVoteAccessPermissions(BaseAccessPermissions):
|
|||||||
for vote in full_data
|
for vote in full_data
|
||||||
if vote["pollstate"] == BasePoll.STATE_PUBLISHED
|
if vote["pollstate"] == BasePoll.STATE_PUBLISHED
|
||||||
or vote["user_id"] == user_id
|
or vote["user_id"] == user_id
|
||||||
|
or vote["delegated_user_id"] == user_id
|
||||||
]
|
]
|
||||||
return data
|
return data
|
||||||
|
|
||||||
@ -71,8 +77,24 @@ class BasePollAccessPermissions(BaseAccessPermissions):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
# add has_voted for all users to check whether op has voted
|
# add has_voted for all users to check whether op has voted
|
||||||
|
# also fill user_has_voted_for_delegations with all users for which he has
|
||||||
|
# already voted
|
||||||
|
user_data = await element_cache.get_element_data(
|
||||||
|
user_collection_string, user_id
|
||||||
|
)
|
||||||
|
if user_data is None:
|
||||||
|
logger.error(f"Could not find userdata for {user_id}")
|
||||||
|
vote_delegated_from_ids = set()
|
||||||
|
else:
|
||||||
|
vote_delegated_from_ids = set(user_data["vote_delegated_from_users_id"])
|
||||||
|
|
||||||
for poll in full_data:
|
for poll in full_data:
|
||||||
poll["user_has_voted"] = user_id in poll["voted_id"]
|
poll["user_has_voted"] = user_id in poll["voted_id"]
|
||||||
|
voted_ids = set(poll["voted_id"])
|
||||||
|
voted_for_delegations = list(
|
||||||
|
vote_delegated_from_ids.intersection(voted_ids)
|
||||||
|
)
|
||||||
|
poll["user_has_voted_for_delegations"] = voted_for_delegations
|
||||||
|
|
||||||
if await async_has_perm(user_id, self.manage_permission):
|
if await async_has_perm(user_id, self.manage_permission):
|
||||||
data = full_data
|
data = full_data
|
||||||
|
@ -29,6 +29,14 @@ class BaseVote(models.Model):
|
|||||||
blank=True,
|
blank=True,
|
||||||
on_delete=SET_NULL_AND_AUTOUPDATE,
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
)
|
)
|
||||||
|
delegated_user = models.ForeignKey(
|
||||||
|
settings.AUTH_USER_MODEL,
|
||||||
|
default=None,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="%(class)s_delegated_votes",
|
||||||
|
)
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
abstract = True
|
abstract = True
|
||||||
|
@ -12,7 +12,15 @@ from ..utils.rest_api import (
|
|||||||
from .models import BasePoll
|
from .models import BasePoll
|
||||||
|
|
||||||
|
|
||||||
BASE_VOTE_FIELDS = ("id", "weight", "value", "user", "option", "pollstate")
|
BASE_VOTE_FIELDS = (
|
||||||
|
"id",
|
||||||
|
"weight",
|
||||||
|
"value",
|
||||||
|
"user",
|
||||||
|
"delegated_user",
|
||||||
|
"option",
|
||||||
|
"pollstate",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaseVoteSerializer(ModelSerializer):
|
class BaseVoteSerializer(ModelSerializer):
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
|
from django.contrib.auth import get_user_model
|
||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.db import transaction
|
from django.db import transaction
|
||||||
from django.db.utils import IntegrityError
|
from django.db.utils import IntegrityError
|
||||||
@ -104,8 +105,8 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
# convert user ids to option ids
|
# convert user ids to option ids
|
||||||
self.convert_option_data(poll, vote_data)
|
self.convert_option_data(poll, vote_data)
|
||||||
|
|
||||||
self.validate_vote_data(vote_data, poll, request.user)
|
self.validate_vote_data(vote_data, poll)
|
||||||
self.handle_analog_vote(vote_data, poll, request.user)
|
self.handle_analog_vote(vote_data, poll)
|
||||||
|
|
||||||
if request.data.get("publish_immediately"):
|
if request.data.get("publish_immediately"):
|
||||||
poll.state = BasePoll.STATE_PUBLISHED
|
poll.state = BasePoll.STATE_PUBLISHED
|
||||||
@ -198,25 +199,41 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
if isinstance(request.user, AnonymousUser):
|
if isinstance(request.user, AnonymousUser):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
|
|
||||||
# check permissions based on poll type and handle requests
|
# data format is:
|
||||||
self.assert_can_vote(poll, request)
|
# { data: <vote_data>, [user_id: int] }
|
||||||
|
# if user_id is given, the operator votes for this user instead of himself
|
||||||
|
# user_id is ignored for analog polls
|
||||||
data = request.data
|
data = request.data
|
||||||
self.validate_vote_data(data, poll, request.user)
|
if "data" not in data:
|
||||||
|
raise ValidationError({"detail": "No data provided."})
|
||||||
|
vote_data = data["data"]
|
||||||
|
if "user_id" in data and poll.type != BasePoll.TYPE_ANALOG:
|
||||||
|
try:
|
||||||
|
vote_user = get_user_model().objects.get(pk=data["user_id"])
|
||||||
|
except get_user_model().DoesNotExist:
|
||||||
|
raise ValidationError({"detail": "The given user does not exist."})
|
||||||
|
else:
|
||||||
|
vote_user = request.user
|
||||||
|
|
||||||
|
# check permissions based on poll type and user
|
||||||
|
self.assert_can_vote(poll, request, vote_user)
|
||||||
|
|
||||||
|
# validate the vote data
|
||||||
|
self.validate_vote_data(vote_data, poll)
|
||||||
|
|
||||||
if poll.type == BasePoll.TYPE_ANALOG:
|
if poll.type == BasePoll.TYPE_ANALOG:
|
||||||
self.handle_analog_vote(data, poll, request.user)
|
self.handle_analog_vote(vote_data, poll)
|
||||||
if request.data.get("publish_immediately") == "1":
|
if vote_data.get("publish_immediately") == "1":
|
||||||
poll.state = BasePoll.STATE_PUBLISHED
|
poll.state = BasePoll.STATE_PUBLISHED
|
||||||
else:
|
else:
|
||||||
poll.state = BasePoll.STATE_FINISHED
|
poll.state = BasePoll.STATE_FINISHED
|
||||||
poll.save()
|
poll.save()
|
||||||
|
|
||||||
elif poll.type == BasePoll.TYPE_NAMED:
|
elif poll.type == BasePoll.TYPE_NAMED:
|
||||||
self.handle_named_vote(data, poll, request.user)
|
self.handle_named_vote(vote_data, poll, vote_user, request.user)
|
||||||
|
|
||||||
elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS:
|
elif poll.type == BasePoll.TYPE_PSEUDOANONYMOUS:
|
||||||
self.handle_pseudoanonymous_vote(data, poll, request.user)
|
self.handle_pseudoanonymous_vote(vote_data, poll, vote_user)
|
||||||
|
|
||||||
inform_changed_data(poll)
|
inform_changed_data(poll)
|
||||||
|
|
||||||
@ -231,13 +248,16 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
inform_changed_data(poll.get_votes(), final_data=True)
|
inform_changed_data(poll.get_votes(), final_data=True)
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
def assert_can_vote(self, poll, request):
|
def assert_can_vote(self, poll, request, vote_user):
|
||||||
"""
|
"""
|
||||||
Raises a permission denied, if the user is not allowed to vote (or has already voted).
|
Raises a permission denied, if the user is not allowed to vote (or has already voted).
|
||||||
Adds the user to the voted array, so this needs to be reverted on error!
|
Adds the user to the voted array, so this needs to be reverted if a later error happens!
|
||||||
Analog: has to have manage permissions
|
Analog: has to have manage permissions
|
||||||
Named & Pseudoanonymous: has to be in a poll group and present
|
Named & Pseudoanonymous: has to be in a poll group and present
|
||||||
"""
|
"""
|
||||||
|
if request.user != vote_user and request.user != vote_user.vote_delegated_to:
|
||||||
|
self.permission_denied(request)
|
||||||
|
|
||||||
if poll.type == BasePoll.TYPE_ANALOG:
|
if poll.type == BasePoll.TYPE_ANALOG:
|
||||||
if not self.has_manage_permissions():
|
if not self.has_manage_permissions():
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
@ -246,14 +266,14 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
raise ValidationError("You can only vote on a started poll.")
|
raise ValidationError("You can only vote on a started poll.")
|
||||||
|
|
||||||
if not request.user.is_present or not in_some_groups(
|
if not request.user.is_present or not in_some_groups(
|
||||||
request.user.id,
|
vote_user.id,
|
||||||
list(poll.groups.values_list("pk", flat=True)),
|
list(poll.groups.values_list("pk", flat=True)),
|
||||||
exact=True,
|
exact=True,
|
||||||
):
|
):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.add_user_to_voted_array(request.user, poll)
|
self.add_user_to_voted_array(vote_user, poll)
|
||||||
inform_changed_data(poll)
|
inform_changed_data(poll)
|
||||||
except IntegrityError:
|
except IntegrityError:
|
||||||
raise ValidationError({"detail": "You have already voted"})
|
raise ValidationError({"detail": "You have already voted"})
|
||||||
@ -292,20 +312,20 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def validate_vote_data(self, data, poll, user):
|
def validate_vote_data(self, data, poll):
|
||||||
"""
|
"""
|
||||||
To be implemented by subclass. Validates the data according to poll type and method and fields by validated versions.
|
To be implemented by subclass. Validates the data according to poll type and method and fields by validated versions.
|
||||||
Raises ValidationError on failure
|
Raises ValidationError on failure
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def handle_analog_vote(self, data, poll, user):
|
def handle_analog_vote(self, data, poll):
|
||||||
"""
|
"""
|
||||||
To be implemented by subclass. Handles the analog vote. Assumes data is validated
|
To be implemented by subclass. Handles the analog vote. Assumes data is validated
|
||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def handle_named_vote(self, data, poll, user):
|
def handle_named_vote(self, data, poll, vote_user, request_user):
|
||||||
"""
|
"""
|
||||||
To be implemented by subclass. Handles the named vote. Assumes data is validated.
|
To be implemented by subclass. Handles the named vote. Assumes data is validated.
|
||||||
Needs to manage the voted-array per option.
|
Needs to manage the voted-array per option.
|
||||||
|
@ -58,6 +58,8 @@ class UserAccessPermissions(BaseAccessPermissions):
|
|||||||
own_data_fields = set(little_data_fields)
|
own_data_fields = set(little_data_fields)
|
||||||
own_data_fields.add("email")
|
own_data_fields.add("email")
|
||||||
own_data_fields.add("gender")
|
own_data_fields.add("gender")
|
||||||
|
own_data_fields.add("vote_delegated_to_id")
|
||||||
|
own_data_fields.add("vote_delegated_from_users_id")
|
||||||
|
|
||||||
# Check user permissions.
|
# Check user permissions.
|
||||||
if await async_has_perm(user_id, "users.can_see_name"):
|
if await async_has_perm(user_id, "users.can_see_name"):
|
||||||
|
@ -0,0 +1,27 @@
|
|||||||
|
# Generated by Django 2.2.16 on 2020-09-03 11:13
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
import openslides.utils.models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("users", "0014_user_rename_permission"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="user",
|
||||||
|
name="vote_delegated_to",
|
||||||
|
field=models.ForeignKey(
|
||||||
|
blank=True,
|
||||||
|
null=True,
|
||||||
|
on_delete=openslides.utils.models.SET_NULL_AND_AUTOUPDATE,
|
||||||
|
related_name="vote_delegated_from_users",
|
||||||
|
to=settings.AUTH_USER_MODEL,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]
|
@ -22,7 +22,11 @@ from openslides.utils.manager import BaseManager
|
|||||||
|
|
||||||
from ..core.config import config
|
from ..core.config import config
|
||||||
from ..utils.auth import GROUP_ADMIN_PK
|
from ..utils.auth import GROUP_ADMIN_PK
|
||||||
from ..utils.models import CASCADE_AND_AUTOUPDATE, RESTModelMixin
|
from ..utils.models import (
|
||||||
|
CASCADE_AND_AUTOUPDATE,
|
||||||
|
SET_NULL_AND_AUTOUPDATE,
|
||||||
|
RESTModelMixin,
|
||||||
|
)
|
||||||
from .access_permissions import (
|
from .access_permissions import (
|
||||||
GroupAccessPermissions,
|
GroupAccessPermissions,
|
||||||
PersonalNoteAccessPermissions,
|
PersonalNoteAccessPermissions,
|
||||||
@ -54,7 +58,8 @@ class UserManager(BaseUserManager):
|
|||||||
queryset=Permission.objects.select_related("content_type"),
|
queryset=Permission.objects.select_related("content_type"),
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
|
"vote_delegated_from_users",
|
||||||
)
|
)
|
||||||
|
|
||||||
def create_user(self, username, password, skip_autoupdate=False, **kwargs):
|
def create_user(self, username, password, skip_autoupdate=False, **kwargs):
|
||||||
@ -164,6 +169,14 @@ class User(RESTModelMixin, PermissionsMixin, AbstractBaseUser):
|
|||||||
default=Decimal("1"), max_digits=15, decimal_places=6, null=False, blank=True
|
default=Decimal("1"), max_digits=15, decimal_places=6, null=False, blank=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vote_delegated_to = models.ForeignKey(
|
||||||
|
"self",
|
||||||
|
on_delete=SET_NULL_AND_AUTOUPDATE,
|
||||||
|
null=True,
|
||||||
|
blank=True,
|
||||||
|
related_name="vote_delegated_from_users",
|
||||||
|
)
|
||||||
|
|
||||||
objects = UserManager()
|
objects = UserManager()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
|
@ -7,6 +7,7 @@ from ..utils.rest_api import (
|
|||||||
JSONField,
|
JSONField,
|
||||||
ModelSerializer,
|
ModelSerializer,
|
||||||
RelatedField,
|
RelatedField,
|
||||||
|
SerializerMethodField,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
from ..utils.validate import validate_html_strict
|
from ..utils.validate import validate_html_strict
|
||||||
@ -36,6 +37,8 @@ USERCANSEEEXTRASERIALIZER_FIELDS = USERCANSEESERIALIZER_FIELDS + (
|
|||||||
"comment",
|
"comment",
|
||||||
"is_active",
|
"is_active",
|
||||||
"auth_type",
|
"auth_type",
|
||||||
|
"vote_delegated_to_id",
|
||||||
|
"vote_delegated_from_users_id",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -57,11 +60,14 @@ class UserSerializer(ModelSerializer):
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
vote_delegated_from_users_id = SerializerMethodField()
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = User
|
model = User
|
||||||
fields = USERCANSEEEXTRASERIALIZER_FIELDS + (
|
fields = USERCANSEEEXTRASERIALIZER_FIELDS + (
|
||||||
"default_password",
|
"default_password",
|
||||||
"session_auth_hash",
|
"session_auth_hash",
|
||||||
|
"vote_delegated_to",
|
||||||
)
|
)
|
||||||
read_only_fields = ("last_email_send", "auth_type")
|
read_only_fields = ("last_email_send", "auth_type")
|
||||||
|
|
||||||
@ -119,6 +125,13 @@ class UserSerializer(ModelSerializer):
|
|||||||
inform_changed_data(user)
|
inform_changed_data(user)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
|
def get_vote_delegated_from_users_id(self, user):
|
||||||
|
# check needed to prevent errors on import since we only give an OrderedDict there
|
||||||
|
if hasattr(user, "vote_delegated_from_users"):
|
||||||
|
return [delegator.id for delegator in user.vote_delegated_from_users.all()]
|
||||||
|
else:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
class PermissionRelatedField(RelatedField):
|
class PermissionRelatedField(RelatedField):
|
||||||
"""
|
"""
|
||||||
|
@ -174,9 +174,77 @@ class UserViewSet(ModelViewSet):
|
|||||||
):
|
):
|
||||||
request.data["username"] = user.username
|
request.data["username"] = user.username
|
||||||
|
|
||||||
|
# check that no chains are created with vote delegation
|
||||||
|
delegate_id = request.data.get("vote_delegated_to_id")
|
||||||
|
if delegate_id:
|
||||||
|
try:
|
||||||
|
delegate = User.objects.get(id=delegate_id)
|
||||||
|
except User.DoesNotExist:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"detail": f"Vote delegation: The user with id {delegate_id} does not exist"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assert_no_self_delegation(user, [delegate_id])
|
||||||
|
self.assert_vote_not_delegated(delegate)
|
||||||
|
self.assert_has_no_delegated_votes(user)
|
||||||
|
|
||||||
|
inform_changed_data(delegate)
|
||||||
|
if user.vote_delegated_to:
|
||||||
|
inform_changed_data(user.vote_delegated_to)
|
||||||
|
|
||||||
|
# handle delegated_from field seperately since its a SerializerMethodField
|
||||||
|
new_delegation_ids = request.data.get("vote_delegated_from_users_id")
|
||||||
|
if "vote_delegated_from_users_id" in request.data:
|
||||||
|
del request.data["vote_delegated_from_users_id"]
|
||||||
|
|
||||||
response = super().update(request, *args, **kwargs)
|
response = super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
|
# after rest of the request succeeded, handle delegation changes
|
||||||
|
if new_delegation_ids:
|
||||||
|
self.assert_no_self_delegation(user, new_delegation_ids)
|
||||||
|
self.assert_vote_not_delegated(user)
|
||||||
|
|
||||||
|
for id in new_delegation_ids:
|
||||||
|
delegation_user = User.objects.get(id=id)
|
||||||
|
self.assert_has_no_delegated_votes(delegation_user)
|
||||||
|
delegation_user.vote_delegated_to = user
|
||||||
|
delegation_user.save()
|
||||||
|
|
||||||
|
delegations_to_remove = user.vote_delegated_from_users.exclude(
|
||||||
|
id__in=(new_delegation_ids or [])
|
||||||
|
)
|
||||||
|
for old_delegation_user in delegations_to_remove:
|
||||||
|
old_delegation_user.vote_delegated_to = None
|
||||||
|
old_delegation_user.save()
|
||||||
|
|
||||||
|
# if only delegated_from was changed, we need an autoupdate for the operator
|
||||||
|
if new_delegation_ids or delegations_to_remove:
|
||||||
|
inform_changed_data(user)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
def assert_vote_not_delegated(self, user):
|
||||||
|
if user.vote_delegated_to:
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"detail": "You cannot delegate a vote to a user who has already delegated his vote."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def assert_has_no_delegated_votes(self, user):
|
||||||
|
if user.vote_delegated_from_users and len(user.vote_delegated_from_users.all()):
|
||||||
|
raise ValidationError(
|
||||||
|
{
|
||||||
|
"detail": "You cannot delegate a vote of a user who is already a delegate of another user."
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
def assert_no_self_delegation(self, user, delegate_ids):
|
||||||
|
if user.id in delegate_ids:
|
||||||
|
raise ValidationError({"detail": "You cannot delegate a vote to yourself."})
|
||||||
|
|
||||||
def destroy(self, request, *args, **kwargs):
|
def destroy(self, request, *args, **kwargs):
|
||||||
"""
|
"""
|
||||||
Customized view endpoint to delete an user.
|
Customized view endpoint to delete an user.
|
||||||
@ -391,6 +459,7 @@ class UserViewSet(ModelViewSet):
|
|||||||
data = serializer.prepare_password(serializer.data)
|
data = serializer.prepare_password(serializer.data)
|
||||||
groups = data["groups_id"]
|
groups = data["groups_id"]
|
||||||
del data["groups_id"]
|
del data["groups_id"]
|
||||||
|
del data["vote_delegated_from_users_id"]
|
||||||
|
|
||||||
db_user = User(**data)
|
db_user = User(**data)
|
||||||
try:
|
try:
|
||||||
|
@ -770,13 +770,15 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"options": {
|
"data": {
|
||||||
"1": {"Y": "1", "N": "2.35", "A": "-1"},
|
"options": {
|
||||||
"2": {"Y": "30", "N": "-2", "A": "8.93"},
|
"1": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
|
"2": {"Y": "30", "N": "-2", "A": "8.93"},
|
||||||
|
},
|
||||||
|
"votesvalid": "4.64",
|
||||||
|
"votesinvalid": "-2",
|
||||||
|
"votescast": "-2",
|
||||||
},
|
},
|
||||||
"votesvalid": "4.64",
|
|
||||||
"votesinvalid": "-2",
|
|
||||||
"votescast": "-2",
|
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -800,10 +802,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"options": {"1": {"Y": "1", "N": "1", "A": "1"}},
|
"data": {
|
||||||
"votesvalid": "-1.5",
|
"options": {"1": {"Y": "1", "N": "1", "A": "1"}},
|
||||||
"votesinvalid": "-2",
|
"votesvalid": "-1.5",
|
||||||
"votescast": "-2",
|
"votesinvalid": "-2",
|
||||||
|
"votescast": "-2",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -814,10 +818,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"options": {
|
"data": {
|
||||||
"1": {"Y": "1", "N": "2.35", "A": "-1"},
|
"options": {
|
||||||
"2": {"Y": "1", "N": "2.35", "A": "-1"},
|
"1": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
}
|
"2": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
|
}
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -828,7 +834,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": {"1": {"Y": "1", "N": "2.35", "A": "-1"}}},
|
{"data": {"options": {"1": {"Y": "1", "N": "2.35", "A": "-1"}}}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -839,9 +845,11 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"options": {
|
"data": {
|
||||||
"1": {"Y": "1", "N": "2.35", "A": "-1"},
|
"options": {
|
||||||
"3": {"Y": "1", "N": "2.35", "A": "-1"},
|
"1": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
|
"3": {"Y": "1", "N": "2.35", "A": "-1"},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -851,25 +859,31 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
def test_no_permissions(self):
|
def test_no_permissions(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_wrong_state(self):
|
def test_wrong_state(self):
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_missing_data(self):
|
def test_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_wrong_data_format(self):
|
def test_wrong_data_format(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -878,7 +892,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": [1, "string"]},
|
{"data": {"options": [1, "string"]}},
|
||||||
)
|
)
|
||||||
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())
|
||||||
@ -887,7 +901,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": {"string": "some_other_string"}},
|
{"data": {"options": {"string": "some_other_string"}}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -896,7 +910,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"options": {"1": [None]}},
|
{"data": {"options": {"1": [None]}}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -907,7 +921,7 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
data = {"options": {"1": {"Y": "1", "N": "3", "A": "-1"}}}
|
data = {"options": {"1": {"Y": "1", "N": "3", "A": "-1"}}}
|
||||||
del data["options"]["1"][value]
|
del data["options"]["1"][value]
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), data
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": data}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -917,10 +931,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.client.post(
|
self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"options": {"1": {"Y": 5, "N": 0, "A": 1}},
|
"data": {
|
||||||
"votesvalid": "-2",
|
"options": {"1": {"Y": 5, "N": 0, "A": 1}},
|
||||||
"votesinvalid": "1",
|
"votesvalid": "-2",
|
||||||
"votescast": "-1",
|
"votesinvalid": "1",
|
||||||
|
"votescast": "-1",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.poll.state = 3
|
self.poll.state = 3
|
||||||
@ -928,10 +944,12 @@ class VoteAssignmentPollAnalogYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"options": {"1": {"Y": 2, "N": 2, "A": 2}},
|
"data": {
|
||||||
"votesvalid": "4.64",
|
"options": {"1": {"Y": 2, "N": 2, "A": 2}},
|
||||||
"votesinvalid": "-2",
|
"votesvalid": "4.64",
|
||||||
"votescast": "3",
|
"votesinvalid": "-2",
|
||||||
|
"votescast": "3",
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -973,7 +991,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "2": "N", "3": "A"},
|
{"data": {"1": "Y", "2": "N", "3": "A"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1007,7 +1025,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "2": "N", "3": "A"},
|
{"data": {"1": "Y", "2": "N", "3": "A"}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
self.assertEqual(AssignmentVote.objects.count(), 3)
|
self.assertEqual(AssignmentVote.objects.count(), 3)
|
||||||
@ -1039,12 +1057,12 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "N"},
|
{"data": {"1": "N"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1064,7 +1082,8 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
option2 = self.poll2.options.get()
|
option2 = self.poll2.options.get()
|
||||||
# Do request to poll with option2 (which is wrong...)
|
# Do request to poll with option2 (which is wrong...)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {str(option2.id): "Y"}
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {str(option2.id): "Y"}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertEqual(AssignmentVote.objects.count(), 0)
|
self.assertEqual(AssignmentVote.objects.count(), 0)
|
||||||
@ -1081,7 +1100,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "2": "N"},
|
{"data": {"1": "Y", "2": "N"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1092,7 +1111,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1103,7 +1122,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "3": "N"},
|
{"data": {"1": "Y", "3": "N"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1114,7 +1133,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
@ -1125,7 +1144,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
gclient = self.create_guest_client()
|
gclient = self.create_guest_client()
|
||||||
response = gclient.post(
|
response = gclient.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
@ -1137,20 +1156,24 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.admin.save()
|
self.admin.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_wrong_state(self):
|
def test_wrong_state(self):
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_missing_data(self):
|
def test_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(
|
self.assertHttpStatusVerbose(
|
||||||
response, status.HTTP_200_OK
|
response, status.HTTP_200_OK
|
||||||
) # new "feature" because of partial requests: empty requests work!
|
) # new "feature" because of partial requests: empty requests work!
|
||||||
@ -1160,7 +1183,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
[1, 2, 5],
|
{"data": [1, 2, 5]},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1170,7 +1193,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "string"},
|
{"data": {"1": "string"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1180,7 +1203,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"id": "Y"},
|
{"data": {"id": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1190,7 +1213,7 @@ class VoteAssignmentPollNamedYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": [None]},
|
{"data": {"1": [None]}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1229,7 +1252,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 1, "2": 0},
|
{"data": {"1": 1, "2": 0}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1254,12 +1277,12 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 1, "2": 0},
|
{"data": {"1": 1, "2": 0}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 0, "2": 1},
|
{"data": {"1": 0, "2": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1278,7 +1301,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), "N"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "N"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -1294,7 +1317,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), "N"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "N"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -1305,7 +1328,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), "A"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -1321,7 +1344,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.poll.save()
|
self.poll.save()
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), "A"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -1331,7 +1354,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": -1},
|
{"data": {"1": -1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1342,7 +1365,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 2, "2": 1},
|
{"data": {"1": 2, "2": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1361,7 +1384,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 2, "2": 2},
|
{"data": {"1": 2, "2": 2}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1372,7 +1395,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 1, "2": 1, "3": 1},
|
{"data": {"1": 1, "2": 1, "3": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1381,7 +1404,9 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
def test_wrong_options(self):
|
def test_wrong_options(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"2": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"2": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -1390,7 +1415,9 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -1399,7 +1426,9 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
gclient = self.create_guest_client()
|
gclient = self.create_guest_client()
|
||||||
response = gclient.post(
|
response = gclient.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -1409,21 +1438,27 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.admin.is_present = False
|
self.admin.is_present = False
|
||||||
self.admin.save()
|
self.admin.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_wrong_state(self):
|
def test_wrong_state(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_missing_data(self):
|
def test_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
@ -1431,7 +1466,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
[1, 2, 5],
|
{"data": [1, 2, 5]},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1441,7 +1476,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "string"},
|
{"data": {"1": "string"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1451,7 +1486,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"id": 1},
|
{"data": {"id": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1461,7 +1496,7 @@ class VoteAssignmentPollNamedVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": [None]},
|
{"data": {"1": [None]}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1495,7 +1530,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "2": "N", "3": "A"},
|
{"data": {"1": "Y", "2": "N", "3": "A"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1522,12 +1557,12 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "N"},
|
{"data": {"1": "N"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1541,7 +1576,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "2": "N"},
|
{"data": {"1": "Y", "2": "N"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1552,7 +1587,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1563,7 +1598,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y", "3": "N"},
|
{"data": {"1": "Y", "3": "N"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1574,7 +1609,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
@ -1585,7 +1620,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
gclient = self.create_guest_client()
|
gclient = self.create_guest_client()
|
||||||
response = gclient.post(
|
response = gclient.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
@ -1597,20 +1632,24 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.admin.save()
|
self.admin.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "Y"},
|
{"data": {"1": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_wrong_state(self):
|
def test_wrong_state(self):
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_missing_data(self):
|
def test_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
@ -1618,7 +1657,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
[1, 2, 5],
|
{"data": [1, 2, 5]},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1628,7 +1667,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "string"},
|
{"data": {"1": "string"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1638,7 +1677,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"id": "Y"},
|
{"data": {"id": "Y"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1648,7 +1687,7 @@ class VoteAssignmentPollPseudoanonymousYNA(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": [None]},
|
{"data": {"1": [None]}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1687,7 +1726,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 1, "2": 0},
|
{"data": {"1": 1, "2": 0}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1714,12 +1753,12 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 1, "2": 0},
|
{"data": {"1": 1, "2": 0}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 0, "2": 1},
|
{"data": {"1": 0, "2": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1737,7 +1776,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": -1},
|
{"data": {"1": -1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1748,7 +1787,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 2, "2": 1},
|
{"data": {"1": 2, "2": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -1769,7 +1808,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 2, "2": 2},
|
{"data": {"1": 2, "2": 2}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1780,7 +1819,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": 1, "2": 1, "3": 1},
|
{"data": {"1": 1, "2": 1, "3": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1789,7 +1828,9 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
def test_wrong_options(self):
|
def test_wrong_options(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"2": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"2": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
@ -1798,7 +1839,9 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -1807,7 +1850,9 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
gclient = self.create_guest_client()
|
gclient = self.create_guest_client()
|
||||||
response = gclient.post(
|
response = gclient.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
@ -1817,21 +1862,27 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.admin.is_present = False
|
self.admin.is_present = False
|
||||||
self.admin.save()
|
self.admin.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
self.assertFalse(AssignmentPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_wrong_state(self):
|
def test_wrong_state(self):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": 1}, format="json"
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"1": 1}},
|
||||||
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
def test_missing_data(self):
|
def test_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(reverse("assignmentpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
self.assertFalse(AssignmentVote.objects.exists())
|
self.assertFalse(AssignmentVote.objects.exists())
|
||||||
|
|
||||||
@ -1839,7 +1890,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
[1, 2, 5],
|
{"data": {"data": [1, 2, 5]}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1849,7 +1900,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": "string"},
|
{"data": {"1": "string"}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1859,7 +1910,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"id": 1},
|
{"data": {"id": 1}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1869,7 +1920,7 @@ class VoteAssignmentPollPseudoanonymousVotes(VoteAssignmentPollBaseTestClass):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
reverse("assignmentpoll-vote", args=[self.poll.pk]),
|
||||||
{"1": [None]},
|
{"data": {"1": [None]}},
|
||||||
format="json",
|
format="json",
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
@ -1923,7 +1974,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
|
|
||||||
def test_vote(self):
|
def test_vote(self):
|
||||||
response = self.user_client.post(
|
response = self.user_client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": "A"}
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {"1": "A"}}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -1956,6 +2007,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
"user_has_voted": False,
|
"user_has_voted": False,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
},
|
},
|
||||||
"assignments/assignment-option:1": {
|
"assignments/assignment-option:1": {
|
||||||
@ -1973,6 +2025,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"option_id": 1,
|
"option_id": 1,
|
||||||
"pollstate": AssignmentPoll.STATE_STARTED,
|
"pollstate": AssignmentPoll.STATE_STARTED,
|
||||||
"user_id": self.user.id,
|
"user_id": self.user.id,
|
||||||
|
"delegated_user_id": self.user.id,
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
},
|
},
|
||||||
@ -1989,6 +2042,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"option_id": 1,
|
"option_id": 1,
|
||||||
"pollstate": AssignmentPoll.STATE_STARTED,
|
"pollstate": AssignmentPoll.STATE_STARTED,
|
||||||
"user_id": self.user.id,
|
"user_id": self.user.id,
|
||||||
|
"delegated_user_id": self.user.id,
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
},
|
},
|
||||||
@ -2018,6 +2072,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"votes_amount": 1,
|
"votes_amount": 1,
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2073,6 +2128,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -2084,6 +2140,7 @@ class VoteAssignmentPollNamedAutoupdates(VoteAssignmentPollAutoupdatesBaseClass)
|
|||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"user_id": 3,
|
"user_id": 3,
|
||||||
|
"delegated_user_id": None,
|
||||||
"option_id": 1,
|
"option_id": 1,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
@ -2108,9 +2165,9 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
):
|
):
|
||||||
poll_type = AssignmentPoll.TYPE_PSEUDOANONYMOUS
|
poll_type = AssignmentPoll.TYPE_PSEUDOANONYMOUS
|
||||||
|
|
||||||
def test_vote(self):
|
def test_votex(self):
|
||||||
response = self.user_client.post(
|
response = self.user_client.post(
|
||||||
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"1": "A"}
|
reverse("assignmentpoll-vote", args=[self.poll.pk]), {"data": {"1": "A"}}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = AssignmentPoll.objects.get()
|
poll = AssignmentPoll.objects.get()
|
||||||
@ -2136,6 +2193,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"description": self.description,
|
"description": self.description,
|
||||||
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
"type": AssignmentPoll.TYPE_PSEUDOANONYMOUS,
|
||||||
"user_has_voted": False,
|
"user_has_voted": False,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"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,
|
||||||
@ -2159,6 +2217,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"option_id": 1,
|
"option_id": 1,
|
||||||
"pollstate": AssignmentPoll.STATE_STARTED,
|
"pollstate": AssignmentPoll.STATE_STARTED,
|
||||||
"user_id": None,
|
"user_id": None,
|
||||||
|
"delegated_user_id": None,
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
},
|
},
|
||||||
@ -2190,6 +2249,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"votes_amount": 1,
|
"votes_amount": 1,
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -2245,6 +2305,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"votesinvalid": "0.000000",
|
"votesinvalid": "0.000000",
|
||||||
"votesvalid": "1.000000",
|
"votesvalid": "1.000000",
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
},
|
},
|
||||||
"assignments/assignment-vote:1": {
|
"assignments/assignment-vote:1": {
|
||||||
@ -2253,6 +2314,7 @@ class VoteAssignmentPollPseudoanonymousAutoupdates(
|
|||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"user_id": None,
|
"user_id": None,
|
||||||
|
"delegated_user_id": None,
|
||||||
"option_id": 1,
|
"option_id": 1,
|
||||||
},
|
},
|
||||||
"assignments/assignment-option:1": {
|
"assignments/assignment-option:1": {
|
||||||
|
@ -167,6 +167,7 @@ class CreateMotionPoll(TestCase):
|
|||||||
"id": 1,
|
"id": 1,
|
||||||
"voted_id": [],
|
"voted_id": [],
|
||||||
"user_has_voted": False,
|
"user_has_voted": False,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(autoupdate[1], [])
|
self.assertEqual(autoupdate[1], [])
|
||||||
@ -610,12 +611,14 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]),
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"Y": "1",
|
"data": {
|
||||||
"N": "2.35",
|
"Y": "1",
|
||||||
"A": "-1",
|
"N": "2.35",
|
||||||
"votesvalid": "4.64",
|
"A": "-1",
|
||||||
"votesinvalid": "-2",
|
"votesvalid": "4.64",
|
||||||
"votescast": "-2",
|
"votesinvalid": "-2",
|
||||||
|
"votescast": "-2",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -634,14 +637,23 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
def test_vote_no_permissions(self):
|
def test_vote_no_permissions(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
def test_vote_no_data(self):
|
||||||
|
self.start_poll()
|
||||||
|
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]), {})
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_vote_missing_data(self):
|
def test_vote_missing_data(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), {"Y": "4", "N": "22.6"}
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": {"Y": "4", "N": "22.6"}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -649,7 +661,7 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
def test_vote_wrong_data_format(self):
|
def test_vote_wrong_data_format(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -658,7 +670,7 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]),
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
{"Y": "some string", "N": "-2", "A": "3"},
|
{"data": {"Y": "some string", "N": "-2", "A": "3"}},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -668,12 +680,14 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
self.client.post(
|
self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]),
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"Y": "3",
|
"data": {
|
||||||
"N": "1",
|
"Y": "3",
|
||||||
"A": "5",
|
"N": "1",
|
||||||
"votesvalid": "-2",
|
"A": "5",
|
||||||
"votesinvalid": "1",
|
"votesvalid": "-2",
|
||||||
"votescast": "-1",
|
"votesinvalid": "1",
|
||||||
|
"votescast": "-1",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.poll.state = 3
|
self.poll.state = 3
|
||||||
@ -681,12 +695,14 @@ class VoteMotionPollAnalog(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]),
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
{
|
{
|
||||||
"Y": "1",
|
"data": {
|
||||||
"N": "2.35",
|
"Y": "1",
|
||||||
"A": "-1",
|
"N": "2.35",
|
||||||
"votesvalid": "4.64",
|
"A": "-1",
|
||||||
"votesinvalid": "-2",
|
"votesvalid": "4.64",
|
||||||
"votescast": "3",
|
"votesinvalid": "-2",
|
||||||
|
"votescast": "3",
|
||||||
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
@ -749,7 +765,7 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -773,7 +789,7 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.admin.vote_weight = weight = Decimal("3.5")
|
self.admin.vote_weight = weight = Decimal("3.5")
|
||||||
self.admin.save()
|
self.admin.save()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -799,11 +815,11 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -824,38 +840,35 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
config["general_system_enable_anonymous"] = True
|
config["general_system_enable_anonymous"] = True
|
||||||
guest_client = APIClient()
|
guest_client = APIClient()
|
||||||
response = guest_client.post(
|
response = guest_client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "Y"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "Y"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
# TODO: Move to unit tests
|
|
||||||
def test_not_set_vote_values(self):
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.poll.votesvalid = Decimal("1")
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.poll.votesinvalid = Decimal("1")
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
self.poll.votescast = Decimal("1")
|
|
||||||
|
|
||||||
def test_vote_wrong_state(self):
|
def test_vote_wrong_state(self):
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_vote_wrong_group(self):
|
def test_vote_wrong_group(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_vote_not_present(self):
|
def test_vote_not_present(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
@ -863,7 +876,9 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
@ -872,11 +887,118 @@ class VoteMotionPollNamed(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
def setup_vote_delegation(self, with_delegation=True):
|
||||||
|
""" user -> admin """
|
||||||
|
self.start_poll()
|
||||||
|
self.make_admin_delegate()
|
||||||
|
self.make_admin_present()
|
||||||
|
user, _ = self.create_user()
|
||||||
|
user.groups.add(GROUP_DELEGATE_PK)
|
||||||
|
if with_delegation:
|
||||||
|
user.vote_delegated_to = self.admin
|
||||||
|
user.save()
|
||||||
|
inform_changed_data(self.admin) # put the admin into the cache to update
|
||||||
|
# its vote_delegated_to_id field
|
||||||
|
self.user = user
|
||||||
|
|
||||||
|
def test_vote_delegation(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": "N", "user_id": self.user.pk}, # user not present
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.votesvalid, Decimal("1"))
|
||||||
|
self.assertEqual(poll.votesinvalid, Decimal("0"))
|
||||||
|
self.assertEqual(poll.votescast, Decimal("1"))
|
||||||
|
self.assertEqual(poll.get_votes().count(), 1)
|
||||||
|
option = poll.options.get()
|
||||||
|
self.assertEqual(option.yes, Decimal("0"))
|
||||||
|
self.assertEqual(option.no, Decimal("1"))
|
||||||
|
self.assertEqual(option.abstain, Decimal("0"))
|
||||||
|
vote = option.votes.get()
|
||||||
|
self.assertEqual(vote.user, self.user)
|
||||||
|
self.assertEqual(vote.delegated_user, self.admin)
|
||||||
|
|
||||||
|
autoupdate = self.get_last_autoupdate(user=self.admin)
|
||||||
|
self.assertIn("motions/motion-poll:1", autoupdate[0])
|
||||||
|
self.assertEqual(
|
||||||
|
autoupdate[0]["motions/motion-poll:1"]["user_has_voted_for_delegations"],
|
||||||
|
[self.user.pk],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_vote_delegation_and_self_vote(self):
|
||||||
|
self.test_vote_delegation()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "Y"}
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.votesvalid, Decimal("2"))
|
||||||
|
self.assertEqual(poll.votesinvalid, Decimal("0"))
|
||||||
|
self.assertEqual(poll.votescast, Decimal("2"))
|
||||||
|
self.assertEqual(poll.get_votes().count(), 2)
|
||||||
|
option = poll.options.get()
|
||||||
|
self.assertEqual(option.yes, Decimal("1"))
|
||||||
|
self.assertEqual(option.no, Decimal("1"))
|
||||||
|
self.assertEqual(option.abstain, Decimal("0"))
|
||||||
|
vote = option.votes.get(user_id=self.admin.pk)
|
||||||
|
self.assertEqual(vote.user, self.admin)
|
||||||
|
self.assertEqual(vote.delegated_user, self.admin)
|
||||||
|
|
||||||
|
def test_vote_delegation_forbidden(self):
|
||||||
|
self.setup_vote_delegation(False)
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": "N", "user_id": self.user.pk},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
def test_vote_delegation_not_present(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
self.admin.is_present = False
|
||||||
|
self.admin.save()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": "N", "user_id": self.user.pk},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
def test_vote_delegation_delegatee_not_in_group(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
self.admin.groups.remove(GROUP_DELEGATE_PK)
|
||||||
|
self.admin.save()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": "N", "user_id": self.user.pk},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
poll = MotionPoll.objects.get()
|
||||||
|
self.assertEqual(poll.get_votes().count(), 1)
|
||||||
|
vote = poll.get_votes()[0]
|
||||||
|
self.assertEqual(vote.value, "N")
|
||||||
|
self.assertEqual(vote.user, self.user)
|
||||||
|
self.assertEqual(vote.delegated_user, self.admin)
|
||||||
|
|
||||||
|
def test_vote_delegation_delegator_not_in_group(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
self.user.groups.remove(GROUP_DELEGATE_PK)
|
||||||
|
self.user.save()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]),
|
||||||
|
{"data": "N", "user_id": self.user.pk},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
|
|
||||||
class VoteMotionPollNamedAutoupdates(TestCase):
|
class VoteMotionPollNamedAutoupdates(TestCase):
|
||||||
"""3 important users:
|
"""3 important users:
|
||||||
@ -916,7 +1038,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
|
|
||||||
def test_vote(self):
|
def test_vote(self):
|
||||||
response = self.user_client.post(
|
response = self.user_client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -942,6 +1064,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"user_has_voted": False,
|
"user_has_voted": False,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
},
|
},
|
||||||
"motions/motion-vote:1": {
|
"motions/motion-vote:1": {
|
||||||
@ -950,6 +1073,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"user_id": self.user.id,
|
"user_id": self.user.id,
|
||||||
|
"delegated_user_id": self.user.id,
|
||||||
"option_id": 1,
|
"option_id": 1,
|
||||||
},
|
},
|
||||||
"motions/motion-option:1": {
|
"motions/motion-option:1": {
|
||||||
@ -975,6 +1099,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"user_id": self.user.id,
|
"user_id": self.user.id,
|
||||||
|
"delegated_user_id": self.user.id,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -1001,6 +1126,7 @@ class VoteMotionPollNamedAutoupdates(TestCase):
|
|||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
@ -1055,7 +1181,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
|
|
||||||
def test_vote(self):
|
def test_vote(self):
|
||||||
response = self.user_client.post(
|
response = self.user_client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -1081,6 +1207,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"user_has_voted": False,
|
"user_has_voted": False,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"voted_id": [self.user.id],
|
"voted_id": [self.user.id],
|
||||||
},
|
},
|
||||||
"motions/motion-vote:1": {
|
"motions/motion-vote:1": {
|
||||||
@ -1090,6 +1217,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
"weight": "1.000000",
|
"weight": "1.000000",
|
||||||
"value": "A",
|
"value": "A",
|
||||||
"user_id": None,
|
"user_id": None,
|
||||||
|
"delegated_user_id": None,
|
||||||
},
|
},
|
||||||
"motions/motion-option:1": {
|
"motions/motion-option:1": {
|
||||||
"abstain": "1.000000",
|
"abstain": "1.000000",
|
||||||
@ -1122,6 +1250,7 @@ class VoteMotionPollPseudoanonymousAutoupdates(TestCase):
|
|||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"user_has_voted": user == self.user,
|
"user_has_voted": user == self.user,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -1177,7 +1306,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
poll = MotionPoll.objects.get()
|
poll = MotionPoll.objects.get()
|
||||||
@ -1199,11 +1328,11 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "N"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "N"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "A"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "A"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
option = MotionPoll.objects.get().options.get()
|
option = MotionPoll.objects.get().options.get()
|
||||||
@ -1219,7 +1348,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
config["general_system_enable_anonymous"] = True
|
config["general_system_enable_anonymous"] = True
|
||||||
guest_client = APIClient()
|
guest_client = APIClient()
|
||||||
response = guest_client.post(
|
response = guest_client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), "Y"
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": "Y"}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -1227,21 +1356,27 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
def test_vote_wrong_state(self):
|
def test_vote_wrong_state(self):
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_vote_wrong_group(self):
|
def test_vote_wrong_group(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
def test_vote_not_present(self):
|
def test_vote_not_present(self):
|
||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
self.assertHttpStatusVerbose(response, status.HTTP_403_FORBIDDEN)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
@ -1249,7 +1384,9 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.start_poll()
|
self.start_poll()
|
||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(reverse("motionpoll-vote", args=[self.poll.pk]))
|
response = self.client.post(
|
||||||
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": {}}
|
||||||
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
|
|
||||||
@ -1258,7 +1395,7 @@ class VoteMotionPollPseudoanonymous(TestCase):
|
|||||||
self.make_admin_delegate()
|
self.make_admin_delegate()
|
||||||
self.make_admin_present()
|
self.make_admin_present()
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motionpoll-vote", args=[self.poll.pk]), [1, 2, 5]
|
reverse("motionpoll-vote", args=[self.poll.pk]), {"data": [1, 2, 5]}
|
||||||
)
|
)
|
||||||
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
self.assertHttpStatusVerbose(response, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
self.assertFalse(MotionPoll.objects.get().get_votes().exists())
|
||||||
@ -1344,6 +1481,7 @@ class PublishMotionPoll(TestCase):
|
|||||||
"options_id": [1],
|
"options_id": [1],
|
||||||
"id": 1,
|
"id": 1,
|
||||||
"user_has_voted": False,
|
"user_has_voted": False,
|
||||||
|
"user_has_voted_for_delegations": [],
|
||||||
"voted_id": [],
|
"voted_id": [],
|
||||||
},
|
},
|
||||||
"motions/motion-vote:1": {
|
"motions/motion-vote:1": {
|
||||||
@ -1353,6 +1491,7 @@ class PublishMotionPoll(TestCase):
|
|||||||
"weight": "2.000000",
|
"weight": "2.000000",
|
||||||
"value": "N",
|
"value": "N",
|
||||||
"user_id": None,
|
"user_id": None,
|
||||||
|
"delegated_user_id": None,
|
||||||
},
|
},
|
||||||
"motions/motion-option:1": {
|
"motions/motion-option:1": {
|
||||||
"abstain": "0.000000",
|
"abstain": "0.000000",
|
||||||
@ -1495,3 +1634,45 @@ class ResetMotionPoll(TestCase):
|
|||||||
for user in (self.admin, self.user1, self.user2):
|
for user in (self.admin, self.user1, self.user2):
|
||||||
self.assertDeletedAutoupdate(self.vote1, user=user)
|
self.assertDeletedAutoupdate(self.vote1, user=user)
|
||||||
self.assertDeletedAutoupdate(self.vote2, user=user)
|
self.assertDeletedAutoupdate(self.vote2, user=user)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMotionPollWithVoteDelegationAutoupdate(TestCase):
|
||||||
|
def advancedSetUp(self):
|
||||||
|
""" Set up user -> other_user delegation. """
|
||||||
|
self.motion = Motion(
|
||||||
|
title="test_title_dL91JqhMTiQuQLSDRItZ",
|
||||||
|
text="test_text_R7nURdXKVEfEnnJBXJYa",
|
||||||
|
)
|
||||||
|
self.motion.save()
|
||||||
|
|
||||||
|
self.delegate_group = get_group_model().objects.get(pk=GROUP_DELEGATE_PK)
|
||||||
|
|
||||||
|
self.other_user, _ = self.create_user()
|
||||||
|
self.user, user_password = self.create_user()
|
||||||
|
self.user.groups.add(self.delegate_group)
|
||||||
|
self.user.is_present = True
|
||||||
|
self.user.vote_delegated_to = self.other_user
|
||||||
|
self.user.save()
|
||||||
|
|
||||||
|
self.user_client = APIClient()
|
||||||
|
self.user_client.login(username=self.user.username, password=user_password)
|
||||||
|
|
||||||
|
self.poll = MotionPoll.objects.create(
|
||||||
|
motion=self.motion,
|
||||||
|
title="test_title_Q3EuRaALSCCPJuQ2tMqj",
|
||||||
|
pollmethod="YNA",
|
||||||
|
type=BasePoll.TYPE_NAMED,
|
||||||
|
onehundred_percent_base="YN",
|
||||||
|
majority_method="simple",
|
||||||
|
)
|
||||||
|
self.poll.create_options()
|
||||||
|
self.poll.groups.add(self.delegate_group)
|
||||||
|
self.poll.save()
|
||||||
|
|
||||||
|
def test_start_poll(self):
|
||||||
|
response = self.client.post(reverse("motionpoll-start", args=[self.poll.pk]))
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
# other_user has to receive an autoupdate because he was delegated
|
||||||
|
autoupdate = self.get_last_autoupdate(user=self.other_user)
|
||||||
|
assert "motions/motion-poll:1" in autoupdate[0]
|
||||||
|
@ -24,12 +24,13 @@ def test_user_db_queries():
|
|||||||
"""
|
"""
|
||||||
Tests that only the following db queries are done:
|
Tests that only the following db queries are done:
|
||||||
* 2 requests to get the list of all users and
|
* 2 requests to get the list of all users and
|
||||||
* 1 requests to get the list of all groups.
|
* 1 request to get all vote delegations
|
||||||
|
* 1 request to get the list of all groups.
|
||||||
"""
|
"""
|
||||||
for index in range(10):
|
for index in range(10):
|
||||||
User.objects.create(username=f"user{index}")
|
User.objects.create(username=f"user{index}")
|
||||||
|
|
||||||
assert count_queries(User.get_elements)() == 3
|
assert count_queries(User.get_elements)() == 4
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=False)
|
@pytest.mark.django_db(transaction=False)
|
||||||
@ -232,6 +233,188 @@ class UserUpdate(TestCase):
|
|||||||
# The user is not allowed to change some other fields (like last_name).
|
# The user is not allowed to change some other fields (like last_name).
|
||||||
self.assertNotEqual(user.last_name, "New name fae1Bu1Eyeis9eRox4xu")
|
self.assertNotEqual(user.last_name, "New name fae1Bu1Eyeis9eRox4xu")
|
||||||
|
|
||||||
|
def test_update_vote_delegation(self):
|
||||||
|
user = User.objects.create_user(
|
||||||
|
username="non-admin Yd4ejrJXZi4Wn16ugHgY",
|
||||||
|
password="non-admin AQ4Dw2tN9byKpGD4f1gs",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[user.pk]),
|
||||||
|
{"vote_delegated_to_id": self.admin.pk},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
user = User.objects.get(pk=user.pk)
|
||||||
|
self.assertEqual(user.vote_delegated_to_id, self.admin.pk)
|
||||||
|
admin = User.objects.get(username="admin")
|
||||||
|
self.assertEqual(
|
||||||
|
list(admin.vote_delegated_from_users.values_list("id", flat=True)),
|
||||||
|
[user.pk],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_update_vote_delegation_non_admin(self):
|
||||||
|
user = User.objects.create_user(
|
||||||
|
username="non-admin WpBQRSsCg6qNWNtN6bLP",
|
||||||
|
password="non-admin IzsDBt1uoqc2wo5BSUF1",
|
||||||
|
)
|
||||||
|
client = APIClient()
|
||||||
|
client.login(
|
||||||
|
username="non-admin WpBQRSsCg6qNWNtN6bLP",
|
||||||
|
password="non-admin IzsDBt1uoqc2wo5BSUF1",
|
||||||
|
)
|
||||||
|
|
||||||
|
response = client.patch(
|
||||||
|
reverse("user-detail", args=[user.pk]),
|
||||||
|
{"vote_delegated_to_id": self.admin.pk},
|
||||||
|
)
|
||||||
|
|
||||||
|
# self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
user = User.objects.get(pk=user.pk)
|
||||||
|
self.assertIsNone(user.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_vote_delegated_to_self(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.admin.pk]),
|
||||||
|
{"vote_delegated_to_id": self.admin.pk},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
admin = User.objects.get(pk=self.admin.pk)
|
||||||
|
self.assertIsNone(admin.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_vote_delegation_invalid_id(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.admin.pk]),
|
||||||
|
{"vote_delegated_to_id": 42},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
admin = User.objects.get(pk=self.admin.pk)
|
||||||
|
self.assertIsNone(admin.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_vote_delegated_from_self(self):
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.admin.pk]),
|
||||||
|
{"vote_delegated_from_users_id": [self.admin.pk]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
admin = User.objects.get(pk=self.admin.pk)
|
||||||
|
self.assertIsNone(admin.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def setup_vote_delegation(self):
|
||||||
|
""" login and setup user -> user2 delegation """
|
||||||
|
self.user, _ = self.create_user()
|
||||||
|
self.user2, _ = self.create_user()
|
||||||
|
self.user.vote_delegated_to = self.user2
|
||||||
|
self.user.save()
|
||||||
|
self.assertEqual(self.user.vote_delegated_to_id, self.user2.pk)
|
||||||
|
|
||||||
|
def test_update_reset_vote_delegated_to(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.user.pk]),
|
||||||
|
{"vote_delegated_to_id": None},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertEqual(user.vote_delegated_to_id, None)
|
||||||
|
|
||||||
|
def test_update_reset_vote_delegated_from(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.user2.pk]),
|
||||||
|
{"vote_delegated_from_users_id": None},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertEqual(user.vote_delegated_to_id, None)
|
||||||
|
|
||||||
|
def test_update_nested_vote_delegation_1(self):
|
||||||
|
""" user -> user2 -> admin """
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.user2.pk]),
|
||||||
|
{"vote_delegated_to_id": self.admin.pk},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
user2 = User.objects.get(pk=self.user2.pk)
|
||||||
|
self.assertIsNone(user2.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_nested_vote_delegation_2(self):
|
||||||
|
""" admin -> user -> user2 """
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.admin.pk]),
|
||||||
|
{"vote_delegated_to_id": self.user.pk},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
admin = User.objects.get(pk=self.admin.pk)
|
||||||
|
self.assertIsNone(admin.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_vote_delegation_autoupdate(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.user.pk]),
|
||||||
|
{"vote_delegated_to_id": self.admin.pk},
|
||||||
|
)
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
|
||||||
|
autoupdate = self.get_last_autoupdate(user=self.admin)
|
||||||
|
user_au = autoupdate[0].get(f"users/user:{self.user.pk}")
|
||||||
|
self.assertIsNotNone(user_au)
|
||||||
|
self.assertEqual(user_au["vote_delegated_to_id"], self.admin.pk)
|
||||||
|
user2_au = autoupdate[0].get(f"users/user:{self.user2.pk}")
|
||||||
|
self.assertIsNotNone(user2_au)
|
||||||
|
self.assertEqual(user2_au["vote_delegated_from_users_id"], [])
|
||||||
|
admin_au = autoupdate[0].get(f"users/user:{self.admin.pk}")
|
||||||
|
self.assertIsNotNone(admin_au)
|
||||||
|
self.assertEqual(admin_au["vote_delegated_from_users_id"], [self.user.pk])
|
||||||
|
self.assertEqual(autoupdate[1], [])
|
||||||
|
|
||||||
|
def test_update_vote_delegated_from(self):
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.user2.pk]),
|
||||||
|
{"vote_delegated_from_users_id": [self.admin.pk]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertHttpStatusVerbose(response, status.HTTP_200_OK)
|
||||||
|
admin = User.objects.get(pk=self.admin.pk)
|
||||||
|
self.assertEqual(admin.vote_delegated_to_id, self.user2.id)
|
||||||
|
user = User.objects.get(pk=self.user.pk)
|
||||||
|
self.assertIsNone(user.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_vote_delegated_from_nested_1(self):
|
||||||
|
""" admin -> user -> user2 """
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.user.pk]),
|
||||||
|
{"vote_delegated_from_users_id": [self.admin.pk]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
admin = User.objects.get(pk=self.admin.pk)
|
||||||
|
self.assertIsNone(admin.vote_delegated_to_id)
|
||||||
|
|
||||||
|
def test_update_vote_delegated_from_nested_2(self):
|
||||||
|
""" user -> user2 -> admin """
|
||||||
|
self.setup_vote_delegation()
|
||||||
|
response = self.client.patch(
|
||||||
|
reverse("user-detail", args=[self.admin.pk]),
|
||||||
|
{"vote_delegated_from_users_id": [self.user2.pk]},
|
||||||
|
)
|
||||||
|
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
user2 = User.objects.get(pk=self.user2.pk)
|
||||||
|
self.assertIsNone(user2.vote_delegated_to_id)
|
||||||
|
|
||||||
|
|
||||||
class UserDelete(TestCase):
|
class UserDelete(TestCase):
|
||||||
"""
|
"""
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
|
from decimal import Decimal
|
||||||
from unittest import TestCase
|
from unittest import TestCase
|
||||||
|
|
||||||
from openslides.motions.models import Motion, MotionChangeRecommendation
|
from openslides.motions.models import Motion, MotionChangeRecommendation, MotionPoll
|
||||||
|
|
||||||
|
|
||||||
# TODO: test for MotionPoll.set_options()
|
# TODO: test for MotionPoll.set_options()
|
||||||
@ -50,3 +51,25 @@ class MotionChangeRecommendationTest(TestCase):
|
|||||||
other_recommendations
|
other_recommendations
|
||||||
)
|
)
|
||||||
self.assertFalse(collides)
|
self.assertFalse(collides)
|
||||||
|
|
||||||
|
|
||||||
|
class MotionPollAnalogFieldsTest(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.motion = Motion(
|
||||||
|
title="test_title_OoK9IeChe2Jeib9Deeji",
|
||||||
|
text="test_text_eichui1oobiSeit9aifo",
|
||||||
|
)
|
||||||
|
self.poll = MotionPoll(
|
||||||
|
motion=self.motion,
|
||||||
|
title="test_title_tho8PhiePh8upaex6phi",
|
||||||
|
pollmethod="YNA",
|
||||||
|
type=MotionPoll.TYPE_NAMED,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_not_set_vote_values(self):
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.poll.votesvalid = Decimal("1")
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.poll.votesinvalid = Decimal("1")
|
||||||
|
with self.assertRaises(ValueError):
|
||||||
|
self.poll.votescast = Decimal("1")
|
||||||
|
Loading…
Reference in New Issue
Block a user