Merge pull request #3996 from FinnStutzenstein/client_perms_for_motion

can_manage_metadata and categories on motion create
This commit is contained in:
Jochen Saalfeld 2018-11-08 15:56:28 +01:00 committed by GitHub
commit a2d736bae9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 109 additions and 43 deletions

View File

@ -1,6 +1,6 @@
import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { Component, OnInit, Input, ViewChild, OnDestroy } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { Subject, ReplaySubject, BehaviorSubject } from 'rxjs'; import { Subject, ReplaySubject, BehaviorSubject, Subscription } from 'rxjs';
import { MatSelect } from '@angular/material'; import { MatSelect } from '@angular/material';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
@ -35,7 +35,7 @@ import { Selectable } from '../selectable';
templateUrl: './search-value-selector.component.html', templateUrl: './search-value-selector.component.html',
styleUrls: ['./search-value-selector.component.scss'] styleUrls: ['./search-value-selector.component.scss']
}) })
export class SearchValueSelectorComponent implements OnInit { export class SearchValueSelectorComponent implements OnInit, OnDestroy {
/** /**
* ngModel variable - Deprecated with Angular 7 * ngModel variable - Deprecated with Angular 7
* DO NOT USE: READ AT remove() FUNCTION! * DO NOT USE: READ AT remove() FUNCTION!
@ -52,6 +52,16 @@ export class SearchValueSelectorComponent implements OnInit {
*/ */
public filteredItems: ReplaySubject<Selectable[]> = new ReplaySubject<Selectable[]>(1); public filteredItems: ReplaySubject<Selectable[]> = new ReplaySubject<Selectable[]>(1);
/**
* The inputlist subject.
*/
private _inputListSubject: BehaviorSubject<Selectable[]>;
/**
* Saves the current subscription to _inputListSubject.
*/
private _inputListSubscription: Subscription = null;
/** /**
* Decide if this should be a single or multi-select-field * Decide if this should be a single or multi-select-field
*/ */
@ -59,10 +69,20 @@ export class SearchValueSelectorComponent implements OnInit {
public multiple: boolean; public multiple: boolean;
/** /**
* The Input List Values * The inputlist subject. Subscribes to it and updates the selector, if the subject
* changes its values.
*/ */
@Input() @Input()
public InputListValues: BehaviorSubject<Selectable[]>; public set InputListValues(value: BehaviorSubject<Selectable[]>) {
// unsubscribe to old subscription.
if (this._inputListSubscription) {
this._inputListSubscription.unsubscribe();
}
this._inputListSubject = value;
this._inputListSubscription = this._inputListSubject.subscribe(values => {
this.filterItems();
});
}
/** /**
* Placeholder of the List * Placeholder of the List
@ -111,31 +131,43 @@ export class SearchValueSelectorComponent implements OnInit {
* onInit with filter ans subscription on filter * onInit with filter ans subscription on filter
*/ */
public ngOnInit(): void { public ngOnInit(): void {
this.filteredItems.next(this.InputListValues.getValue()); if (this._inputListSubject) {
this.filteredItems.next(this._inputListSubject.getValue());
}
// listen to value changes // listen to value changes
this.filterControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => { this.filterControl.valueChanges.pipe(takeUntil(this._onDestroy)).subscribe(() => {
this.filterItems(); this.filterItems();
}); });
} }
/**
* Unsubscribe on destroing.
*/
public ngOnDestroy(): void {
if (this._inputListSubscription) {
this._inputListSubscription.unsubscribe();
}
this._onDestroy.next();
}
/** /**
* the filter function itself * the filter function itself
*/ */
private filterItems(): void { private filterItems(): void {
if (!this.InputListValues) { if (!this._inputListSubject) {
return; return;
} }
// get the search keyword // get the search keyword
let search = this.filterControl.value; let search = this.filterControl.value;
if (!search) { if (!search) {
this.filteredItems.next(this.InputListValues.getValue()); this.filteredItems.next(this._inputListSubject.getValue());
return; return;
} else { } else {
search = search.toLowerCase(); search = search.toLowerCase();
} }
// filter the values // filter the values
this.filteredItems.next( this.filteredItems.next(
this.InputListValues.getValue().filter( this._inputListSubject.getValue().filter(
selectedItem => selectedItem =>
selectedItem selectedItem
.toString() .toString()

View File

@ -39,6 +39,13 @@ export class PermsDirective extends OpenSlidesComponent {
*/ */
private alternative: boolean; private alternative: boolean;
/**
* Switch, to invert the result of checkPermission. Usefull for using osPerms as if-else:
* For one element you can use `*osPerms="'perm'"` and for the else-element use
* `*osPerms="'perm';complement: true"`.
*/
private complement: boolean;
/** /**
* Constructs the directive once. Observes the operator for it's groups so the * Constructs the directive once. Observes the operator for it's groups so the
* directive can perform changes dynamically * directive can perform changes dynamically
@ -85,6 +92,15 @@ export class PermsDirective extends OpenSlidesComponent {
this.updateView(); this.updateView();
} }
/**
* COmes from the view.
*/
@Input('osPermsComplement')
public set osPermsComplement(value: boolean) {
this.complement = value;
this.updateView();
}
/** /**
* Shows or hides certain content in the view. * Shows or hides certain content in the view.
*/ */
@ -108,6 +124,11 @@ export class PermsDirective extends OpenSlidesComponent {
* Returns true if the users permissions fit. * Returns true if the users permissions fit.
*/ */
private checkPermissions(): boolean { private checkPermissions(): boolean {
return this.permissions.length === 0 || this.operator.hasPerms(...this.permissions); const hasPerms = this.permissions.length === 0 || this.operator.hasPerms(...this.permissions);
if (this.complement) {
return !hasPerms;
} else {
return hasPerms;
}
} }
} }

View File

@ -94,8 +94,8 @@
</div> </div>
</mat-expansion-panel> </mat-expansion-panel>
<os-motion-comments [motion]="motion"></os-motion-comments> <os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
<os-personal-note [motion]="motion"></os-personal-note> <os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
<!-- Content --> <!-- Content -->
<mat-expansion-panel #contentPanel [expanded]='true'> <mat-expansion-panel #contentPanel [expanded]='true'>
@ -122,8 +122,8 @@
<ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container> <ng-container *ngTemplateOutlet="metaInfoTemplate"></ng-container>
</div> </div>
<os-motion-comments [motion]="motion"></os-motion-comments> <os-motion-comments *ngIf="!newMotion" [motion]="motion"></os-motion-comments>
<os-personal-note [motion]="motion"></os-personal-note> <os-personal-note *ngIf="!newMotion" [motion]="motion"></os-personal-note>
</div> </div>
<div class="desktop-right "> <div class="desktop-right ">
@ -153,8 +153,8 @@
<!-- Submitter --> <!-- Submitter -->
<div *ngIf="motion && motion.submitters || editMotion"> <div *ngIf="motion && motion.submitters || editMotion">
<div *ngIf="editMotion && newMotion"> <div *ngIf="editMotion">
<div *ngIf="motion && editMotion"> <div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('submitters_id')" <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('submitters_id')"
[multiple]="true" listname="Submitter" [InputListValues]="this.submitterObserver"></os-search-value-selector> [multiple]="true" listname="Submitter" [InputListValues]="this.submitterObserver"></os-search-value-selector>
</div> </div>
@ -171,7 +171,7 @@
<div *ngIf='motion && motion.hasSupporters() || editMotion'> <div *ngIf='motion && motion.hasSupporters() || editMotion'>
<!-- print all motion supporters --> <!-- print all motion supporters -->
<div *ngIf="editMotion"> <div *ngIf="editMotion">
<div *ngIf="motion && editMotion"> <div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('supporters_id')" <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('supporters_id')"
[multiple]="true" listname="Supporter" [InputListValues]="this.supporterObserver"></os-search-value-selector> [multiple]="true" listname="Supporter" [InputListValues]="this.supporterObserver"></os-search-value-selector>
</div> </div>
@ -179,24 +179,33 @@
<div *ngIf="!editMotion && motion.hasSupporters()"> <div *ngIf="!editMotion && motion.hasSupporters()">
<h3 translate>Supporters</h3> <h3 translate>Supporters</h3>
<ul *ngFor="let supporter of motion.supporters"> <ul *ngFor="let supporter of motion.supporters">
<li>{{supporter.full_name}}</li> <li>{{ supporter.full_name }}</li>
</ul> </ul>
</div> </div>
</div> </div>
<!-- State --> <!-- State -->
<div *ngIf='motion && motion.state'> <div *ngIf='motion && motion.state'>
<mat-form-field *ngIf="!editMotion && !newMotion"> <div *osPerms="['motions.can_manage', 'motions.can_manage_metadata'];complement:true">
<mat-select placeholder='State' formControlName='state_id' (selectionChange)="onChangeState($event)"> <ng-container *ngIf="!newMotion">
<mat-option [value]="motion.state_id">{{motion.state}}</mat-option> <h3 translate>State</h3>
<mat-divider></mat-divider> {{ motion.state }}
<mat-option *ngFor="let state of motion.nextStates" [value]="state.id">{{state}}</mat-option> </ng-container>
<mat-divider></mat-divider> </div>
<mat-option [value]="null"> <div *ngIf="!editMotion">
<mat-icon>replay</mat-icon><span translate>Reset State</span> <mat-form-field *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
</mat-option> <mat-select placeholder='State' formControlName='state_id' (selectionChange)="onChangeState($event)">
</mat-select> <mat-option [value]="motion.state_id">{{ motion.state }}</mat-option>
</mat-form-field> <mat-divider></mat-divider>
<mat-option *ngFor="let state of motion.nextStates" [value]="state.id">{{ state }}</mat-option>
<mat-divider></mat-divider>
<mat-option [value]="null">
<mat-icon>replay</mat-icon>
<span translate>Reset State</span>
</mat-option>
</mat-select>
</mat-form-field>
</div>
</div> </div>
<!-- Recommendation --> <!-- Recommendation -->
@ -204,7 +213,7 @@
<div *ngIf='motion && motion.state && recommender'> <div *ngIf='motion && motion.state && recommender'>
<mat-form-field *ngIf="!editMotion && !newMotion"> <mat-form-field *ngIf="!editMotion && !newMotion">
<mat-select [placeholder]=recommender formControlName='recommendation_id' (selectionChange)="onChangerRecommenderState($event)"> <mat-select [placeholder]=recommender formControlName='recommendation_id' (selectionChange)="onChangerRecommenderState($event)">
<mat-option *ngFor="let state of motion.possibleRecommendations" [value]="state.id">{{state}}</mat-option> <mat-option *ngFor="let recommendation of motion.possibleRecommendations" [value]="recommendation.id">{{ recommendation }}</mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
</div> </div>
@ -215,7 +224,7 @@
<h3 translate>Category</h3> <h3 translate>Category</h3>
{{motion.category}} {{motion.category}}
</div> </div>
<div *ngIf="editMotion"> <div *ngIf="editMotion || newMotion">
<os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('category_id')" <os-search-value-selector ngDefaultControl [form]="metaInfoForm" [formControl]="this.metaInfoForm.get('category_id')"
[multiple]="false" listname="Category" [InputListValues]="this.categoryObserver"></os-search-value-selector> [multiple]="false" listname="Category" [InputListValues]="this.categoryObserver"></os-search-value-selector>
</div> </div>
@ -225,11 +234,13 @@
<div *ngIf="motion && motion.origin || editMotion"> <div *ngIf="motion && motion.origin || editMotion">
<div *ngIf='!editMotion'> <div *ngIf='!editMotion'>
<h3 translate> Origin</h3> <h3 translate> Origin</h3>
{{motion.origin}} {{ motion.origin }}
</div>
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
<mat-form-field *ngIf="editMotion">
<input matInput placeholder='Origin' formControlName='origin' [value]='motionCopy.origin'>
</mat-form-field>
</div> </div>
<mat-form-field *ngIf="editMotion">
<input matInput placeholder='Origin' formControlName='origin' [value]='motionCopy.origin'>
</mat-form-field>
</div> </div>
<!-- Voting --> <!-- Voting -->
@ -258,7 +269,8 @@
<h4>{{motion.title}}</h4> <h4>{{motion.title}}</h4>
</div> </div>
<mat-form-field *ngIf="editMotion" class="wide-form"> <mat-form-field *ngIf="editMotion" class="wide-form">
<input matInput osAutofocus placeholder='Title' formControlName='title' [value]='motionCopy.title'> <input matInput osAutofocus placeholder='Title' formControlName='title' [value]='motionCopy.title'
required>
</mat-form-field> </mat-form-field>
</div> </div>
@ -278,7 +290,7 @@
[scrollToChange]="scrollToChange" (createChangeRecommendation)="createChangeRecommendation($event)"></os-motion-detail-diff> [scrollToChange]="scrollToChange" (createChangeRecommendation)="createChangeRecommendation($event)"></os-motion-detail-diff>
</ng-container> </ng-container>
<mat-form-field *ngIf="motion && editMotion" class="wide-form"> <mat-form-field *ngIf="motion && editMotion" class="wide-form">
<textarea matInput placeholder='Motion Text' formControlName='text' [value]='motionCopy.text'></textarea> <textarea matInput placeholder='Motion Text' formControlName='text' [value]='motionCopy.text' required></textarea>
</mat-form-field> </mat-form-field>

View File

@ -105,17 +105,17 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
/** /**
* Subject for the Categories * Subject for the Categories
*/ */
public categoryObserver: BehaviorSubject<Array<Category>>; public categoryObserver: BehaviorSubject<Category[]>;
/** /**
* Subject for the Submitters * Subject for the Submitters
*/ */
public submitterObserver: BehaviorSubject<Array<User>>; public submitterObserver: BehaviorSubject<User[]>;
/** /**
* Subject for the Supporters * Subject for the Supporters
*/ */
public supporterObserver: BehaviorSubject<Array<User>>; public supporterObserver: BehaviorSubject<User[]>;
/** /**
* Value for os-motion-detail-diff: when this is set, that component scrolls to the given change * Value for os-motion-detail-diff: when this is set, that component scrolls to the given change
@ -503,7 +503,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit {
* Determine if the user has the correct requirements to alter the motion * Determine if the user has the correct requirements to alter the motion
*/ */
public opCanEdit(): boolean { public opCanEdit(): boolean {
return this.op.hasPerms('motions.can_manage'); return this.op.hasPerms('motions.can_manage', 'motions.can_manage_metadata');
} }
/** /**

View File

@ -134,14 +134,15 @@ class MotionViewSet(ModelViewSet):
'title', 'title',
'text', 'text',
'reason', 'reason',
'category_id',
] ]
if parent_motion is not None: if parent_motion is not None:
# For creating amendments. # For creating amendments.
whitelist.extend([ whitelist.extend([
'parent_id', 'parent_id',
'amendment_paragraphs', 'amendment_paragraphs',
'category_id', # This will be set to the matching 'motion_block_id', # This and the category_id will be set to the matching
'motion_block_id', # values from parent_motion. # values from parent_motion.
]) ])
request.data['category_id'] = parent_motion.category_id request.data['category_id'] = parent_motion.category_id
request.data['motion_block_id'] = parent_motion.motion_block_id request.data['motion_block_id'] = parent_motion.motion_block_id