added vote per user table and progress for polls
added update for options after stopping a poll
This commit is contained in:
parent
604df9d48b
commit
682db96b7c
@ -9,6 +9,7 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
|
|||||||
import { RelationDefinition } from 'app/core/definitions/relations';
|
import { RelationDefinition } from 'app/core/definitions/relations';
|
||||||
import { VotingService } from 'app/core/ui-services/voting.service';
|
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 { 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';
|
||||||
import { BasePollRepositoryService } from 'app/site/polls/services/base-poll-repository.service';
|
import { BasePollRepositoryService } from 'app/site/polls/services/base-poll-repository.service';
|
||||||
@ -35,6 +36,12 @@ const MotionPollRelations: RelationDefinition[] = [
|
|||||||
ownIdKey: 'options_id',
|
ownIdKey: 'options_id',
|
||||||
ownKey: 'options',
|
ownKey: 'options',
|
||||||
foreignViewModel: ViewMotionOption
|
foreignViewModel: ViewMotionOption
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2O',
|
||||||
|
ownIdKey: 'motion_id',
|
||||||
|
ownKey: 'motion',
|
||||||
|
foreignViewModel: ViewMotion
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -35,7 +35,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): boolean {
|
||||||
return !this.getVotePermissionError(poll);
|
const error = this.getVotePermissionError(poll);
|
||||||
|
return !error;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -7,10 +7,11 @@ import { AssignmentPollDetailComponent } from './components/assignment-poll-deta
|
|||||||
import { AssignmentPollVoteComponent } from './components/assignment-poll-vote/assignment-poll-vote.component';
|
import { AssignmentPollVoteComponent } from './components/assignment-poll-vote/assignment-poll-vote.component';
|
||||||
import { AssignmentPollComponent } from './components/assignment-poll/assignment-poll.component';
|
import { AssignmentPollComponent } from './components/assignment-poll/assignment-poll.component';
|
||||||
import { AssignmentsRoutingModule } from './assignments-routing.module';
|
import { AssignmentsRoutingModule } from './assignments-routing.module';
|
||||||
|
import { PollsModule } from '../polls/polls.module';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, AssignmentsRoutingModule, SharedModule],
|
imports: [CommonModule, AssignmentsRoutingModule, SharedModule, PollsModule],
|
||||||
declarations: [
|
declarations: [
|
||||||
AssignmentDetailComponent,
|
AssignmentDetailComponent,
|
||||||
AssignmentListComponent,
|
AssignmentListComponent,
|
||||||
|
@ -38,30 +38,13 @@
|
|||||||
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div *ngIf="poll.state === 2">
|
||||||
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div *ngIf="poll.state === 3 || poll.state === 4">
|
<div *ngIf="poll.state === 3 || poll.state === 4">
|
||||||
<h2 translate>Result</h2>
|
<h2 translate>Result</h2>
|
||||||
|
|
||||||
<div
|
|
||||||
*ngIf="poll.type === 'named'"
|
|
||||||
style="display: grid; grid-template-columns: auto repeat({{ poll.options.length }}, max-content);"
|
|
||||||
>
|
|
||||||
<!-- top left cell is empty -->
|
|
||||||
<div></div>
|
|
||||||
<!-- header (the assignment related users) -->
|
|
||||||
<ng-container *ngFor="let option of poll.options">
|
|
||||||
<div *ngIf="option.user">{{ option.user.full_name }}</div>
|
|
||||||
<div *ngIf="!option.user">{{ 'Unknown user' | translate }}</div>
|
|
||||||
</ng-container>
|
|
||||||
<!-- rows -->
|
|
||||||
<ng-container *ngFor="let obj of votesByUser | keyvalue">
|
|
||||||
<div *ngIf="obj.value.user">{{ obj.value.user.full_name }}</div>
|
|
||||||
<div *ngIf="!obj.value.user">{{ 'Unknown user' | translate }}</div>
|
|
||||||
<ng-container *ngFor="let option of poll.options">
|
|
||||||
<div>{{ obj.value.votes[option.user_id] }}</div>
|
|
||||||
</ng-container>
|
|
||||||
</ng-container>
|
|
||||||
</div>
|
|
||||||
<div class="chart-wrapper"></div>
|
<div class="chart-wrapper"></div>
|
||||||
<mat-table [dataSource]="poll.tableData">
|
<mat-table [dataSource]="poll.tableData">
|
||||||
<ng-container matColumnDef="user" sticky>
|
<ng-container matColumnDef="user" sticky>
|
||||||
@ -88,9 +71,10 @@
|
|||||||
<mat-cell *matCellDef="let row"></mat-cell>
|
<mat-cell *matCellDef="let row"></mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
<mat-header-row *matHeaderRowDef="columnDefinitionOverview"></mat-header-row>
|
||||||
<mat-row *matRowDef="let row; columns: columnDefinition"></mat-row>
|
<mat-row *matRowDef="let row; columns: columnDefinitionOverview"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<os-charts
|
<os-charts
|
||||||
*ngIf="chartDataSubject.value"
|
*ngIf="chartDataSubject.value"
|
||||||
[type]="chartType"
|
[type]="chartType"
|
||||||
@ -98,6 +82,31 @@
|
|||||||
[showLegend]="true"
|
[showLegend]="true"
|
||||||
[data]="chartDataSubject"
|
[data]="chartDataSubject"
|
||||||
></os-charts>
|
></os-charts>
|
||||||
|
|
||||||
|
<ng-container *ngIf="poll.type === 'named' && votesDataSource.data">
|
||||||
|
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter"/>
|
||||||
|
<mat-table [dataSource]="votesDataSource">
|
||||||
|
<ng-container matColumnDef="users" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ "User" | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">
|
||||||
|
<div *ngIf="row.user">{{ row.user.getFullName() }}</div>
|
||||||
|
<div *ngIf="!row.user">{{ "Unknown user" | translate }}</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container [matColumnDef]="'votes-' + option.user_id" *ngFor="let option of poll.options" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>
|
||||||
|
<div *ngIf="option.user">{{ option.user.getFullName() }}</div>
|
||||||
|
<div *ngIf="!option.user">{{ "Unknown user" | translate }}</div>
|
||||||
|
</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let row">
|
||||||
|
{{ row.votes[option.user_id] }}
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
|
<mat-header-row *matHeaderRowDef="columnDefinitionPerName"></mat-header-row>
|
||||||
|
<mat-row *matRowDef="let row; columns: columnDefinitionPerName"></mat-row>
|
||||||
|
</mat-table>
|
||||||
|
</ng-container>
|
||||||
</div>
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
@ -106,7 +115,7 @@
|
|||||||
<mat-menu #pollDetailMenu="matMenu">
|
<mat-menu #pollDetailMenu="matMenu">
|
||||||
<os-projector-button [menuItem]="true" [object]="poll" *osPerms="'core.can_manage_projector'"></os-projector-button>
|
<os-projector-button [menuItem]="true" [object]="poll" *osPerms="'core.can_manage_projector'"></os-projector-button>
|
||||||
<button mat-menu-item *ngIf="poll && poll.type === 'named'" (click)="pseudoanonymizePoll()">
|
<button mat-menu-item *ngIf="poll && poll.type === 'named'" (click)="pseudoanonymizePoll()">
|
||||||
<mat-icon>questionmark</mat-icon>
|
<mat-icon>polymer</mat-icon>
|
||||||
<span translate>Pseudoanonymize</span>
|
<span translate>Pseudoanonymize</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
@ -12,10 +12,8 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
|||||||
import { ChartType } from 'app/shared/components/charts/charts.component';
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
||||||
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
|
||||||
import { ViewUser } from 'app/site/users/models/view-user';
|
|
||||||
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
|
||||||
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
|
||||||
import { ViewAssignmentVote } from '../../models/view-assignment-vote';
|
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'os-assignment-poll-detail',
|
selector: 'os-assignment-poll-detail',
|
||||||
@ -27,20 +25,20 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
|
|
||||||
public candidatesLabels: string[] = [];
|
public candidatesLabels: string[] = [];
|
||||||
|
|
||||||
public votesByUser: { [key: number]: { user: ViewUser; votes: { [key: number]: ViewAssignmentVote } } };
|
|
||||||
|
|
||||||
public get chartType(): ChartType {
|
public get chartType(): ChartType {
|
||||||
return 'horizontalBar';
|
return 'horizontalBar';
|
||||||
}
|
}
|
||||||
|
|
||||||
public get columnDefinition(): string[] {
|
public get columnDefinitionOverview(): string[] {
|
||||||
const columns = ['user', 'yes', 'no', 'quorum'];
|
const columns = ['user', 'yes', 'no', 'quorum'];
|
||||||
if ((<ViewAssignmentPoll>this.poll).pollmethod === AssignmentPollMethods.YNA) {
|
if (this.poll.pollmethod === AssignmentPollMethods.YNA) {
|
||||||
columns.splice(3, 0, 'abstain');
|
columns.splice(3, 0, 'abstain');
|
||||||
}
|
}
|
||||||
return columns;
|
return columns;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public columnDefinitionPerName: string[];
|
||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
title: Title,
|
title: Title,
|
||||||
translate: TranslateService,
|
translate: TranslateService,
|
||||||
@ -55,27 +53,32 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
|
|||||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
public onPollLoaded(): void {
|
public onPollWithOptionsLoaded(): void {
|
||||||
const votes = {};
|
this.columnDefinitionPerName = ['users'].concat(this.poll.options.map(option => 'votes-' + option.user_id));
|
||||||
|
|
||||||
setTimeout(() => {
|
const votes = {};
|
||||||
for (const option of this.poll.options) {
|
let i = -1;
|
||||||
for (const vote of option.votes) {
|
for (const option of this.poll.options) {
|
||||||
if (!votes[vote.user_id]) {
|
for (const vote of option.votes) {
|
||||||
votes[vote.user_id] = {
|
// if poll was pseudoanonymized, use a negative index to not interfere with
|
||||||
user: vote.user,
|
// possible named votes (although this should never happen)
|
||||||
votes: {}
|
const userId = vote.user_id || i--;
|
||||||
};
|
if (!votes[userId]) {
|
||||||
}
|
votes[userId] = {
|
||||||
votes[vote.user_id].votes[option.user_id] =
|
user: vote.user,
|
||||||
this.poll.pollmethod === AssignmentPollMethods.Votes ? vote.weight : vote.valueVerbose;
|
votes: {}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
votes[userId].votes[option.user_id] =
|
||||||
|
this.poll.pollmethod === AssignmentPollMethods.Votes ? vote.weight : vote.valueVerbose;
|
||||||
}
|
}
|
||||||
console.log(votes, this.poll, this.poll.options);
|
}
|
||||||
this.votesByUser = votes;
|
|
||||||
this.candidatesLabels = this.poll.initChartLabels();
|
this.setVotesData(Object.values(votes));
|
||||||
this.isReady = true;
|
|
||||||
});
|
this.candidatesLabels = this.poll.initChartLabels();
|
||||||
|
|
||||||
|
this.isReady = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hasPerms(): boolean {
|
protected hasPerms(): boolean {
|
||||||
|
@ -4,6 +4,7 @@ import { PollColor } from 'app/shared/models/poll/base-poll';
|
|||||||
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
import { ProjectorElementBuildDeskriptor } from 'app/site/base/projectable';
|
||||||
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
|
||||||
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||||
|
import { ViewMotion } from './view-motion';
|
||||||
|
|
||||||
export interface MotionPollTitleInformation {
|
export interface MotionPollTitleInformation {
|
||||||
title: string;
|
title: string;
|
||||||
@ -72,5 +73,6 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll> implements MotionPo
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ViewMotionPoll extends MotionPoll {
|
export interface ViewMotionPoll extends MotionPoll {
|
||||||
|
motion: ViewMotion;
|
||||||
options: ViewMotionOption[];
|
options: ViewMotionOption[];
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<os-head-bar [goBack]="true" [nav]="false">
|
<os-head-bar [goBack]="true" [nav]="false">
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 *ngIf="motion">{{ 'Motion' | translate }} {{ motion.id }}</h2>
|
<h2 *ngIf="poll">{{ 'Motion' | translate }} {{ poll.motion.id }}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="menu-slot" *osPerms="'agenda.can_manage'; or: 'agenda.can_see_list_of_speakers'">
|
<div class="menu-slot" *osPerms="'agenda.can_manage'; or: 'agenda.can_see_list_of_speakers'">
|
||||||
@ -51,17 +51,26 @@
|
|||||||
<mat-row *matRowDef="let row; columns: columnDefinition"></mat-row>
|
<mat-row *matRowDef="let row; columns: columnDefinition"></mat-row>
|
||||||
</mat-table>
|
</mat-table>
|
||||||
|
|
||||||
<!-- Named table -->
|
<!-- Named table: only show if votes are present -->
|
||||||
<!-- The table was created in another PR -->
|
<ng-container *ngIf="poll.type === 'named' && votesDataSource.data">
|
||||||
<div class="named-result-table" *ngIf="poll.type === 'named'">
|
<input matInput [(ngModel)]="votesDataSource.filter" placeholder="Filter"/>
|
||||||
<h3>{{ 'Singe votes' | translate }}</h3>
|
<mat-table [dataSource]="votesDataSource">
|
||||||
|
<ng-container matColumnDef="key" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ "User" | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let vote">
|
||||||
|
<div *ngIf="vote.user">{{ vote.user.getFullName() }}</div>
|
||||||
|
<div *ngIf="!vote.user">{{ 'Unknown user' | translate }}</div>
|
||||||
|
</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
<ng-container matColumnDef="value" sticky>
|
||||||
|
<mat-header-cell *matHeaderCellDef>{{ "Vote" | translate }}</mat-header-cell>
|
||||||
|
<mat-cell *matCellDef="let vote">{{ vote.valueVerbose }}</mat-cell>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<div *ngFor="let vote of poll.options[0].votes">
|
<mat-header-row *matHeaderRowDef="columnDefinition"></mat-header-row>
|
||||||
<div *ngIf="vote.user">{{ vote.user.full_name }}</div>
|
<mat-row *matRowDef="let vote; columns: columnDefinition"></mat-row>
|
||||||
<div *ngIf="!vote.user">{{ 'Unknown user' | translate }}</div>
|
</mat-table>
|
||||||
<div>{{ vote.valueVerbose }}</div>
|
</ng-container>
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -76,6 +85,10 @@
|
|||||||
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
<div>{{ 'Majority method' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="poll.state === 2">
|
||||||
|
<os-poll-progress [poll]="poll"></os-poll-progress>
|
||||||
|
</div>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
@ -87,7 +100,7 @@
|
|||||||
<span translate>Edit</span>
|
<span translate>Edit</span>
|
||||||
</button>
|
</button>
|
||||||
<button mat-menu-item *ngIf="poll && poll.type === 'named'" (click)="pseudoanonymizePoll()">
|
<button mat-menu-item *ngIf="poll && poll.type === 'named'" (click)="pseudoanonymizePoll()">
|
||||||
<mat-icon>questionmark</mat-icon>
|
<mat-icon>polymer</mat-icon>
|
||||||
<span translate>Pseudoanonymize</span>
|
<span translate>Pseudoanonymize</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
@ -7,7 +7,6 @@ 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 { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
|
|
||||||
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
|
||||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||||
import { ChartType } from 'app/shared/components/charts/charts.component';
|
import { ChartType } from 'app/shared/components/charts/charts.component';
|
||||||
@ -45,14 +44,13 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
|
|||||||
prompt: PromptService,
|
prompt: PromptService,
|
||||||
pollDialog: MotionPollDialogService,
|
pollDialog: MotionPollDialogService,
|
||||||
private operator: OperatorService,
|
private operator: OperatorService,
|
||||||
private router: Router,
|
private router: Router
|
||||||
private motionRepo: MotionRepositoryService
|
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
super(title, translate, matSnackbar, repo, route, groupRepo, prompt, pollDialog);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected onPollLoaded(): void {
|
protected onPollWithOptionsLoaded(): void {
|
||||||
this.motion = this.motionRepo.getViewModel((<ViewMotionPoll>this.poll).motion_id);
|
this.setVotesData(this.poll.options[0].votes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public openDialog(): void {
|
public openDialog(): void {
|
||||||
@ -61,7 +59,7 @@ export class MotionPollDetailComponent extends BasePollDetailComponent<ViewMotio
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected onDeleted(): void {
|
protected onDeleted(): void {
|
||||||
this.router.navigate(['motions', (<ViewMotionPoll>this.poll).motion_id]);
|
this.router.navigate(['motions', this.poll.motion_id]);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected hasPerms(): boolean {
|
protected hasPerms(): boolean {
|
||||||
|
@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { SharedModule } from 'app/shared/shared.module';
|
import { SharedModule } from 'app/shared/shared.module';
|
||||||
|
import { PollsModule } from 'app/site/polls/polls.module';
|
||||||
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
import { MotionPollDetailComponent } from './motion-poll-detail/motion-poll-detail.component';
|
||||||
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
import { MotionPollListComponent } from './motion-poll-list/motion-poll-list.component';
|
||||||
import { MotionPollRoutingModule } from './motion-poll-routing.module';
|
import { MotionPollRoutingModule } from './motion-poll-routing.module';
|
||||||
@ -9,7 +10,7 @@ import { MotionPollVoteComponent } from './motion-poll-vote/motion-poll-vote.com
|
|||||||
import { MotionPollComponent } from './motion-poll/motion-poll.component';
|
import { MotionPollComponent } from './motion-poll/motion-poll.component';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, SharedModule, MotionPollRoutingModule],
|
imports: [CommonModule, SharedModule, MotionPollRoutingModule, PollsModule],
|
||||||
exports: [MotionPollComponent],
|
exports: [MotionPollComponent],
|
||||||
declarations: [MotionPollComponent, MotionPollDetailComponent, MotionPollListComponent, MotionPollVoteComponent]
|
declarations: [MotionPollComponent, MotionPollDetailComponent, MotionPollListComponent, MotionPollVoteComponent]
|
||||||
})
|
})
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { OnInit } from '@angular/core';
|
import { OnInit } from '@angular/core';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar, MatTableDataSource } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
import { ActivatedRoute } from '@angular/router';
|
import { ActivatedRoute } from '@angular/router';
|
||||||
|
|
||||||
@ -15,9 +15,14 @@ import { ChartData, ChartType } from 'app/shared/components/charts/charts.compon
|
|||||||
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
import { PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
import { ViewGroup } from 'app/site/users/models/view-group';
|
import { ViewGroup } from 'app/site/users/models/view-group';
|
||||||
|
import { ViewUser } from 'app/site/users/models/view-user';
|
||||||
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
import { BasePollRepositoryService } from '../services/base-poll-repository.service';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
|
export interface BaseVoteData {
|
||||||
|
user?: ViewUser;
|
||||||
|
}
|
||||||
|
|
||||||
export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewComponent implements OnInit {
|
export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends BaseViewComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* All the groups of users.
|
* All the groups of users.
|
||||||
@ -55,6 +60,9 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
*/
|
*/
|
||||||
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject(null);
|
public chartDataSubject: BehaviorSubject<ChartData> = new BehaviorSubject(null);
|
||||||
|
|
||||||
|
// The datasource for the votes-per-user table
|
||||||
|
public votesDataSource: MatTableDataSource<BaseVoteData> = new MatTableDataSource();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -81,6 +89,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
protected pollDialog: BasePollDialogService<V>
|
protected pollDialog: BasePollDialogService<V>
|
||||||
) {
|
) {
|
||||||
super(title, translate, matSnackbar);
|
super(title, translate, matSnackbar);
|
||||||
|
this.votesDataSource.filterPredicate = this.dataSourceFilterPredicate;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -100,7 +109,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
const text = 'Do you really want to delete the selected poll?';
|
const text = 'Do you really want to delete the selected poll?';
|
||||||
|
|
||||||
if (await this.promptDialog.open(title, text)) {
|
if (await this.promptDialog.open(title, text)) {
|
||||||
this.repo.delete(this.poll).then(() => this.onDeleted());
|
this.repo.delete(this.poll).then(() => this.onDeleted(), this.raiseError);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +118,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
const text = 'Do you really want to pseudoanonymize the selected poll?';
|
const text = 'Do you really want to pseudoanonymize the selected poll?';
|
||||||
|
|
||||||
if (await this.promptDialog.open(title, text)) {
|
if (await this.promptDialog.open(title, text)) {
|
||||||
await this.repo.pseudoanonymize(this.poll);
|
this.repo.pseudoanonymize(this.poll).then(() => this.onPollLoaded(), this.raiseError); // votes have changed, but not the poll, so the components have to be informed about the update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,10 +138,35 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
*/
|
*/
|
||||||
protected onPollLoaded(): void {}
|
protected onPollLoaded(): void {}
|
||||||
|
|
||||||
|
protected onPollWithOptionsLoaded(): void {}
|
||||||
|
|
||||||
protected onStateChanged(): void {}
|
protected onStateChanged(): void {}
|
||||||
|
|
||||||
protected abstract hasPerms(): boolean;
|
protected abstract hasPerms(): boolean;
|
||||||
|
|
||||||
|
// custom filter for the data source: only search in usernames
|
||||||
|
protected dataSourceFilterPredicate(data: BaseVoteData, filter: string): boolean {
|
||||||
|
return (
|
||||||
|
data.user &&
|
||||||
|
data.user
|
||||||
|
.getFullName()
|
||||||
|
.trim()
|
||||||
|
.toLowerCase()
|
||||||
|
.indexOf(filter.trim().toLowerCase()) !== -1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* sets the votes data only if the poll wasn't pseudoanonymized
|
||||||
|
*/
|
||||||
|
protected setVotesData(data: BaseVoteData[]): void {
|
||||||
|
if (data.every(voteDate => !voteDate.user)) {
|
||||||
|
this.votesDataSource.data = null;
|
||||||
|
} else {
|
||||||
|
this.votesDataSource.data = data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This checks, if the poll has votes.
|
* This checks, if the poll has votes.
|
||||||
*/
|
*/
|
||||||
@ -155,6 +189,15 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
|
|||||||
this.updateBreadcrumbs();
|
this.updateBreadcrumbs();
|
||||||
this.checkData();
|
this.checkData();
|
||||||
this.onPollLoaded();
|
this.onPollLoaded();
|
||||||
|
|
||||||
|
// wait for options to be loaded
|
||||||
|
(function waitForOptions(): void {
|
||||||
|
if (!this.poll.options || !this.poll.options.length) {
|
||||||
|
setTimeout(waitForOptions.bind(this), 1);
|
||||||
|
} else {
|
||||||
|
this.onPollWithOptionsLoaded();
|
||||||
|
}
|
||||||
|
}.call(this));
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
<span>{{ this.poll.voted_id.length }} von {{ this.max }} haben abgestimmt.</span>
|
@ -0,0 +1,26 @@
|
|||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { E2EImportsModule } from 'e2e-imports.module';
|
||||||
|
|
||||||
|
import { PollProgressComponent } from './poll-progress.component';
|
||||||
|
|
||||||
|
describe('PollProgressComponent', () => {
|
||||||
|
let component: PollProgressComponent;
|
||||||
|
let fixture: ComponentFixture<PollProgressComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(PollProgressComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,44 @@
|
|||||||
|
import { Component, Input, OnInit } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material';
|
||||||
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { map } from 'rxjs/operators';
|
||||||
|
|
||||||
|
import { UserRepositoryService } from 'app/core/repositories/users/user-repository.service';
|
||||||
|
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||||
|
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-poll-progress',
|
||||||
|
templateUrl: './poll-progress.component.html',
|
||||||
|
styleUrls: ['./poll-progress.component.scss']
|
||||||
|
})
|
||||||
|
export class PollProgressComponent extends BaseViewComponent implements OnInit {
|
||||||
|
@Input()
|
||||||
|
public poll: ViewBasePoll;
|
||||||
|
|
||||||
|
public max: number;
|
||||||
|
|
||||||
|
public constructor(
|
||||||
|
title: Title,
|
||||||
|
protected translate: TranslateService,
|
||||||
|
snackbar: MatSnackBar,
|
||||||
|
private userRepo: UserRepositoryService
|
||||||
|
) {
|
||||||
|
super(title, translate, snackbar);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OnInit.
|
||||||
|
* Sets the observable for groups.
|
||||||
|
*/
|
||||||
|
public ngOnInit(): void {
|
||||||
|
this.userRepo
|
||||||
|
.getViewModelListObservable()
|
||||||
|
.pipe(map(users => users.filter(user => this.poll.groups_id.intersect(user.groups_id).length)))
|
||||||
|
.subscribe(users => {
|
||||||
|
this.max = users.length;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ import { CommonModule } from '@angular/common';
|
|||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
import { PollListComponent } from './components/poll-list/poll-list.component';
|
import { PollListComponent } from './components/poll-list/poll-list.component';
|
||||||
|
import { PollProgressComponent } from './components/poll-progress/poll-progress.component';
|
||||||
import { PollsRoutingModule } from './polls-routing.module';
|
import { PollsRoutingModule } from './polls-routing.module';
|
||||||
import { SharedModule } from '../../shared/shared.module';
|
import { SharedModule } from '../../shared/shared.module';
|
||||||
|
|
||||||
@ -11,6 +12,7 @@ import { SharedModule } from '../../shared/shared.module';
|
|||||||
*/
|
*/
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [CommonModule, PollsRoutingModule, SharedModule],
|
imports: [CommonModule, PollsRoutingModule, SharedModule],
|
||||||
declarations: [PollListComponent]
|
exports: [PollProgressComponent],
|
||||||
|
declarations: [PollListComponent, PollProgressComponent]
|
||||||
})
|
})
|
||||||
export class PollsModule {}
|
export class PollsModule {}
|
||||||
|
@ -2,10 +2,12 @@ import { Injectable } from '@angular/core';
|
|||||||
|
|
||||||
import { BehaviorSubject, Observable } from 'rxjs';
|
import { BehaviorSubject, Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { CollectionStringMapperService } from 'app/core/core-services/collection-string-mapper.service';
|
||||||
import { HasViewModelListObservable } from 'app/core/definitions/has-view-model-list-observable';
|
import { HasViewModelListObservable } from 'app/core/definitions/has-view-model-list-observable';
|
||||||
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
import { AssignmentPollRepositoryService } from 'app/core/repositories/assignments/assignment-poll-repository.service';
|
||||||
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
|
||||||
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
|
||||||
|
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||||
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
|
||||||
import { ViewBasePoll } from '../models/view-base-poll';
|
import { ViewBasePoll } from '../models/view-base-poll';
|
||||||
|
|
||||||
@ -21,7 +23,8 @@ export class PollListObservableService implements HasViewModelListObservable<Vie
|
|||||||
|
|
||||||
public constructor(
|
public constructor(
|
||||||
motionPollRepo: MotionPollRepositoryService,
|
motionPollRepo: MotionPollRepositoryService,
|
||||||
assignmentPollRepo: AssignmentPollRepositoryService
|
assignmentPollRepo: AssignmentPollRepositoryService,
|
||||||
|
private mapper: CollectionStringMapperService
|
||||||
) {
|
) {
|
||||||
motionPollRepo
|
motionPollRepo
|
||||||
.getViewModelListObservable()
|
.getViewModelListObservable()
|
||||||
@ -41,4 +44,8 @@ export class PollListObservableService implements HasViewModelListObservable<Vie
|
|||||||
public getViewModelListObservable(): Observable<ViewBasePoll[]> {
|
public getViewModelListObservable(): Observable<ViewBasePoll[]> {
|
||||||
return this.viewPollListSubject.asObservable();
|
return this.viewPollListSubject.asObservable();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getObservableFromViewModel(poll: ViewBasePoll): Observable<BaseViewModel> {
|
||||||
|
return this.mapper.getRepository(poll.collectionString).getViewModelObservable(poll.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,6 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
return super().update(request, *args, **kwargs)
|
return super().update(request, *args, **kwargs)
|
||||||
|
|
||||||
def handle_request_with_votes(self, request, poll):
|
def handle_request_with_votes(self, request, poll):
|
||||||
print(poll, poll.type, BasePoll.TYPE_ANALOG)
|
|
||||||
if poll.type != BasePoll.TYPE_ANALOG:
|
if poll.type != BasePoll.TYPE_ANALOG:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
{"detail": "You cannot enter votes for a non-analog poll."}
|
{"detail": "You cannot enter votes for a non-analog poll."}
|
||||||
@ -142,6 +141,7 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
poll.state = BasePoll.STATE_FINISHED
|
poll.state = BasePoll.STATE_FINISHED
|
||||||
poll.save()
|
poll.save()
|
||||||
inform_changed_data(poll.get_votes())
|
inform_changed_data(poll.get_votes())
|
||||||
|
inform_changed_data(poll.get_options())
|
||||||
return Response()
|
return Response()
|
||||||
|
|
||||||
@detail_route(methods=["POST"])
|
@detail_route(methods=["POST"])
|
||||||
@ -233,7 +233,9 @@ class BasePollViewSet(ModelViewSet):
|
|||||||
if poll.state != BasePoll.STATE_STARTED:
|
if poll.state != BasePoll.STATE_STARTED:
|
||||||
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, poll.groups.all(), exact=True
|
request.user.id,
|
||||||
|
list(poll.groups.values_list("pk", flat=True)),
|
||||||
|
exact=True,
|
||||||
):
|
):
|
||||||
self.permission_denied(request)
|
self.permission_denied(request)
|
||||||
|
|
||||||
|
@ -7,7 +7,6 @@ from django.contrib.auth import get_user_model
|
|||||||
from django.contrib.auth.models import AnonymousUser
|
from django.contrib.auth.models import AnonymousUser
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.db.models.query import QuerySet
|
|
||||||
|
|
||||||
from .cache import element_cache
|
from .cache import element_cache
|
||||||
|
|
||||||
@ -112,9 +111,8 @@ async def async_has_perm(user_id: int, perm: str) -> bool:
|
|||||||
return has_perm
|
return has_perm
|
||||||
|
|
||||||
|
|
||||||
def in_some_groups(
|
# async code doesn't work well with QuerySets, so we have to give a list of ints for groups
|
||||||
user_id: int, groups: Union[List[int], QuerySet], exact: bool = False
|
def in_some_groups(user_id: int, groups: List[int], exact: bool = False) -> bool:
|
||||||
) -> bool:
|
|
||||||
"""
|
"""
|
||||||
Checks that user is in at least one given group. Groups can be given as a list
|
Checks that user is in at least one given group. Groups can be given as a list
|
||||||
of ids or a QuerySet.
|
of ids or a QuerySet.
|
||||||
@ -134,7 +132,7 @@ def in_some_groups(
|
|||||||
|
|
||||||
|
|
||||||
async def async_in_some_groups(
|
async def async_in_some_groups(
|
||||||
user_id: int, groups: Union[List[int], QuerySet], exact: bool = False
|
user_id: int, groups: List[int], exact: bool = False
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Checks that user is in at least one given group. Groups can be given as a list
|
Checks that user is in at least one given group. Groups can be given as a list
|
||||||
@ -143,9 +141,6 @@ async def async_in_some_groups(
|
|||||||
|
|
||||||
user_id 0 means anonymous user.
|
user_id 0 means anonymous user.
|
||||||
"""
|
"""
|
||||||
if isinstance(groups, QuerySet):
|
|
||||||
groups = [group.pk for group in groups]
|
|
||||||
|
|
||||||
if not user_id and not await async_anonymous_is_enabled():
|
if not user_id and not await async_anonymous_is_enabled():
|
||||||
in_some_groups = False
|
in_some_groups = False
|
||||||
elif not user_id:
|
elif not user_id:
|
||||||
|
@ -382,7 +382,6 @@ class ManageSpeaker(TestCase):
|
|||||||
self.assertEqual(response.status_code, 403)
|
self.assertEqual(response.status_code, 403)
|
||||||
|
|
||||||
def test_remove_someone_else(self):
|
def test_remove_someone_else(self):
|
||||||
print(self.user)
|
|
||||||
speaker = Speaker.objects.add(self.user, self.list_of_speakers)
|
speaker = Speaker.objects.add(self.user, self.list_of_speakers)
|
||||||
response = self.client.delete(
|
response = self.client.delete(
|
||||||
reverse("listofspeakers-manage-speaker", args=[self.list_of_speakers.pk]),
|
reverse("listofspeakers-manage-speaker", args=[self.list_of_speakers.pk]),
|
||||||
|
@ -708,6 +708,7 @@ class VoteAssignmentPollBaseTestClass(TestCase):
|
|||||||
self.admin.save()
|
self.admin.save()
|
||||||
self.poll.groups.add(GROUP_ADMIN_PK)
|
self.poll.groups.add(GROUP_ADMIN_PK)
|
||||||
self.poll.create_options()
|
self.poll.create_options()
|
||||||
|
inform_changed_data(self.poll)
|
||||||
|
|
||||||
def create_poll(self):
|
def create_poll(self):
|
||||||
# has to be implemented by subclasses
|
# has to be implemented by subclasses
|
||||||
|
@ -70,6 +70,7 @@ class TestCreation(TestCase):
|
|||||||
"is_directory": True,
|
"is_directory": True,
|
||||||
"mediafile": self.file,
|
"mediafile": self.file,
|
||||||
},
|
},
|
||||||
|
format="multipart",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertFalse(Mediafile.objects.exists())
|
self.assertFalse(Mediafile.objects.exists())
|
||||||
@ -79,6 +80,7 @@ class TestCreation(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("mediafile-list"),
|
reverse("mediafile-list"),
|
||||||
{"title": "test_title_vai8oDogohheideedie4", "mediafile": file},
|
{"title": "test_title_vai8oDogohheideedie4", "mediafile": file},
|
||||||
|
format="multipart",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
mediafile = Mediafile.objects.get()
|
mediafile = Mediafile.objects.get()
|
||||||
@ -90,6 +92,7 @@ class TestCreation(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("mediafile-list"),
|
reverse("mediafile-list"),
|
||||||
{"title": "test_title_Zeicheipeequie3ohfid", "mediafile": file1},
|
{"title": "test_title_Zeicheipeequie3ohfid", "mediafile": file1},
|
||||||
|
format="multipart",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
mediafile = Mediafile.objects.get()
|
mediafile = Mediafile.objects.get()
|
||||||
@ -98,6 +101,7 @@ class TestCreation(TestCase):
|
|||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("mediafile-list"),
|
reverse("mediafile-list"),
|
||||||
{"title": "test_title_aiChaetohs0quicee9eb", "mediafile": file2},
|
{"title": "test_title_aiChaetohs0quicee9eb", "mediafile": file2},
|
||||||
|
format="multipart",
|
||||||
)
|
)
|
||||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
self.assertEqual(Mediafile.objects.count(), 1)
|
self.assertEqual(Mediafile.objects.count(), 1)
|
||||||
|
Loading…
Reference in New Issue
Block a user