Projector for polls: Server, client structure and data modeling
This commit is contained in:
parent
84a39ccb62
commit
e2585fb757
|
@ -33,14 +33,13 @@ export class ViewAssignmentPoll extends ViewBasePoll<AssignmentPoll> implements
|
||||||
// TODO: update to new voting system?
|
// TODO: update to new voting system?
|
||||||
return {
|
return {
|
||||||
getBasicProjectorElement: options => ({
|
getBasicProjectorElement: options => ({
|
||||||
name: 'assignments/assignment-poll',
|
name: AssignmentPoll.COLLECTIONSTRING,
|
||||||
assignment_id: this.assignment_id,
|
id: this.id,
|
||||||
poll_id: this.id,
|
getIdentifiers: () => ['name', 'id']
|
||||||
getIdentifiers: () => ['name', 'assignment_id', 'poll_id']
|
|
||||||
}),
|
}),
|
||||||
slideOptions: [],
|
slideOptions: [],
|
||||||
projectionDefaultName: 'assignment-poll',
|
projectionDefaultName: 'assignment_poll',
|
||||||
getDialogTitle: () => 'TODO'
|
getDialogTitle: this.getTitle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -62,7 +62,7 @@ export class ViewMotionPoll extends ViewBasePoll<MotionPoll> implements MotionPo
|
||||||
getIdentifiers: () => ['name', 'id']
|
getIdentifiers: () => ['name', 'id']
|
||||||
}),
|
}),
|
||||||
slideOptions: [],
|
slideOptions: [],
|
||||||
projectionDefaultName: 'motion-poll',
|
projectionDefaultName: 'motion_poll',
|
||||||
getDialogTitle: this.getTitle
|
getDialogTitle: this.getTitle
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,6 +87,8 @@
|
||||||
<div>{{ 'Required majority' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
<div>{{ 'Required majority' | translate }}: {{ poll.majorityMethodVerbose | translate }}</div>
|
||||||
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
<div>{{ '100% base' | translate }}: {{ poll.percentBaseVerbose | translate }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<os-projector-button [object]="poll"></os-projector-button>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</ng-template>
|
</ng-template>
|
||||||
|
|
||||||
|
|
|
@ -25,6 +25,11 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[
|
||||||
scaleable: true,
|
scaleable: true,
|
||||||
scrollable: true
|
scrollable: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slide: 'motions/motion-poll',
|
||||||
|
scaleable: true,
|
||||||
|
scrollable: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slide: 'users/user',
|
slide: 'users/user',
|
||||||
scaleable: true,
|
scaleable: true,
|
||||||
|
@ -83,7 +88,7 @@ export const allSlidesDynamicConfiguration: (SlideDynamicConfiguration & Slide)[
|
||||||
scrollable: true
|
scrollable: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slide: 'assignments/poll',
|
slide: 'assignments/assignment-poll',
|
||||||
scaleable: true,
|
scaleable: true,
|
||||||
scrollable: true
|
scrollable: true
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,6 +41,14 @@ export const allSlides: SlideManifest[] = [
|
||||||
elementIdentifiers: ['name', 'id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
slide: 'motions/motion-poll',
|
||||||
|
path: 'motions/motion-poll',
|
||||||
|
loadChildren: './slides/motions/motion-poll/motion-poll-slide.module#MotionPollSlideModule',
|
||||||
|
verboseName: 'Motion Poll',
|
||||||
|
elementIdentifiers: ['name', 'id'],
|
||||||
|
canBeMappedToModel: true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
slide: 'users/user',
|
slide: 'users/user',
|
||||||
path: 'users/user',
|
path: 'users/user',
|
||||||
|
@ -126,12 +134,12 @@ export const allSlides: SlideManifest[] = [
|
||||||
canBeMappedToModel: true
|
canBeMappedToModel: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slide: 'assignments/poll',
|
slide: 'assignments/assignment-poll',
|
||||||
path: 'assignments/poll',
|
path: 'assignments/assignment-poll',
|
||||||
loadChildren: () => import('./assignments/poll/poll-slide.module').then(m => m.PollSlideModule),
|
loadChildren: () => import('./assignments/poll/poll-slide.module').then(m => m.PollSlideModule),
|
||||||
verboseName: 'Poll',
|
verboseName: 'Assignment Poll',
|
||||||
elementIdentifiers: ['name', 'assignment_id', 'poll_id'],
|
elementIdentifiers: ['name', 'id'],
|
||||||
canBeMappedToModel: false
|
canBeMappedToModel: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
slide: 'mediafiles/mediafile',
|
slide: 'mediafiles/mediafile',
|
||||||
|
|
|
@ -0,0 +1,31 @@
|
||||||
|
import { AssignmentPollMethods } from 'app/shared/models/assignments/assignment-poll';
|
||||||
|
import { MajorityMethod, PercentBase, PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { AssignmentTitleInformation } from 'app/site/assignments/models/view-assignment';
|
||||||
|
|
||||||
|
export interface AssignmentPollSlideData {
|
||||||
|
assignment: AssignmentTitleInformation;
|
||||||
|
poll: {
|
||||||
|
title: string;
|
||||||
|
type: PollType;
|
||||||
|
pollmethod: AssignmentPollMethods;
|
||||||
|
votes_amount: number;
|
||||||
|
description: string;
|
||||||
|
state: PollState;
|
||||||
|
onehundered_percent_base: PercentBase;
|
||||||
|
majority_method: MajorityMethod;
|
||||||
|
|
||||||
|
options: {
|
||||||
|
user: string;
|
||||||
|
yes?: string;
|
||||||
|
no?: string;
|
||||||
|
abstain?: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
// optional for published polls:
|
||||||
|
amount_global_no?: string;
|
||||||
|
amount_global_abstain: string;
|
||||||
|
votesvalid: string;
|
||||||
|
votesinvalid: string;
|
||||||
|
votescast: string;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div *ngIf="data">
|
||||||
|
|
||||||
|
<pre>{{ verboseData }}</pre>
|
||||||
|
|
||||||
|
</div>
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AssignmentPollSlideComponent } from './assignment-poll-slide.component';
|
||||||
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
|
|
||||||
|
describe('AssignmentPollSlideComponent', () => {
|
||||||
|
let component: AssignmentPollSlideComponent;
|
||||||
|
let fixture: ComponentFixture<AssignmentPollSlideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async(() => {
|
||||||
|
TestBed.configureTestingModule({
|
||||||
|
imports: [E2EImportsModule],
|
||||||
|
declarations: [AssignmentPollSlideComponent]
|
||||||
|
}).compileComponents();
|
||||||
|
}));
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
fixture = TestBed.createComponent(AssignmentPollSlideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
|
import { AssignmentPollSlideData } from './assignment-poll-slide-data';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-assignment-poll-slide',
|
||||||
|
templateUrl: './assignment-poll-slide.component.html',
|
||||||
|
styleUrls: ['./assignment-poll-slide.component.scss']
|
||||||
|
})
|
||||||
|
export class AssignmentPollSlideComponent extends BaseSlideComponent<AssignmentPollSlideData> {
|
||||||
|
public get verboseData(): string {
|
||||||
|
return JSON.stringify(this.data, null, 2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { AssignmentPollSlideModule } from './assignment-poll-slide.module';
|
||||||
|
|
||||||
|
describe('AssignmentPollSlideModule', () => {
|
||||||
|
let assignmentPollSlideModule: AssignmentPollSlideModule;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
assignmentPollSlideModule = new AssignmentPollSlideModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(assignmentPollSlideModule).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||||
|
import { AssignmentPollSlideComponent } from './assignment-poll-slide.component';
|
||||||
|
|
||||||
|
@NgModule(makeSlideModule(AssignmentPollSlideComponent))
|
||||||
|
export class AssignmentPollSlideModule {}
|
|
@ -1,27 +0,0 @@
|
||||||
import { PollVoteValue } from 'app/site/polls/services/poll.service';
|
|
||||||
|
|
||||||
export interface PollSlideOption {
|
|
||||||
user: string;
|
|
||||||
is_elected: boolean;
|
|
||||||
votes: {
|
|
||||||
weight: string;
|
|
||||||
value: PollVoteValue;
|
|
||||||
}[];
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PollSlideData {
|
|
||||||
title: string;
|
|
||||||
assignments_poll_100_percent_base: any /*AssignmentPercentBase*/;
|
|
||||||
poll: {
|
|
||||||
published: boolean;
|
|
||||||
description?: string;
|
|
||||||
has_votes?: boolean;
|
|
||||||
pollmethod?: any /*AssignmentPollmethods*/;
|
|
||||||
votesno?: string;
|
|
||||||
votesabstain?: string;
|
|
||||||
votesvalid?: string;
|
|
||||||
votesinvalid?: string;
|
|
||||||
votescast?: string;
|
|
||||||
options?: PollSlideOption[];
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
<div *ngIf="data">
|
|
||||||
<div class="slidetitle">
|
|
||||||
<h1>{{ data.data.title }}</h1>
|
|
||||||
<h2 translate>Election result</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="spacer-top-10"></div>
|
|
||||||
<div *ngIf="!data.data.poll.published"><span translate>Waiting for results</span><span>...</span></div>
|
|
||||||
<div *ngIf="data.data.poll.published">
|
|
||||||
<div *ngIf="data.data.poll.has_votes" class="result-table">
|
|
||||||
<div class="row">
|
|
||||||
<div class="option-name heading">
|
|
||||||
<h3 translate>Candidates</h3>
|
|
||||||
</div>
|
|
||||||
<div class="option-percents heading">
|
|
||||||
<h3 translate>Votes</h3>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngFor="let option of data.data.poll.options">
|
|
||||||
<div class="row">
|
|
||||||
<div class="option-name">
|
|
||||||
<span class="bold">{{ option.user }}</span>
|
|
||||||
<mat-icon *ngIf="option.is_elected">star</mat-icon>
|
|
||||||
</div>
|
|
||||||
<div class="option-percents">
|
|
||||||
<div *ngFor="let vote of option.votes" class="bold">
|
|
||||||
<span *ngIf="vote.value !== 'Votes'">{{ getLabel(vote.value) }}:</span>
|
|
||||||
<span> {{ getVotePercent(vote.value, option) }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div *ngFor="let value of pollValues" class="row">
|
|
||||||
<div class="option-name grey">
|
|
||||||
<span>{{ getLabel(value) }}</span>
|
|
||||||
</div>
|
|
||||||
<div class="option-percents grey">
|
|
||||||
{{ getPollPercent(value) }}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
|
@ -1,31 +0,0 @@
|
||||||
.row {
|
|
||||||
border-top: 1px solid #ddd;
|
|
||||||
display: table;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.heading {
|
|
||||||
text-transform: uppercase;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: normal;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-name {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 5px;
|
|
||||||
vertical-align: middle;
|
|
||||||
width: 70%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.option-percents {
|
|
||||||
display: table-cell;
|
|
||||||
padding: 5px;
|
|
||||||
width: 30%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grey {
|
|
||||||
background-color: #ddd !important;
|
|
||||||
color: rgba(0, 0, 0, 0.87) !important;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
import { Component, Input } from '@angular/core';
|
|
||||||
|
|
||||||
import { SlideData } from 'app/core/core-services/projector-data.service';
|
|
||||||
import { CalculablePollKey, PollVoteValue } from 'app/site/polls/services/poll.service';
|
|
||||||
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
|
||||||
import { PollSlideData, PollSlideOption } from './poll-slide-data';
|
|
||||||
|
|
||||||
@Component({
|
|
||||||
selector: 'os-poll-slide',
|
|
||||||
templateUrl: './poll-slide.component.html',
|
|
||||||
styleUrls: ['./poll-slide.component.scss']
|
|
||||||
})
|
|
||||||
export class PollSlideComponent extends BaseSlideComponent<PollSlideData> {
|
|
||||||
private _data: SlideData<PollSlideData>;
|
|
||||||
|
|
||||||
public get pollValues(): any {
|
|
||||||
// SummaryPollKey[] {
|
|
||||||
if (!this.data) {
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
const values: any /*SummaryPollKey[]*/ = ['votesno', 'votesabstain', 'votesvalid', 'votesinvalid', 'votescast'];
|
|
||||||
return values.filter(val => this.data.data.poll[val] !== null);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Input()
|
|
||||||
public set data(data: SlideData<PollSlideData>) {
|
|
||||||
this._data = data;
|
|
||||||
/*this.calculationData = {
|
|
||||||
pollMethod: data.data.poll.pollmethod,
|
|
||||||
votesno: parseFloat(data.data.poll.votesno),
|
|
||||||
votesabstain: parseFloat(data.data.poll.votesabstain),
|
|
||||||
votescast: parseFloat(data.data.poll.votescast),
|
|
||||||
votesvalid: parseFloat(data.data.poll.votesvalid),
|
|
||||||
votesinvalid: parseFloat(data.data.poll.votesinvalid),
|
|
||||||
pollOptions: data.data.poll.options.map(opt => {
|
|
||||||
return {
|
|
||||||
votes: opt.votes.map(vote => {
|
|
||||||
return {
|
|
||||||
weight: parseFloat(vote.weight),
|
|
||||||
value: vote.value
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
percentBase: data.data.assignments_poll_100_percent_base
|
|
||||||
};*/
|
|
||||||
}
|
|
||||||
|
|
||||||
public get data(): SlideData<PollSlideData> {
|
|
||||||
return this._data;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* get a vote's numerical or special label, including percent values if these are to
|
|
||||||
* be displayed
|
|
||||||
*
|
|
||||||
* @param key
|
|
||||||
* @param option
|
|
||||||
*/
|
|
||||||
public getVotePercent(key: PollVoteValue, option: PollSlideOption): string {
|
|
||||||
/*const calcOption = {
|
|
||||||
votes: option.votes.map(vote => {
|
|
||||||
return { weight: parseFloat(vote.weight), value: vote.value };
|
|
||||||
})
|
|
||||||
};
|
|
||||||
const percent = this.pollService.getPercent(this.calculationData, calcOption, key);
|
|
||||||
const number = this.translate.instant(
|
|
||||||
this.pollService.getSpecialLabel(parseFloat(option.votes.find(v => v.value === key).weight))
|
|
||||||
);
|
|
||||||
return percent === null ? number : `${number} (${percent}%)`;*/
|
|
||||||
throw new Error('TODO');
|
|
||||||
}
|
|
||||||
|
|
||||||
public getPollPercent(key: CalculablePollKey): string {
|
|
||||||
/*const percent = this.pollService.getValuePercent(this.calculationData, key);
|
|
||||||
const number = this.translate.instant(this.pollService.getSpecialLabel(this.calculationData[key]));
|
|
||||||
return percent === null ? number : `${number} (${percent}%)`;*/
|
|
||||||
throw new Error('TODO');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns a translated label for a key
|
|
||||||
*/
|
|
||||||
public getLabel(key: CalculablePollKey): string {
|
|
||||||
// return this.translate.instant(this.pollService.getLabel(key));
|
|
||||||
throw new Error('TODO');
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,13 +0,0 @@
|
||||||
import { PollSlideModule } from './poll-slide.module';
|
|
||||||
|
|
||||||
describe('PollSlideModule', () => {
|
|
||||||
let pollSlideModule: PollSlideModule;
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
pollSlideModule = new PollSlideModule();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should create an instance', () => {
|
|
||||||
expect(pollSlideModule).toBeTruthy();
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -1,7 +0,0 @@
|
||||||
import { NgModule } from '@angular/core';
|
|
||||||
|
|
||||||
import { makeSlideModule } from 'app/slides/base-slide-module';
|
|
||||||
import { PollSlideComponent } from './poll-slide.component';
|
|
||||||
|
|
||||||
@NgModule(makeSlideModule(PollSlideComponent))
|
|
||||||
export class PollSlideModule {}
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import { MotionPollMethods } from 'app/shared/models/motions/motion-poll';
|
||||||
|
import { MajorityMethod, PercentBase, PollState, PollType } from 'app/shared/models/poll/base-poll';
|
||||||
|
import { MotionTitleInformation } from 'app/site/motions/models/view-motion';
|
||||||
|
|
||||||
|
export interface MotionPollSlideData {
|
||||||
|
motion: MotionTitleInformation;
|
||||||
|
poll: {
|
||||||
|
title: string;
|
||||||
|
type: PollType;
|
||||||
|
pollmethod: MotionPollMethods;
|
||||||
|
state: PollState;
|
||||||
|
onehundered_percent_base: PercentBase;
|
||||||
|
majority_method: MajorityMethod;
|
||||||
|
|
||||||
|
options: {
|
||||||
|
yes?: string;
|
||||||
|
no?: string;
|
||||||
|
abstain?: string;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
// optional for published polls:
|
||||||
|
votesvalid: string;
|
||||||
|
votesinvalid: string;
|
||||||
|
votescast: string;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,5 @@
|
||||||
|
<div *ngIf="data">
|
||||||
|
|
||||||
|
<pre>{{ verboseData }}</pre>
|
||||||
|
|
||||||
|
</div>
|
|
@ -1,21 +1,21 @@
|
||||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
import { E2EImportsModule } from '../../../../e2e-imports.module';
|
||||||
import { PollSlideComponent } from './poll-slide.component';
|
import { MotionPollSlideComponent } from './motion-poll-slide.component';
|
||||||
|
|
||||||
describe('PollSlideComponent', () => {
|
describe('MotionPollSlideComponent', () => {
|
||||||
let component: PollSlideComponent;
|
let component: MotionPollSlideComponent;
|
||||||
let fixture: ComponentFixture<PollSlideComponent>;
|
let fixture: ComponentFixture<MotionPollSlideComponent>;
|
||||||
|
|
||||||
beforeEach(async(() => {
|
beforeEach(async(() => {
|
||||||
TestBed.configureTestingModule({
|
TestBed.configureTestingModule({
|
||||||
imports: [E2EImportsModule],
|
imports: [E2EImportsModule],
|
||||||
declarations: [PollSlideComponent]
|
declarations: [MotionPollSlideComponent]
|
||||||
}).compileComponents();
|
}).compileComponents();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
fixture = TestBed.createComponent(PollSlideComponent);
|
fixture = TestBed.createComponent(MotionPollSlideComponent);
|
||||||
component = fixture.componentInstance;
|
component = fixture.componentInstance;
|
||||||
fixture.detectChanges();
|
fixture.detectChanges();
|
||||||
});
|
});
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
import { BaseSlideComponent } from 'app/slides/base-slide-component';
|
||||||
|
import { MotionPollSlideData } from './motion-poll-slide-data';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'os-motion-poll-slide',
|
||||||
|
templateUrl: './motion-poll-slide.component.html',
|
||||||
|
styleUrls: ['./motion-poll-slide.component.scss']
|
||||||
|
})
|
||||||
|
export class MotionPollSlideComponent extends BaseSlideComponent<MotionPollSlideData> {
|
||||||
|
public get verboseData(): string {
|
||||||
|
return JSON.stringify(this.data, null, 2);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,13 @@
|
||||||
|
import { MotionPollSlideModule } from './motion-poll-slide.module';
|
||||||
|
|
||||||
|
describe('MotionPollSlideModule', () => {
|
||||||
|
let motionPollSlideModule: MotionPollSlideModule;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
motionPollSlideModule = new MotionPollSlideModule();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create an instance', () => {
|
||||||
|
expect(motionPollSlideModule).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
|
@ -0,0 +1,7 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
|
||||||
|
import { makeSlideModule } from 'app/slides/base-slide-module';
|
||||||
|
import { MotionPollSlideComponent } from './motion-poll-slide.component';
|
||||||
|
|
||||||
|
@NgModule(makeSlideModule(MotionPollSlideComponent))
|
||||||
|
export class MotionPollSlideModule {}
|
|
@ -1,12 +1,8 @@
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
|
||||||
from ..users.projector import get_user_name
|
from ..users.projector import get_user_name
|
||||||
from ..utils.projector import (
|
from ..utils.projector import AllData, get_model, register_projector_slide
|
||||||
AllData,
|
from .models import AssignmentPoll
|
||||||
ProjectorElementException,
|
|
||||||
get_config,
|
|
||||||
register_projector_slide,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
# Important: All functions have to be prune. This means, that thay can only
|
# Important: All functions have to be prune. This means, that thay can only
|
||||||
|
@ -14,24 +10,13 @@ from ..utils.projector import (
|
||||||
# side effects.
|
# side effects.
|
||||||
|
|
||||||
|
|
||||||
def get_assignment(all_data: AllData, id: Any) -> Dict[str, Any]:
|
|
||||||
if id is None:
|
|
||||||
raise ProjectorElementException("id is required for assignment slide")
|
|
||||||
|
|
||||||
try:
|
|
||||||
assignment = all_data["assignments/assignment"][id]
|
|
||||||
except KeyError:
|
|
||||||
raise ProjectorElementException(f"assignment with id {id} does not exist")
|
|
||||||
return assignment
|
|
||||||
|
|
||||||
|
|
||||||
async def assignment_slide(
|
async def assignment_slide(
|
||||||
all_data: AllData, element: Dict[str, Any], projector_id: int
|
all_data: AllData, element: Dict[str, Any], projector_id: int
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Assignment slide.
|
Assignment slide.
|
||||||
"""
|
"""
|
||||||
assignment = get_assignment(all_data, element.get("id"))
|
assignment = get_model(all_data, "assignments/assignment", element.get("id"))
|
||||||
|
|
||||||
assignment_related_users: List[Dict[str, Any]] = [
|
assignment_related_users: List[Dict[str, Any]] = [
|
||||||
{
|
{
|
||||||
|
@ -52,57 +37,52 @@ async def assignment_slide(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def poll_slide(
|
async def assignment_poll_slide(
|
||||||
all_data: AllData, element: Dict[str, Any], projector_id: int
|
all_data: AllData, element: Dict[str, Any], projector_id: int
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Poll slide.
|
Poll slide.
|
||||||
"""
|
"""
|
||||||
assignment = get_assignment(all_data, element.get("assignment_id"))
|
poll = get_model(all_data, "assignments/assignment-poll", element.get("id"))
|
||||||
|
assignment = get_model(all_data, "assignments/assignment", poll["assignment_id"])
|
||||||
|
|
||||||
# get poll
|
poll_data = {
|
||||||
poll_id = element.get("poll_id")
|
key: poll[key]
|
||||||
if poll_id is None:
|
for key in (
|
||||||
raise ProjectorElementException("id is required for poll slide")
|
"title",
|
||||||
|
"type",
|
||||||
|
"pollmethod",
|
||||||
|
"votes_amount",
|
||||||
|
"description",
|
||||||
|
"state",
|
||||||
|
"onehundred_percent_base",
|
||||||
|
"majority_method",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
poll = None
|
# Add options:
|
||||||
for p in assignment["polls"]:
|
poll_data["options"] = []
|
||||||
if p["id"] == poll_id:
|
for option in sorted(poll["options"], key=lambda option: option["weight"]):
|
||||||
poll = p
|
option_data = {"user": await get_user_name(all_data, option["user_id"])}
|
||||||
break
|
if poll["state"] == AssignmentPoll.STATE_PUBLISHED:
|
||||||
if poll is None:
|
option_data["yes"] = option["yes"]
|
||||||
raise ProjectorElementException(f"poll with id {poll_id} does not exist")
|
option_data["no"] = option["no"]
|
||||||
|
option_data["abstain"] = option["abstain"]
|
||||||
|
poll_data["options"].append(option_data)
|
||||||
|
|
||||||
poll_data = {"published": poll["published"]}
|
if poll["state"] == AssignmentPoll.STATE_PUBLISHED:
|
||||||
|
poll_data["amount_global_no"] = poll["amount_global_no"]
|
||||||
if poll["published"]:
|
poll_data["amount_global_abstain"] = poll["amount_global_abstain"]
|
||||||
poll_data["description"] = poll["description"]
|
|
||||||
poll_data["has_votes"] = poll["has_votes"]
|
|
||||||
poll_data["pollmethod"] = poll["pollmethod"]
|
|
||||||
poll_data["votesno"] = poll["votesno"]
|
|
||||||
poll_data["votesabstain"] = poll["votesabstain"]
|
|
||||||
poll_data["votesvalid"] = poll["votesvalid"]
|
poll_data["votesvalid"] = poll["votesvalid"]
|
||||||
poll_data["votesinvalid"] = poll["votesinvalid"]
|
poll_data["votesinvalid"] = poll["votesinvalid"]
|
||||||
poll_data["votescast"] = poll["votescast"]
|
poll_data["votescast"] = poll["votescast"]
|
||||||
|
|
||||||
poll_data["options"] = [
|
|
||||||
{
|
|
||||||
"user": await get_user_name(all_data, option["candidate_id"]),
|
|
||||||
"is_elected": option["is_elected"],
|
|
||||||
"votes": option["votes"],
|
|
||||||
}
|
|
||||||
for option in sorted(poll["options"], key=lambda option: option["weight"])
|
|
||||||
]
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"title": assignment["title"],
|
"assignment": {"title": assignment["title"]},
|
||||||
"assignments_poll_100_percent_base": await get_config(
|
|
||||||
all_data, "assignments_poll_100_percent_base"
|
|
||||||
),
|
|
||||||
"poll": poll_data,
|
"poll": poll_data,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def register_projector_slides() -> None:
|
def register_projector_slides() -> None:
|
||||||
register_projector_slide("assignments/assignment", assignment_slide)
|
register_projector_slide("assignments/assignment", assignment_slide)
|
||||||
register_projector_slide("assignments/poll", poll_slide)
|
register_projector_slide("assignments/assignment-poll", assignment_poll_slide)
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# Generated by Fin Stutzenstein on 2019-20-11 16:30
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
def add_poll_projection_defaults(apps, schema_editor):
|
||||||
|
"""
|
||||||
|
Adds projectiondefaults for messages and countdowns.
|
||||||
|
"""
|
||||||
|
Projector = apps.get_model("core", "Projector")
|
||||||
|
ProjectionDefault = apps.get_model("core", "ProjectionDefault")
|
||||||
|
default_projector = Projector.objects.order_by("pk").first()
|
||||||
|
|
||||||
|
projectiondefaults = []
|
||||||
|
|
||||||
|
projectiondefaults.append(
|
||||||
|
ProjectionDefault(
|
||||||
|
name="assignment_poll",
|
||||||
|
display_name="Assignment poll",
|
||||||
|
projector=default_projector,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
projectiondefaults.append(
|
||||||
|
ProjectionDefault(
|
||||||
|
name="motion_poll", display_name="Motion Poll", projector=default_projector
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create all new projectiondefaults
|
||||||
|
ProjectionDefault.objects.bulk_create(projectiondefaults)
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0029_remove_history_restricted"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.RunPython(add_poll_projection_defaults),
|
||||||
|
]
|
|
@ -6,8 +6,10 @@ from ..utils.projector import (
|
||||||
AllData,
|
AllData,
|
||||||
ProjectorElementException,
|
ProjectorElementException,
|
||||||
get_config,
|
get_config,
|
||||||
|
get_model,
|
||||||
register_projector_slide,
|
register_projector_slide,
|
||||||
)
|
)
|
||||||
|
from .models import MotionPoll
|
||||||
|
|
||||||
|
|
||||||
motion_placeholder_regex = re.compile(r"\[motion:(\d+)\]")
|
motion_placeholder_regex = re.compile(r"\[motion:(\d+)\]")
|
||||||
|
@ -91,11 +93,7 @@ async def get_amendments_for_motion(motion, all_data):
|
||||||
|
|
||||||
|
|
||||||
async def get_amendment_base_motion(amendment, all_data):
|
async def get_amendment_base_motion(amendment, all_data):
|
||||||
try:
|
motion = get_model(all_data, "motions/motion", amendment.get("parent_id"))
|
||||||
motion = all_data["motions/motion"][amendment["parent_id"]]
|
|
||||||
except KeyError:
|
|
||||||
motion_id = amendment["parent_id"]
|
|
||||||
raise ProjectorElementException(f"motion with id {motion_id} does not exist")
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"identifier": motion["identifier"],
|
"identifier": motion["identifier"],
|
||||||
|
@ -105,14 +103,9 @@ async def get_amendment_base_motion(amendment, all_data):
|
||||||
|
|
||||||
|
|
||||||
async def get_amendment_base_statute(amendment, all_data):
|
async def get_amendment_base_statute(amendment, all_data):
|
||||||
try:
|
statute = get_model(
|
||||||
statute = all_data["motions/statute-paragraph"][
|
all_data, "motions/statute-paragraph", amendment.get("statute_paragraph_id")
|
||||||
amendment["statute_paragraph_id"]
|
)
|
||||||
]
|
|
||||||
except KeyError:
|
|
||||||
statute_id = amendment["statute_paragraph_id"]
|
|
||||||
raise ProjectorElementException(f"statute with id {statute_id} does not exist")
|
|
||||||
|
|
||||||
return {"title": statute["title"], "text": statute["text"]}
|
return {"title": statute["title"], "text": statute["text"]}
|
||||||
|
|
||||||
|
|
||||||
|
@ -167,15 +160,7 @@ async def motion_slide(
|
||||||
mode = element.get(
|
mode = element.get(
|
||||||
"mode", await get_config(all_data, "motions_recommendation_text_mode")
|
"mode", await get_config(all_data, "motions_recommendation_text_mode")
|
||||||
)
|
)
|
||||||
motion_id = element.get("id")
|
motion = get_model(all_data, "motions/motion", element.get("id"))
|
||||||
|
|
||||||
if motion_id is None:
|
|
||||||
raise ProjectorElementException("id is required for motion slide")
|
|
||||||
|
|
||||||
try:
|
|
||||||
motion = all_data["motions/motion"][motion_id]
|
|
||||||
except KeyError:
|
|
||||||
raise ProjectorElementException(f"motion with id {motion_id} does not exist")
|
|
||||||
|
|
||||||
# Add submitters
|
# Add submitters
|
||||||
submitters = [
|
submitters = [
|
||||||
|
@ -270,7 +255,7 @@ async def motion_slide(
|
||||||
# Add recommendation-referencing motions
|
# Add recommendation-referencing motions
|
||||||
return_value[
|
return_value[
|
||||||
"recommendation_referencing_motions"
|
"recommendation_referencing_motions"
|
||||||
] = await get_recommendation_referencing_motions(all_data, motion_id)
|
] = await get_recommendation_referencing_motions(all_data, motion["id"])
|
||||||
|
|
||||||
return return_value
|
return return_value
|
||||||
|
|
||||||
|
@ -317,17 +302,7 @@ async def motion_block_slide(
|
||||||
"""
|
"""
|
||||||
Motion block slide.
|
Motion block slide.
|
||||||
"""
|
"""
|
||||||
motion_block_id = element.get("id")
|
motion_block = get_model(all_data, "motions/motion-block", element.get("id"))
|
||||||
|
|
||||||
if motion_block_id is None:
|
|
||||||
raise ProjectorElementException("id is required for motion block slide")
|
|
||||||
|
|
||||||
try:
|
|
||||||
motion_block = all_data["motions/motion-block"][motion_block_id]
|
|
||||||
except KeyError:
|
|
||||||
raise ProjectorElementException(
|
|
||||||
f"motion block with id {motion_block_id} does not exist"
|
|
||||||
)
|
|
||||||
|
|
||||||
# All motions in this motion block
|
# All motions in this motion block
|
||||||
motions = []
|
motions = []
|
||||||
|
@ -337,7 +312,7 @@ async def motion_block_slide(
|
||||||
|
|
||||||
# Search motions.
|
# Search motions.
|
||||||
for motion in all_data["motions/motion"].values():
|
for motion in all_data["motions/motion"].values():
|
||||||
if motion["motion_block_id"] == motion_block_id:
|
if motion["motion_block_id"] == motion_block["id"]:
|
||||||
motion_object = {
|
motion_object = {
|
||||||
"title": motion["title"],
|
"title": motion["title"],
|
||||||
"identifier": motion["identifier"],
|
"identifier": motion["identifier"],
|
||||||
|
@ -366,6 +341,40 @@ async def motion_block_slide(
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async def motion_poll_slide(
|
||||||
|
all_data: AllData, element: Dict[str, Any], projector_id: int
|
||||||
|
) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Poll slide.
|
||||||
|
"""
|
||||||
|
poll = get_model(all_data, "motions/motion-poll", element.get("id"))
|
||||||
|
motion = get_model(all_data, "motions/motion", poll["motion_id"])
|
||||||
|
|
||||||
|
poll_data = {
|
||||||
|
key: poll[key]
|
||||||
|
for key in (
|
||||||
|
"title",
|
||||||
|
"type",
|
||||||
|
"pollmethod",
|
||||||
|
"state",
|
||||||
|
"onehundred_percent_base",
|
||||||
|
"majority_method",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if poll["state"] == MotionPoll.STATE_PUBLISHED:
|
||||||
|
poll_data["options"] = poll["options"]
|
||||||
|
poll_data["votesvalid"] = poll["votesvalid"]
|
||||||
|
poll_data["votesinvalid"] = poll["votesinvalid"]
|
||||||
|
poll_data["votescast"] = poll["votescast"]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"motion": {"title": motion["title"], "identifier": motion["identifier"]},
|
||||||
|
"poll": poll_data,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
def register_projector_slides() -> None:
|
def register_projector_slides() -> None:
|
||||||
register_projector_slide("motions/motion", motion_slide)
|
register_projector_slide("motions/motion", motion_slide)
|
||||||
register_projector_slide("motions/motion-block", motion_block_slide)
|
register_projector_slide("motions/motion-block", motion_block_slide)
|
||||||
|
register_projector_slide("motions/motion-poll", motion_poll_slide)
|
||||||
|
|
|
@ -100,3 +100,18 @@ async def get_config(all_data: AllData, key: str) -> Any:
|
||||||
config_id = (await config.async_get_key_to_id())[key]
|
config_id = (await config.async_get_key_to_id())[key]
|
||||||
|
|
||||||
return all_data[config.get_collection_string()][config_id]["value"]
|
return all_data[config.get_collection_string()][config_id]["value"]
|
||||||
|
|
||||||
|
|
||||||
|
def get_model(all_data: AllData, collection: str, id: Any) -> Dict[str, Any]:
|
||||||
|
"""
|
||||||
|
Tries to get the model identified by the collection and id.
|
||||||
|
If the id is invalid or the model not found, ProjectorElementExceptions will be raised.
|
||||||
|
"""
|
||||||
|
if id is None:
|
||||||
|
raise ProjectorElementException(f"id is required for {collection} slide")
|
||||||
|
|
||||||
|
try:
|
||||||
|
model = all_data[collection][id]
|
||||||
|
except KeyError:
|
||||||
|
raise ProjectorElementException(f"{collection} with id {id} does not exist")
|
||||||
|
return model
|
||||||
|
|
Loading…
Reference in New Issue