Last changes and cleanup some todos

This commit is contained in:
FinnStutzenstein 2020-03-16 09:29:22 +01:00
parent d15c9892ed
commit 64f2720b1a
36 changed files with 133 additions and 185 deletions

View File

@ -85,11 +85,7 @@ matrix:
- "3.6"
script:
- mypy openslides/ tests/
<<<<<<< HEAD
- pytest --cov --cov-fail-under=72
=======
- pytest --cov --cov-fail-under=74
>>>>>>> Initial work for supporting voting
- pytest --cov --cov-fail-under=75
- name: "Server: Tests Python 3.7"
language: python
@ -100,11 +96,7 @@ matrix:
- isort --check-only --diff --recursive openslides tests
- black --check --diff --target-version py36 openslides tests
- mypy openslides/ tests/
<<<<<<< HEAD
- pytest --cov --cov-fail-under=72
=======
- pytest --cov --cov-fail-under=74
>>>>>>> Initial work for supporting voting
- pytest --cov --cov-fail-under=75
- name: "Server: Tests Python 3.8"
language: python
@ -115,7 +107,7 @@ matrix:
- isort --check-only --diff --recursive openslides tests
- black --check --diff --target-version py36 openslides tests
- mypy openslides/ tests/
- pytest --cov --cov-fail-under=72
- pytest --cov --cov-fail-under=75
- name: "Client: Linting"
language: node_js

View File

@ -114,14 +114,6 @@ export class AppComponent {
.subscribe(() => servertimeService.startScheduler());
}
/**
* Function to alter the normal Array.toString - function
*
* Will add a whitespace after a comma and shorten the output to
* three strings.
*
* TODO: Should be renamed
*/
private overloadArrayFunctions(): void {
Object.defineProperty(Array.prototype, 'toString', {
value: function(): string {
@ -153,7 +145,6 @@ export class AppComponent {
enumerable: false
});
// intersect
Object.defineProperty(Array.prototype, 'intersect', {
value: function<T>(other: T[]): T[] {
let a = this;
@ -167,7 +158,6 @@ export class AppComponent {
enumerable: false
});
// mapToObject
Object.defineProperty(Array.prototype, 'mapToObject', {
value: function<T>(f: (item: T) => { [key: string]: any }): { [key: string]: any } {
return this.reduce((aggr, item) => {
@ -188,18 +178,17 @@ export class AppComponent {
* Adds some functions to Set.
*/
private overloadSetFunctions(): void {
// equals
Object.defineProperty(Set.prototype, 'equals', {
value: function<T>(other: Set<T>): boolean {
const _difference = new Set(this);
const difference = new Set(this);
for (const elem of other) {
if (_difference.has(elem)) {
_difference.delete(elem);
if (difference.has(elem)) {
difference.delete(elem);
} else {
return false;
}
}
return !_difference.size;
return !difference.size;
},
enumerable: false
});

View File

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { _ } from 'app/core/translate/translation-marker';
import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { _ } from 'app/core/translate/translation-marker';
import { BannerDefinition, BannerService } from '../ui-services/banner.service';
/**

View File

@ -71,7 +71,6 @@ export abstract class PollPdfService {
* @returns the amount of ballots, depending on the config settings
*/
protected getBallotCount(): number {
// TODO: seems to be broken
switch (this.ballotCountSelection) {
case 'NUMBER_OF_ALL_PARTICIPANTS':
return this.userRepo.getViewModelList().length;

View File

@ -9,6 +9,7 @@ import { ViewModelStoreService } from 'app/core/core-services/view-model-store.s
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 { VoteValue } from 'app/shared/models/poll/base-vote';
import { ViewMotion } from 'app/site/motions/models/view-motion';
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
@ -91,7 +92,7 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
return this.translate.instant(plural ? 'Polls' : 'Poll');
};
public vote(vote: 'Y' | 'N' | 'A', poll_id: number): Promise<void> {
public vote(vote: VoteValue, poll_id: number): Promise<void> {
return this.http.post(`/rest/motions/motion-poll/${poll_id}/vote/`, JSON.stringify(vote));
}
}

View File

@ -99,7 +99,6 @@ _('Only main agenda items');
_('Topics');
_('Open requests to speak');
// ** Motions **
// config strings
// subgroup general
@ -246,7 +245,6 @@ _('Motion block');
_('The text field may not be blank.');
_('The reason field may not be blank.');
// ** Assignments **
// Assignment config strings
_('Elections');
@ -257,7 +255,7 @@ _('All valid ballots');
_('All casted ballots');
_('Disabled (no percents)');
_('Default groups with voting rights');
_('Sort election results by amount of votes')
_('Sort election results by amount of votes');
_('Put all candidates on the list of speakers');
// subgroup ballot papers
_('Ballot papers');
@ -291,7 +289,6 @@ _('Entitled to vote');
_('Voting method');
_('Amount of votes');
// ** Users **
// permission strings (see models.py of each Django app)
// agenda

View File

@ -1,8 +1,8 @@
import { Injectable } from '@angular/core';
import { _ } from 'app/core/translate/translation-marker';
import { TranslateService } from '@ngx-translate/core';
import { _ } from 'app/core/translate/translation-marker';
import { ViewAssignmentPoll } from 'app/site/assignments/models/view-assignment-poll';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { ViewBasePoll } from 'app/site/polls/models/view-base-poll';
@ -44,10 +44,7 @@ export class VotingBannerService {
const banner =
pollsToVote.length === 1
? this.createBanner(this.getTextForPoll(pollsToVote[0]), pollsToVote[0].parentLink)
: this.createBanner(
`${pollsToVote.length} ${this.translate.instant('open votes')}`,
'/polls/'
);
: this.createBanner(`${pollsToVote.length} ${this.translate.instant('open votes')}`, '/polls/');
this.sliceBanner(banner);
}

View File

@ -47,21 +47,15 @@ export class AttachmentControlComponent extends BaseFormControlComponent<ViewMed
return 'attachment-control';
}
/**
* Default constructor
*
* @param dialogService Reference to the `MatDialog`
* @param mediaService Reference for the `MediaFileRepositoryService`
*/
public constructor(
fb: FormBuilder,
fm: FocusMonitor,
formBuilder: FormBuilder,
focusMonitor: FocusMonitor,
element: ElementRef<HTMLElement>,
@Optional() @Self() public ngControl: NgControl,
private dialogService: MatDialog,
private mediaService: MediafileRepositoryService
) {
super(fb, fm, element, ngControl);
super(formBuilder, focusMonitor, element, ngControl);
}
/**
@ -102,12 +96,15 @@ export class AttachmentControlComponent extends BaseFormControlComponent<ViewMed
this.errorHandler.emit(error);
}
public onContainerClick(event: MouseEvent): void {
// TODO: implement
}
/**
* Declared as abstract in MatFormFieldControl and not required for this component
*/
public onContainerClick(event: MouseEvent): void {}
protected initializeForm(): void {
this.contentForm = this.fb.control([]);
}
protected updateForm(value: ViewMediafile[] | null): void {
this.contentForm.setValue(value || []);
}

View File

@ -15,7 +15,8 @@
</ng-container>
<ng-container *ngSwitchDefault>
<a class="banner-link" [routerLink]="banner.link" [style.cursor]="banner.link ? 'pointer' : 'default'">
<mat-icon>{{ banner.icon }}</mat-icon> <span>{{ banner.text }}</span>
<mat-icon>{{ banner.icon }}</mat-icon>
<span>{{ banner.text }}</span>
<div *ngIf="banner.subText">
{{ banner.subText | translate }}
</div>

View File

@ -76,7 +76,7 @@ export class ChartsComponent extends BaseViewComponent {
}
];
this.circleColors = !!circleColors[0].backgroundColor.length ? circleColors : null;
this.checkChartType();
this.checkAndUpdateChartType();
this.cd.detectChanges();
})
);
@ -87,7 +87,8 @@ export class ChartsComponent extends BaseViewComponent {
*/
@Input()
public set type(type: ChartType) {
this.checkChartType(type);
this._type = type;
this.checkAndUpdateChartType();
this.cd.detectChanges();
}
@ -290,12 +291,10 @@ export class ChartsComponent extends BaseViewComponent {
this.cd.detectChanges();
}
private checkChartType(chartType?: ChartType): void {
let type = chartType || this._type;
if (type === 'stackedBar') {
private checkAndUpdateChartType(): void {
if (this._type === 'stackedBar') {
this.setupBar();
type = 'horizontalBar';
this._type = 'horizontalBar';
}
this._type = type;
}
}

View File

@ -6,12 +6,6 @@
'bar action';
grid-template-columns: auto min-content;
.mat-progress-bar-buffer {
// TODO theme
// background-color: mat-color($background, card) !important;
background-color: white !important;
}
.message {
grid-area: message;
}

View File

@ -0,0 +1,10 @@
@import '~@angular/material/theming';
/** Custom component theme. Only lives in a specific scope */
@mixin os-progress-snack-bar-style($theme) {
$background: map-get($theme, background);
.mat-progress-bar-buffer {
background-color: mat-color($background, card) !important;
}
}

View File

@ -9,8 +9,10 @@
[removable]="true"
(removed)="removeItem(item.id)"
[disableRipple]="true"
>{{ item.getTitle() }} <mat-icon matChipRemove>cancel</mat-icon></mat-chip
>
{{ item.getTitle() }}
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
</mat-chip-list>
</div>
<div class="os-search-value-selector-chip-placeholder"></div>

View File

@ -119,17 +119,14 @@ export class SearchValueSelectorComponent extends BaseFormControlComponent<Selec
*/
private selectableItems: Selectable[];
/**
* Empty constructor
*/
public constructor(
protected translate: TranslateService,
fb: FormBuilder,
formBuilder: FormBuilder,
@Optional() @Self() public ngControl: NgControl,
fm: FocusMonitor,
focusMonitor: FocusMonitor,
element: ElementRef<HTMLElement>
) {
super(fb, fm, element, ngControl);
super(formBuilder, focusMonitor, element, ngControl);
}
/**

View File

@ -1,7 +1,6 @@
import { BaseModel } from './base-model';
export abstract class BaseDecimalModel<T = any> extends BaseModel<T> {
// TODO: no more elegant solution available in current Typescript
protected abstract getDecimalFields(): string[];
public deserialize(input: any): void {

View File

@ -38,6 +38,10 @@ export enum PercentBase {
Disabled = 'disabled'
}
export const VOTE_MAJORITY = -1;
export const VOTE_UNDOCUMENTED = -2;
export const LOWEST_VOTE_VALUE = VOTE_UNDOCUMENTED;
export abstract class BasePoll<
T = any,
O extends BaseOption<any> = any,

View File

@ -2,6 +2,8 @@ import { Pipe, PipeTransform } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { VOTE_MAJORITY, VOTE_UNDOCUMENTED } from '../models/poll/base-poll';
@Pipe({
name: 'parsePollNumber'
})
@ -11,9 +13,9 @@ export class ParsePollNumberPipe implements PipeTransform {
public transform(value: number): number | string {
const input = Math.trunc(value);
switch (input) {
case -1:
case VOTE_MAJORITY:
return this.translate.instant('majority');
case -2:
case VOTE_UNDOCUMENTED:
return this.translate.instant('undocumented');
default:
return input;

View File

@ -18,7 +18,7 @@ export const AssignmentsAppConfig: AppConfig = {
{
model: Assignment,
viewModel: ViewAssignment,
// searchOrder: 3, // TODO: enable, if there is a detail page and so on.
searchOrder: 3,
repository: AssignmentRepositoryService
},
{

View File

@ -308,7 +308,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
* Creates a new Poll
*/
public openDialog(): void {
// TODO: That is not really a ViewObject
const dialogData = {
collectionString: ViewAssignmentPoll.COLLECTIONSTRING,
assignment_id: this.assignment.id,

View File

@ -12,6 +12,7 @@ import { AssignmentVoteRepositoryService } from 'app/core/repositories/assignmen
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { ChartType } from 'app/shared/components/charts/charts.component';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
import { PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
@ -110,7 +111,7 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
this.isReady = true;
}
private voteValueToLabel(vote: 'Y' | 'N' | 'A'): string {
private voteValueToLabel(vote: VoteValue): string {
if (vote === 'Y') {
return this.translate.instant('Yes');
} else if (vote === 'N') {

View File

@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { PollType } from 'app/shared/models/poll/base-poll';
import { LOWEST_VOTE_VALUE, PollType } from 'app/shared/models/poll/base-poll';
import { GeneralValueVerbose, VoteValue, VoteValueVerbose } from 'app/shared/models/poll/base-vote';
import {
AssignmentPollMethodVerbose,
@ -168,30 +168,20 @@ export class AssignmentPollDialogComponent extends BasePollDialogComponent<ViewA
[option.user_id]: this.fb.group(
// for each user, create a form group with a control for each valid input (Y, N, A)
this.analogPollValues.mapToObject(value => ({
[value]: ['', [Validators.min(-2)]]
[value]: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
}))
)
}))
),
amount_global_no: ['', [Validators.min(-2)]],
amount_global_abstain: ['', [Validators.min(-2)]],
amount_global_no: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
amount_global_abstain: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
// insert all used global fields
...this.sumValues.mapToObject(sumValue => ({
[sumValue]: ['', [Validators.min(-2)]]
[sumValue]: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
}))
});
if (this.isAnalogPoll && this.pollData.poll) {
this.updateDialogVoteForm(this.pollData);
}
}
/**
* Sets a per-poll value
*
* @param value
* @param weight
*/
public setSumValue(value: any /*SummaryPollKey*/, weight: string): void {
this.pollData[value] = parseFloat(weight);
}
}

View File

@ -14,19 +14,18 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
import { VotingService } from 'app/core/ui-services/voting.service';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { PollType } from 'app/shared/models/poll/base-poll';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
// TODO: Duplicate
interface VoteActions {
vote: Vote;
vote: VoteValue;
css: string;
icon: string;
label: string;
}
type Vote = 'Y' | 'N' | 'A';
@Component({
selector: 'os-assignment-poll-vote',
templateUrl: './assignment-poll-vote.component.html',
@ -45,12 +44,12 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
title: Title,
protected translate: TranslateService,
matSnackbar: MatSnackBar,
vmanager: VotingService,
operator: OperatorService,
public vmanager: VotingService,
private pollRepo: AssignmentPollRepositoryService,
private promptService: PromptService
) {
super(title, translate, matSnackbar, vmanager, operator);
super(title, translate, matSnackbar, operator);
}
public ngOnInit(): void {
@ -116,7 +115,7 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
});
}
public saveSingleVote(optionId: number, vote: Vote): void {
public saveSingleVote(optionId: number, vote: VoteValue): void {
if (this.isGlobalOptionSelected()) {
delete this.voteRequestData.global;
}

View File

@ -10,7 +10,7 @@ import {
AssignmentPollMethod,
AssignmentPollPercentBase
} from 'app/shared/models/assignments/assignment-poll';
import { MajorityMethod } from 'app/shared/models/poll/base-poll';
import { MajorityMethod, VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
import { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
import { ViewAssignmentPoll } from '../models/view-assignment-poll';
@ -77,17 +77,16 @@ export class AssignmentPollService extends PollService {
}
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] {
// debugger;
return [
{
vote: 'amount_global_no',
showPercent: this.showPercentOfValidOrCast(poll),
hide: poll.amount_global_no === -2 || !poll.amount_global_no
hide: poll.amount_global_no === VOTE_UNDOCUMENTED || !poll.amount_global_no
},
{
vote: 'amount_global_abstain',
showPercent: this.showPercentOfValidOrCast(poll),
hide: poll.amount_global_abstain === -2 || !poll.amount_global_abstain
hide: poll.amount_global_abstain === VOTE_UNDOCUMENTED || !poll.amount_global_abstain
}
];
}

View File

@ -1628,7 +1628,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
}
public openDialog(): void {
// TODO: Could be simpler, requires a lot of refactoring
const dialogData = {
collectionString: ViewMotionPoll.COLLECTIONSTRING,
motion_id: this.motion.id,

View File

@ -5,6 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { LOWEST_VOTE_VALUE } from 'app/shared/models/poll/base-poll';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component';
import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.component';
@ -22,11 +23,11 @@ export class MotionPollDialogComponent extends BasePollDialogComponent<ViewMotio
protected pollForm: PollFormComponent<ViewMotionPoll>;
public constructor(
private fb: FormBuilder,
title: Title,
protected translate: TranslateService,
translate: TranslateService,
matSnackbar: MatSnackBar,
public dialogRef: MatDialogRef<BasePollDialogComponent<ViewMotionPoll>>,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public pollData: Partial<ViewMotionPoll>
) {
super(title, translate, matSnackbar, dialogRef);
@ -56,13 +57,13 @@ export class MotionPollDialogComponent extends BasePollDialogComponent<ViewMotio
* Pre-executed method to initialize the dialog-form depending on the poll-method.
*/
private createDialog(): void {
this.dialogVoteForm = this.fb.group({
Y: ['', [Validators.min(-2)]],
N: ['', [Validators.min(-2)]],
A: ['', [Validators.min(-2)]],
votesvalid: ['', [Validators.min(-2)]],
votesinvalid: ['', [Validators.min(-2)]],
votescast: ['', [Validators.min(-2)]]
this.dialogVoteForm = this.formBuilder.group({
Y: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
N: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
A: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
votesvalid: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
votesinvalid: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
votescast: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
});
if (this.pollData.poll) {

View File

@ -8,11 +8,12 @@ import { OperatorService } from 'app/core/core-services/operator.service';
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { VotingService } from 'app/core/ui-services/voting.service';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
interface VoteOption {
vote?: 'Y' | 'N' | 'A';
vote?: VoteValue;
css?: string;
icon?: string;
label?: string;
@ -50,18 +51,15 @@ export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPol
title: Title,
translate: TranslateService,
matSnackbar: MatSnackBar,
vmanager: VotingService,
operator: OperatorService,
public vmanager: VotingService,
private pollRepo: MotionPollRepositoryService,
private promptService: PromptService
) {
super(title, translate, matSnackbar, vmanager, operator);
super(title, translate, matSnackbar, operator);
}
/**
* TODO: 'Y' | 'N' | 'A' should refer to some ENUM
*/
public saveVote(vote: 'Y' | 'N' | 'A'): void {
public saveVote(vote: VoteValue): void {
this.currentVote.vote = vote;
const title = this.translate.instant('Submit selection now?');
const content = this.translate.instant('Your decision cannot be changed afterwards.');

View File

@ -4,6 +4,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { OperatorService } from 'app/core/core-services/operator.service';
import { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service';
import { VotingPrivacyWarningComponent } from 'app/shared/components/voting-privacy-warning/voting-privacy-warning.component';
@ -14,7 +15,6 @@ import { MotionPollPdfService } from 'app/site/motions/services/motion-poll-pdf.
import { MotionPollService } from 'app/site/motions/services/motion-poll.service';
import { BasePollComponent } from 'app/site/polls/components/base-poll.component';
import { PollService, PollTableData } from 'app/site/polls/services/poll.service';
import { OperatorService } from 'app/core/core-services/operator.service';
/**
* Component to show a motion-poll.
@ -25,10 +25,6 @@ import { OperatorService } from 'app/core/core-services/operator.service';
styleUrls: ['./motion-poll.component.scss']
})
export class MotionPollComponent extends BasePollComponent<ViewMotionPoll> {
/**
* The dedicated `ViewMotionPoll`.
* TODO: shadows superclass `poll`. Maybe change when chart data is generated?
*/
@Input()
public set poll(value: ViewMotionPoll) {
this.initPoll(value);

View File

@ -8,6 +8,7 @@ import { Label } from 'ng2-charts';
import { BehaviorSubject, from, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
import { Deferred } from 'app/core/promises/deferred';
import { BaseRepository } from 'app/core/repositories/base-repository';
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.service';
@ -73,7 +74,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
// The observable for the votes-per-user table
public votesDataObservable: Observable<BaseVoteData[]>;
protected optionsLoaded = false;
protected optionsLoaded = new Deferred();
/**
* Constructor
@ -103,7 +104,13 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object>
) {
super(title, translate, matSnackbar);
votesRepo
this.setup();
}
private async setup(): Promise<void> {
await this.optionsLoaded;
this.votesRepo
.getViewModelListObservable()
.pipe(
filter(() => this.poll && this.canSeeVotes), // filter first for valid poll state to avoid unneccessary iteration of potentially thousands of votes
@ -111,10 +118,7 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
filter(votes => !!votes.length)
)
.subscribe(() => {
// votes data can only be created when options are ready
if (this.optionsLoaded) {
this.createVotesData();
}
});
}
@ -156,10 +160,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
*/
protected onPollLoaded(): void {}
protected onPollWithOptionsLoaded(): void {
this.createVotesData();
}
protected onStateChanged(): void {}
protected abstract hasPerms(): boolean;
@ -194,15 +194,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
this.chartDataSubject.next(this.pollService.generateChartData(this.poll));
}
/**
* This checks if the poll has votes.
*/
private checkData(): void {
if (this.poll.stateHasVotes) {
setTimeout(() => this.initChartData());
}
}
/**
* Helper-function to search for this poll and display data or create a new one.
*/
@ -214,23 +205,12 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
if (poll) {
this.poll = poll;
this.onPollLoaded();
this.waitForOptions();
this.checkData();
this.createVotesData();
this.initChartData();
this.optionsLoaded.resolve();
}
})
);
}
}
/**
* Waits until poll's options are loaded.
*/
private waitForOptions(): void {
if (!this.poll.options || !this.poll.options.length) {
setTimeout(() => this.waitForOptions(), 1);
} else {
this.optionsLoaded = true;
this.onPollWithOptionsLoaded();
}
}
}

View File

@ -6,6 +6,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { VOTE_UNDOCUMENTED } from 'app/shared/models/poll/base-poll';
import { OneOfValidator } from 'app/shared/validators/one-of-validator';
import { BaseViewComponent } from 'app/site/base/base-view';
import { PollFormComponent } from './poll-form/poll-form.component';
@ -80,7 +81,9 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
}
/**
* check recursively whether the given vote data object is empty, meaning all values would be '-2' when sent
* check recursively whether the given vote data object is empty, meaning all values would
* be VOTE_UNDOCUMENTED when sent
*
* @param voteData the (partial) vote data
*/
private isVoteDataEmpty(voteData: object): boolean {
@ -91,7 +94,7 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
/**
* iterates over the given data and returns a new object with all empty fields recursively
* replaced with '-2'
* replaced with VOTE_UNDOCUMENTED
* @param voteData the (partial) data
*/
private replaceEmptyValues(voteData: object, undo: boolean = false): object {
@ -101,9 +104,9 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
result[key] = this.replaceEmptyValues(voteData[key], undo);
} else {
if (undo) {
result[key] = voteData[key] === -2 ? null : voteData[key];
result[key] = voteData[key] === VOTE_UNDOCUMENTED ? null : voteData[key];
} else {
result[key] = !!voteData[key] ? voteData[key] : -2;
result[key] = !!voteData[key] ? voteData[key] : VOTE_UNDOCUMENTED;
}
}
}
@ -111,7 +114,9 @@ export abstract class BasePollDialogComponent<T extends ViewBasePoll> extends Ba
}
/**
* reverses the replacement of empty values by '-2'; replaces each '-2' with null
* reverses the replacement of empty values by VOTE_UNDOCUMENTED; replaces each
* VOTE_UNDOCUMENTED with null
*
* @param voteData the vote data
*/
protected undoReplaceEmptyValues(voteData: object): object {

View File

@ -5,7 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core';
import { OperatorService } from 'app/core/core-services/operator.service';
import { VotingError, VotingService } from 'app/core/ui-services/voting.service';
import { VotingError } from 'app/core/ui-services/voting.service';
import { BaseViewComponent } from 'app/site/base/base-view';
import { ViewUser } from 'app/site/users/models/view-user';
import { ViewBasePoll } from '../models/view-base-poll';
@ -20,9 +20,8 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
public constructor(
title: Title,
protected translate: TranslateService,
translate: TranslateService,
matSnackbar: MatSnackBar,
public vmanager: VotingService,
protected operator: OperatorService
) {
super(title, translate, matSnackbar);
@ -30,10 +29,7 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
this.subscriptions.push(
this.operator.getViewUserObservable().subscribe(user => {
this.user = user;
// this.updateVotes();
})
);
}
// protected abstract updateVotes(): void;
}

View File

@ -205,7 +205,6 @@ export class PollFormComponent<T extends ViewBasePoll> extends BaseViewComponent
/**
* Disable votes_amount form control if the poll type is anonymous
* and the poll method is votes.
* TODO: Enabling this requires at least another layout and some rework
*/
private setVotesAmountCtrl(): void {
if (this.contentForm.get('type').value === PollType.Pseudoanonymous) {

View File

@ -3,7 +3,14 @@ import { Injectable } from '@angular/core';
import { _ } from 'app/core/translate/translation-marker';
import { ChartData, ChartDate } from 'app/shared/components/charts/charts.component';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { BasePoll, MajorityMethod, PercentBase, PollColor, PollType } from 'app/shared/models/poll/base-poll';
import {
BasePoll,
MajorityMethod,
PercentBase,
PollColor,
PollType,
VOTE_UNDOCUMENTED
} from 'app/shared/models/poll/base-poll';
import { AssignmentPollMethodVerbose } from 'app/site/assignments/models/view-assignment-poll';
import {
MajorityMethodVerbose,
@ -70,17 +77,17 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
{
value: 'simple_majority',
display_name: 'Simple majority',
calc: base => calcMajority(base * 0.5, true)
calc: base => calcMajority(base / 2, true)
},
{
value: 'two-thirds_majority',
display_name: 'Two-thirds majority',
calc: base => calcMajority((base / 3) * 2)
calc: base => calcMajority((base * 2) / 3)
},
{
value: 'three-quarters_majority',
display_name: 'Three-quarters majority',
calc: base => calcMajority((base / 4) * 3)
calc: base => calcMajority((base * 3) / 4)
},
{
value: 'disabled',
@ -91,6 +98,7 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
export interface PollData {
pollmethod: string;
type: string;
onehundred_percent_base: string;
options: {
user?: {
@ -242,20 +250,18 @@ export abstract class PollService {
return [
{
vote: 'votesvalid',
hide: poll.votesvalid === -2,
hide: poll.votesvalid === VOTE_UNDOCUMENTED,
showPercent: this.showPercentOfValidOrCast(poll)
},
{
vote: 'votesinvalid',
icon: 'not_interested',
// TODO || PollType === analog
hide: poll.votesinvalid === -2,
hide: poll.votesinvalid === VOTE_UNDOCUMENTED || poll.type !== PollType.Analog,
showPercent: poll.onehundred_percent_base === PercentBase.Cast
},
{
vote: 'votescast',
// TODO || PollType === analog
hide: poll.votescast === -2,
hide: poll.votescast === VOTE_UNDOCUMENTED || poll.type !== PollType.Analog,
showPercent: poll.onehundred_percent_base === PercentBase.Cast
}
];
@ -312,6 +318,6 @@ export abstract class PollService {
}
public isVoteDocumented(vote: number): boolean {
return vote !== null && vote !== undefined && vote !== -2;
return vote !== null && vote !== undefined && vote !== VOTE_UNDOCUMENTED;
}
}

View File

@ -110,9 +110,7 @@ export class UserDetailComponent extends BaseViewComponent implements OnInit {
this.constantsService.get<UserBackends>('UserBackends').subscribe(backends => (this.userBackends = backends));
this.groupRepo
.getViewModelListObservable()
.subscribe(groups => this.groups.next(groups.filter(group => group.id !== 1)));
this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
}
/**

View File

@ -31,6 +31,7 @@
@import './app/site/motions/modules/motion-poll/motion-poll/motion-poll.component.scss-theme.scss';
@import './app/site/motions/modules/motion-poll/motion-poll-detail/motion-poll-detail.component.scss-theme.scss';
@import './app/site/assignments/components/assignment-poll-detail/assignment-poll-detail-component.scss-theme.scss';
@import './app/shared/components/progress-snack-bar/progress-snack-bar.component.scss-theme.scss';
/** fonts */
@import './assets/styles/fonts.scss';
@ -62,6 +63,7 @@ $narrow-spacing: (
@include os-motion-poll-style($theme);
@include os-motion-poll-detail-style($theme);
@include os-assignment-poll-detail-style($theme);
@include os-progress-snack-bar-style($theme);
}
/** Load projector specific SCSS values */

View File

@ -7,7 +7,6 @@ from django.db import connections, models
from jsonfield import JSONField
from openslides.utils import logging
from openslides.utils.manager import BaseManager
from ..agenda.mixins import ListOfSpeakersMixin

View File

@ -184,8 +184,9 @@ class TestCreation(TestCase):
self.assertEqual(
sorted([group.id for group in mediafile.access_groups.all()]), [2, 4]
)
self.assertTrue(mediafile.mediafile.name)
self.assertEqual(mediafile.path, mediafile.original_filename)
self.assertEqual(mediafile.mediafile.name, "")
self.assertEqual(mediafile.original_filename, "")
self.assertEqual(mediafile.path, mediafile.title + "/")
def test_with_access_groups_wrong_json(self):
response = self.client.post(