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

View File

@ -114,14 +114,6 @@ export class AppComponent {
.subscribe(() => servertimeService.startScheduler()); .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 { private overloadArrayFunctions(): void {
Object.defineProperty(Array.prototype, 'toString', { Object.defineProperty(Array.prototype, 'toString', {
value: function(): string { value: function(): string {
@ -153,7 +145,6 @@ export class AppComponent {
enumerable: false enumerable: false
}); });
// intersect
Object.defineProperty(Array.prototype, 'intersect', { Object.defineProperty(Array.prototype, 'intersect', {
value: function<T>(other: T[]): T[] { value: function<T>(other: T[]): T[] {
let a = this; let a = this;
@ -167,7 +158,6 @@ export class AppComponent {
enumerable: false enumerable: false
}); });
// mapToObject
Object.defineProperty(Array.prototype, 'mapToObject', { Object.defineProperty(Array.prototype, 'mapToObject', {
value: function<T>(f: (item: T) => { [key: string]: any }): { [key: string]: any } { value: function<T>(f: (item: T) => { [key: string]: any }): { [key: string]: any } {
return this.reduce((aggr, item) => { return this.reduce((aggr, item) => {
@ -188,18 +178,17 @@ export class AppComponent {
* Adds some functions to Set. * Adds some functions to Set.
*/ */
private overloadSetFunctions(): void { private overloadSetFunctions(): void {
// equals
Object.defineProperty(Set.prototype, 'equals', { Object.defineProperty(Set.prototype, 'equals', {
value: function<T>(other: Set<T>): boolean { value: function<T>(other: Set<T>): boolean {
const _difference = new Set(this); const difference = new Set(this);
for (const elem of other) { for (const elem of other) {
if (_difference.has(elem)) { if (difference.has(elem)) {
_difference.delete(elem); difference.delete(elem);
} else { } else {
return false; return false;
} }
} }
return !_difference.size; return !difference.size;
}, },
enumerable: false enumerable: false
}); });

View File

@ -1,9 +1,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { _ } from 'app/core/translate/translation-marker';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { _ } from 'app/core/translate/translation-marker';
import { BannerDefinition, BannerService } from '../ui-services/banner.service'; 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 * @returns the amount of ballots, depending on the config settings
*/ */
protected getBallotCount(): number { protected getBallotCount(): number {
// TODO: seems to be broken
switch (this.ballotCountSelection) { switch (this.ballotCountSelection) {
case 'NUMBER_OF_ALL_PARTICIPANTS': case 'NUMBER_OF_ALL_PARTICIPANTS':
return this.userRepo.getViewModelList().length; 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 { 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 { VoteValue } from 'app/shared/models/poll/base-vote';
import { ViewMotion } from 'app/site/motions/models/view-motion'; import { ViewMotion } from 'app/site/motions/models/view-motion';
import { ViewMotionOption } from 'app/site/motions/models/view-motion-option'; import { ViewMotionOption } from 'app/site/motions/models/view-motion-option';
import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll'; import { MotionPollTitleInformation, ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
@ -91,7 +92,7 @@ export class MotionPollRepositoryService extends BasePollRepositoryService<
return this.translate.instant(plural ? 'Polls' : 'Poll'); 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)); 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'); _('Topics');
_('Open requests to speak'); _('Open requests to speak');
// ** Motions ** // ** Motions **
// config strings // config strings
// subgroup general // subgroup general
@ -246,7 +245,6 @@ _('Motion block');
_('The text field may not be blank.'); _('The text field may not be blank.');
_('The reason field may not be blank.'); _('The reason field may not be blank.');
// ** Assignments ** // ** Assignments **
// Assignment config strings // Assignment config strings
_('Elections'); _('Elections');
@ -257,7 +255,7 @@ _('All valid ballots');
_('All casted ballots'); _('All casted ballots');
_('Disabled (no percents)'); _('Disabled (no percents)');
_('Default groups with voting rights'); _('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'); _('Put all candidates on the list of speakers');
// subgroup ballot papers // subgroup ballot papers
_('Ballot papers'); _('Ballot papers');
@ -291,7 +289,6 @@ _('Entitled to vote');
_('Voting method'); _('Voting method');
_('Amount of votes'); _('Amount of votes');
// ** Users ** // ** Users **
// permission strings (see models.py of each Django app) // permission strings (see models.py of each Django app)
// agenda // agenda

View File

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

View File

@ -47,21 +47,15 @@ export class AttachmentControlComponent extends BaseFormControlComponent<ViewMed
return 'attachment-control'; return 'attachment-control';
} }
/**
* Default constructor
*
* @param dialogService Reference to the `MatDialog`
* @param mediaService Reference for the `MediaFileRepositoryService`
*/
public constructor( public constructor(
fb: FormBuilder, formBuilder: FormBuilder,
fm: FocusMonitor, focusMonitor: FocusMonitor,
element: ElementRef<HTMLElement>, element: ElementRef<HTMLElement>,
@Optional() @Self() public ngControl: NgControl, @Optional() @Self() public ngControl: NgControl,
private dialogService: MatDialog, private dialogService: MatDialog,
private mediaService: MediafileRepositoryService 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); 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 { protected initializeForm(): void {
this.contentForm = this.fb.control([]); this.contentForm = this.fb.control([]);
} }
protected updateForm(value: ViewMediafile[] | null): void { protected updateForm(value: ViewMediafile[] | null): void {
this.contentForm.setValue(value || []); this.contentForm.setValue(value || []);
} }

View File

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

View File

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

View File

@ -6,12 +6,6 @@
'bar action'; 'bar action';
grid-template-columns: auto min-content; grid-template-columns: auto min-content;
.mat-progress-bar-buffer {
// TODO theme
// background-color: mat-color($background, card) !important;
background-color: white !important;
}
.message { .message {
grid-area: 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" [removable]="true"
(removed)="removeItem(item.id)" (removed)="removeItem(item.id)"
[disableRipple]="true" [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> </mat-chip-list>
</div> </div>
<div class="os-search-value-selector-chip-placeholder"></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[]; private selectableItems: Selectable[];
/**
* Empty constructor
*/
public constructor( public constructor(
protected translate: TranslateService, protected translate: TranslateService,
fb: FormBuilder, formBuilder: FormBuilder,
@Optional() @Self() public ngControl: NgControl, @Optional() @Self() public ngControl: NgControl,
fm: FocusMonitor, focusMonitor: FocusMonitor,
element: ElementRef<HTMLElement> element: ElementRef<HTMLElement>
) { ) {
super(fb, fm, element, ngControl); super(formBuilder, focusMonitor, element, ngControl);
} }
/** /**

View File

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

View File

@ -38,6 +38,10 @@ export enum PercentBase {
Disabled = 'disabled' Disabled = 'disabled'
} }
export const VOTE_MAJORITY = -1;
export const VOTE_UNDOCUMENTED = -2;
export const LOWEST_VOTE_VALUE = VOTE_UNDOCUMENTED;
export abstract class BasePoll< export abstract class BasePoll<
T = any, T = any,
O extends BaseOption<any> = 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 { TranslateService } from '@ngx-translate/core';
import { VOTE_MAJORITY, VOTE_UNDOCUMENTED } from '../models/poll/base-poll';
@Pipe({ @Pipe({
name: 'parsePollNumber' name: 'parsePollNumber'
}) })
@ -11,9 +13,9 @@ export class ParsePollNumberPipe implements PipeTransform {
public transform(value: number): number | string { public transform(value: number): number | string {
const input = Math.trunc(value); const input = Math.trunc(value);
switch (input) { switch (input) {
case -1: case VOTE_MAJORITY:
return this.translate.instant('majority'); return this.translate.instant('majority');
case -2: case VOTE_UNDOCUMENTED:
return this.translate.instant('undocumented'); return this.translate.instant('undocumented');
default: default:
return input; return input;

View File

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

View File

@ -308,7 +308,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
* Creates a new Poll * Creates a new Poll
*/ */
public openDialog(): void { public openDialog(): void {
// TODO: That is not really a ViewObject
const dialogData = { const dialogData = {
collectionString: ViewAssignmentPoll.COLLECTIONSTRING, collectionString: ViewAssignmentPoll.COLLECTIONSTRING,
assignment_id: this.assignment.id, 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 { 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';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component'; import { BasePollDetailComponent } from 'app/site/polls/components/base-poll-detail.component';
import { PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service'; import { PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service'; import { AssignmentPollDialogService } from '../../services/assignment-poll-dialog.service';
@ -110,7 +111,7 @@ export class AssignmentPollDetailComponent extends BasePollDetailComponent<ViewA
this.isReady = true; this.isReady = true;
} }
private voteValueToLabel(vote: 'Y' | 'N' | 'A'): string { private voteValueToLabel(vote: VoteValue): string {
if (vote === 'Y') { if (vote === 'Y') {
return this.translate.instant('Yes'); return this.translate.instant('Yes');
} else if (vote === 'N') { } else if (vote === 'N') {

View File

@ -8,7 +8,7 @@ import { TranslateService } from '@ngx-translate/core';
import { debounceTime, distinctUntilChanged } from 'rxjs/operators'; import { debounceTime, distinctUntilChanged } from 'rxjs/operators';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll'; import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { PollType } from 'app/shared/models/poll/base-poll'; import { LOWEST_VOTE_VALUE, PollType } from 'app/shared/models/poll/base-poll';
import { GeneralValueVerbose, VoteValue, VoteValueVerbose } from 'app/shared/models/poll/base-vote'; import { GeneralValueVerbose, VoteValue, VoteValueVerbose } from 'app/shared/models/poll/base-vote';
import { import {
AssignmentPollMethodVerbose, AssignmentPollMethodVerbose,
@ -168,30 +168,20 @@ export class AssignmentPollDialogComponent extends BasePollDialogComponent<ViewA
[option.user_id]: this.fb.group( [option.user_id]: this.fb.group(
// for each user, create a form group with a control for each valid input (Y, N, A) // for each user, create a form group with a control for each valid input (Y, N, A)
this.analogPollValues.mapToObject(value => ({ this.analogPollValues.mapToObject(value => ({
[value]: ['', [Validators.min(-2)]] [value]: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
})) }))
) )
})) }))
), ),
amount_global_no: ['', [Validators.min(-2)]], amount_global_no: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
amount_global_abstain: ['', [Validators.min(-2)]], amount_global_abstain: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
// insert all used global fields // insert all used global fields
...this.sumValues.mapToObject(sumValue => ({ ...this.sumValues.mapToObject(sumValue => ({
[sumValue]: ['', [Validators.min(-2)]] [sumValue]: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
})) }))
}); });
if (this.isAnalogPoll && this.pollData.poll) { if (this.isAnalogPoll && this.pollData.poll) {
this.updateDialogVoteForm(this.pollData); 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 { VotingService } from 'app/core/ui-services/voting.service';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll'; import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll';
import { PollType } from 'app/shared/models/poll/base-poll'; import { PollType } from 'app/shared/models/poll/base-poll';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component'; import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
import { ViewAssignmentPoll } from '../../models/view-assignment-poll'; import { ViewAssignmentPoll } from '../../models/view-assignment-poll';
// TODO: Duplicate // TODO: Duplicate
interface VoteActions { interface VoteActions {
vote: Vote; vote: VoteValue;
css: string; css: string;
icon: string; icon: string;
label: string; label: string;
} }
type Vote = 'Y' | 'N' | 'A';
@Component({ @Component({
selector: 'os-assignment-poll-vote', selector: 'os-assignment-poll-vote',
templateUrl: './assignment-poll-vote.component.html', templateUrl: './assignment-poll-vote.component.html',
@ -45,12 +44,12 @@ export class AssignmentPollVoteComponent extends BasePollVoteComponent<ViewAssig
title: Title, title: Title,
protected translate: TranslateService, protected translate: TranslateService,
matSnackbar: MatSnackBar, matSnackbar: MatSnackBar,
vmanager: VotingService,
operator: OperatorService, operator: OperatorService,
public vmanager: VotingService,
private pollRepo: AssignmentPollRepositoryService, private pollRepo: AssignmentPollRepositoryService,
private promptService: PromptService private promptService: PromptService
) { ) {
super(title, translate, matSnackbar, vmanager, operator); super(title, translate, matSnackbar, operator);
} }
public ngOnInit(): void { 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()) { if (this.isGlobalOptionSelected()) {
delete this.voteRequestData.global; delete this.voteRequestData.global;
} }

View File

@ -10,7 +10,7 @@ import {
AssignmentPollMethod, AssignmentPollMethod,
AssignmentPollPercentBase AssignmentPollPercentBase
} from 'app/shared/models/assignments/assignment-poll'; } 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 { PollData, PollService, PollTableData, VotingResult } from 'app/site/polls/services/poll.service';
import { ViewAssignmentPoll } from '../models/view-assignment-poll'; import { ViewAssignmentPoll } from '../models/view-assignment-poll';
@ -77,17 +77,16 @@ export class AssignmentPollService extends PollService {
} }
private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] { private getGlobalVoteKeys(poll: ViewAssignmentPoll): VotingResult[] {
// debugger;
return [ return [
{ {
vote: 'amount_global_no', vote: 'amount_global_no',
showPercent: this.showPercentOfValidOrCast(poll), 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', vote: 'amount_global_abstain',
showPercent: this.showPercentOfValidOrCast(poll), 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 { public openDialog(): void {
// TODO: Could be simpler, requires a lot of refactoring
const dialogData = { const dialogData = {
collectionString: ViewMotionPoll.COLLECTIONSTRING, collectionString: ViewMotionPoll.COLLECTIONSTRING,
motion_id: this.motion.id, motion_id: this.motion.id,

View File

@ -5,6 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; 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 { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component'; import { BasePollDialogComponent } from 'app/site/polls/components/base-poll-dialog.component';
import { PollFormComponent } from 'app/site/polls/components/poll-form/poll-form.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>; protected pollForm: PollFormComponent<ViewMotionPoll>;
public constructor( public constructor(
private fb: FormBuilder,
title: Title, title: Title,
protected translate: TranslateService, translate: TranslateService,
matSnackbar: MatSnackBar, matSnackbar: MatSnackBar,
public dialogRef: MatDialogRef<BasePollDialogComponent<ViewMotionPoll>>, public dialogRef: MatDialogRef<BasePollDialogComponent<ViewMotionPoll>>,
private formBuilder: FormBuilder,
@Inject(MAT_DIALOG_DATA) public pollData: Partial<ViewMotionPoll> @Inject(MAT_DIALOG_DATA) public pollData: Partial<ViewMotionPoll>
) { ) {
super(title, translate, matSnackbar, dialogRef); 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. * Pre-executed method to initialize the dialog-form depending on the poll-method.
*/ */
private createDialog(): void { private createDialog(): void {
this.dialogVoteForm = this.fb.group({ this.dialogVoteForm = this.formBuilder.group({
Y: ['', [Validators.min(-2)]], Y: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
N: ['', [Validators.min(-2)]], N: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
A: ['', [Validators.min(-2)]], A: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
votesvalid: ['', [Validators.min(-2)]], votesvalid: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
votesinvalid: ['', [Validators.min(-2)]], votesinvalid: ['', [Validators.min(LOWEST_VOTE_VALUE)]],
votescast: ['', [Validators.min(-2)]] votescast: ['', [Validators.min(LOWEST_VOTE_VALUE)]]
}); });
if (this.pollData.poll) { 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 { MotionPollRepositoryService } from 'app/core/repositories/motions/motion-poll-repository.service';
import { PromptService } from 'app/core/ui-services/prompt.service'; import { PromptService } from 'app/core/ui-services/prompt.service';
import { VotingService } from 'app/core/ui-services/voting.service'; import { VotingService } from 'app/core/ui-services/voting.service';
import { VoteValue } from 'app/shared/models/poll/base-vote';
import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll'; import { ViewMotionPoll } from 'app/site/motions/models/view-motion-poll';
import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component'; import { BasePollVoteComponent } from 'app/site/polls/components/base-poll-vote.component';
interface VoteOption { interface VoteOption {
vote?: 'Y' | 'N' | 'A'; vote?: VoteValue;
css?: string; css?: string;
icon?: string; icon?: string;
label?: string; label?: string;
@ -50,18 +51,15 @@ export class MotionPollVoteComponent extends BasePollVoteComponent<ViewMotionPol
title: Title, title: Title,
translate: TranslateService, translate: TranslateService,
matSnackbar: MatSnackBar, matSnackbar: MatSnackBar,
vmanager: VotingService,
operator: OperatorService, operator: OperatorService,
public vmanager: VotingService,
private pollRepo: MotionPollRepositoryService, private pollRepo: MotionPollRepositoryService,
private promptService: PromptService private promptService: PromptService
) { ) {
super(title, translate, matSnackbar, vmanager, operator); super(title, translate, matSnackbar, operator);
} }
/** public saveVote(vote: VoteValue): void {
* TODO: 'Y' | 'N' | 'A' should refer to some ENUM
*/
public saveVote(vote: 'Y' | 'N' | 'A'): void {
this.currentVote.vote = vote; this.currentVote.vote = vote;
const title = this.translate.instant('Submit selection now?'); const title = this.translate.instant('Submit selection now?');
const content = this.translate.instant('Your decision cannot be changed afterwards.'); const content = this.translate.instant('Your decision cannot be changed afterwards.');

View File

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

View File

@ -8,6 +8,7 @@ import { Label } from 'ng2-charts';
import { BehaviorSubject, from, Observable } from 'rxjs'; import { BehaviorSubject, from, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators'; import { filter, map } from 'rxjs/operators';
import { Deferred } from 'app/core/promises/deferred';
import { BaseRepository } from 'app/core/repositories/base-repository'; import { BaseRepository } from 'app/core/repositories/base-repository';
import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service'; import { GroupRepositoryService } from 'app/core/repositories/users/group-repository.service';
import { BasePollDialogService } from 'app/core/ui-services/base-poll-dialog.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 // The observable for the votes-per-user table
public votesDataObservable: Observable<BaseVoteData[]>; public votesDataObservable: Observable<BaseVoteData[]>;
protected optionsLoaded = false; protected optionsLoaded = new Deferred();
/** /**
* Constructor * Constructor
@ -103,7 +104,13 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object> protected votesRepo: BaseRepository<ViewBaseVote, BaseVote, object>
) { ) {
super(title, translate, matSnackbar); super(title, translate, matSnackbar);
votesRepo this.setup();
}
private async setup(): Promise<void> {
await this.optionsLoaded;
this.votesRepo
.getViewModelListObservable() .getViewModelListObservable()
.pipe( .pipe(
filter(() => this.poll && this.canSeeVotes), // filter first for valid poll state to avoid unneccessary iteration of potentially thousands of votes 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) filter(votes => !!votes.length)
) )
.subscribe(() => { .subscribe(() => {
// votes data can only be created when options are ready this.createVotesData();
if (this.optionsLoaded) {
this.createVotesData();
}
}); });
} }
@ -156,10 +160,6 @@ export abstract class BasePollDetailComponent<V extends ViewBasePoll> extends Ba
*/ */
protected onPollLoaded(): void {} protected onPollLoaded(): void {}
protected onPollWithOptionsLoaded(): void {
this.createVotesData();
}
protected onStateChanged(): void {} protected onStateChanged(): void {}
protected abstract hasPerms(): boolean; 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.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. * 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) { if (poll) {
this.poll = poll; this.poll = poll;
this.onPollLoaded(); this.onPollLoaded();
this.waitForOptions(); this.createVotesData();
this.checkData(); 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 { 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 { OneOfValidator } from 'app/shared/validators/one-of-validator';
import { BaseViewComponent } from 'app/site/base/base-view'; import { BaseViewComponent } from 'app/site/base/base-view';
import { PollFormComponent } from './poll-form/poll-form.component'; 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 * @param voteData the (partial) vote data
*/ */
private isVoteDataEmpty(voteData: object): boolean { 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 * 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 * @param voteData the (partial) data
*/ */
private replaceEmptyValues(voteData: object, undo: boolean = false): object { 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); result[key] = this.replaceEmptyValues(voteData[key], undo);
} else { } else {
if (undo) { if (undo) {
result[key] = voteData[key] === -2 ? null : voteData[key]; result[key] = voteData[key] === VOTE_UNDOCUMENTED ? null : voteData[key];
} else { } 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 * @param voteData the vote data
*/ */
protected undoReplaceEmptyValues(voteData: object): object { protected undoReplaceEmptyValues(voteData: object): object {

View File

@ -5,7 +5,7 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { OperatorService } from 'app/core/core-services/operator.service'; import { OperatorService } from 'app/core/core-services/operator.service';
import { VotingError, 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 { BaseViewComponent } from 'app/site/base/base-view';
import { ViewUser } from 'app/site/users/models/view-user'; import { ViewUser } from 'app/site/users/models/view-user';
import { ViewBasePoll } from '../models/view-base-poll'; import { ViewBasePoll } from '../models/view-base-poll';
@ -20,9 +20,8 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
public constructor( public constructor(
title: Title, title: Title,
protected translate: TranslateService, translate: TranslateService,
matSnackbar: MatSnackBar, matSnackbar: MatSnackBar,
public vmanager: VotingService,
protected operator: OperatorService protected operator: OperatorService
) { ) {
super(title, translate, matSnackbar); super(title, translate, matSnackbar);
@ -30,10 +29,7 @@ export abstract class BasePollVoteComponent<V extends ViewBasePoll> extends Base
this.subscriptions.push( this.subscriptions.push(
this.operator.getViewUserObservable().subscribe(user => { this.operator.getViewUserObservable().subscribe(user => {
this.user = 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 * Disable votes_amount form control if the poll type is anonymous
* and the poll method is votes. * and the poll method is votes.
* TODO: Enabling this requires at least another layout and some rework
*/ */
private setVotesAmountCtrl(): void { private setVotesAmountCtrl(): void {
if (this.contentForm.get('type').value === PollType.Pseudoanonymous) { 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 { _ } from 'app/core/translate/translation-marker';
import { ChartData, ChartDate } from 'app/shared/components/charts/charts.component'; import { ChartData, ChartDate } from 'app/shared/components/charts/charts.component';
import { AssignmentPollMethod } from 'app/shared/models/assignments/assignment-poll'; 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 { AssignmentPollMethodVerbose } from 'app/site/assignments/models/view-assignment-poll';
import { import {
MajorityMethodVerbose, MajorityMethodVerbose,
@ -70,17 +77,17 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
{ {
value: 'simple_majority', value: 'simple_majority',
display_name: 'Simple majority', display_name: 'Simple majority',
calc: base => calcMajority(base * 0.5, true) calc: base => calcMajority(base / 2, true)
}, },
{ {
value: 'two-thirds_majority', value: 'two-thirds_majority',
display_name: 'Two-thirds majority', display_name: 'Two-thirds majority',
calc: base => calcMajority((base / 3) * 2) calc: base => calcMajority((base * 2) / 3)
}, },
{ {
value: 'three-quarters_majority', value: 'three-quarters_majority',
display_name: 'Three-quarters majority', display_name: 'Three-quarters majority',
calc: base => calcMajority((base / 4) * 3) calc: base => calcMajority((base * 3) / 4)
}, },
{ {
value: 'disabled', value: 'disabled',
@ -91,6 +98,7 @@ export const PollMajorityMethod: CalculableMajorityMethod[] = [
export interface PollData { export interface PollData {
pollmethod: string; pollmethod: string;
type: string;
onehundred_percent_base: string; onehundred_percent_base: string;
options: { options: {
user?: { user?: {
@ -242,20 +250,18 @@ export abstract class PollService {
return [ return [
{ {
vote: 'votesvalid', vote: 'votesvalid',
hide: poll.votesvalid === -2, hide: poll.votesvalid === VOTE_UNDOCUMENTED,
showPercent: this.showPercentOfValidOrCast(poll) showPercent: this.showPercentOfValidOrCast(poll)
}, },
{ {
vote: 'votesinvalid', vote: 'votesinvalid',
icon: 'not_interested', icon: 'not_interested',
// TODO || PollType === analog hide: poll.votesinvalid === VOTE_UNDOCUMENTED || poll.type !== PollType.Analog,
hide: poll.votesinvalid === -2,
showPercent: poll.onehundred_percent_base === PercentBase.Cast showPercent: poll.onehundred_percent_base === PercentBase.Cast
}, },
{ {
vote: 'votescast', vote: 'votescast',
// TODO || PollType === analog hide: poll.votescast === VOTE_UNDOCUMENTED || poll.type !== PollType.Analog,
hide: poll.votescast === -2,
showPercent: poll.onehundred_percent_base === PercentBase.Cast showPercent: poll.onehundred_percent_base === PercentBase.Cast
} }
]; ];
@ -312,6 +318,6 @@ export abstract class PollService {
} }
public isVoteDocumented(vote: number): boolean { 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.constantsService.get<UserBackends>('UserBackends').subscribe(backends => (this.userBackends = backends));
this.groupRepo this.groupRepo.getViewModelListObservableWithoutDefaultGroup().subscribe(this.groups);
.getViewModelListObservable()
.subscribe(groups => this.groups.next(groups.filter(group => group.id !== 1)));
} }
/** /**

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/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/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/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 */ /** fonts */
@import './assets/styles/fonts.scss'; @import './assets/styles/fonts.scss';
@ -62,6 +63,7 @@ $narrow-spacing: (
@include os-motion-poll-style($theme); @include os-motion-poll-style($theme);
@include os-motion-poll-detail-style($theme); @include os-motion-poll-detail-style($theme);
@include os-assignment-poll-detail-style($theme); @include os-assignment-poll-detail-style($theme);
@include os-progress-snack-bar-style($theme);
} }
/** Load projector specific SCSS values */ /** Load projector specific SCSS values */

View File

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

View File

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