Add referenced motions by the server (fixes #4383)

This commit is contained in:
FinnStutzenstein 2019-02-26 14:43:51 +01:00
parent 23c1857fa6
commit 4cde0431ab
8 changed files with 113 additions and 48 deletions

View File

@ -92,6 +92,22 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
]);
}
public getTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
if (motion.identifier) {
return motion.identifier + ': ' + motion.title;
} else {
return motion.title;
}
};
public getIdentifierOrTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
if (motion.identifier) {
return motion.identifier;
} else {
return motion.title;
}
};
public getAgendaTitle = (motion: Partial<Motion> | Partial<ViewMotion>) => {
// if the identifier is set, the title will be 'Motion <identifier>'.
if (motion.identifier) {
@ -149,6 +165,8 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
tags,
parent
);
viewMotion.getIdentifierOrTitle = () => this.getIdentifierOrTitle(viewMotion);
viewMotion.getTitle = () => this.getTitle(viewMotion);
viewMotion.getVerboseName = this.getVerboseName;
viewMotion.getAgendaTitle = () => this.getAgendaTitle(viewMotion);
viewMotion.getProjectorTitle = viewMotion.getAgendaTitle;
@ -759,7 +777,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
}
let state = this.translate.instant(motion.state.name);
if (motion.stateExtension && motion.state.show_state_extension_field) {
state += ' ' + this.solveExtensionPlaceHolder(motion.stateExtension);
state += ' ' + this.parseMotionPlaceholders(motion.stateExtension);
}
return state;
}
@ -775,7 +793,7 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
if (motion.recommendation) {
let rec = this.translate.instant(motion.recommendation.recommendation_label);
if (motion.recommendationExtension && motion.recommendation.show_recommendation_extension_field) {
rec += ' ' + this.solveExtensionPlaceHolder(motion.recommendationExtension);
rec += ' ' + this.parseMotionPlaceholders(motion.recommendationExtension);
}
return rec;
}
@ -785,20 +803,16 @@ export class MotionRepositoryService extends BaseAgendaContentObjectRepository<V
* Replaces any motion placeholder (`[motion:id]`) with the motion's title(s)
*
* @param value
* @returns the string with the motion titles replacing the placeholders, '??' strings for errors
* @returns the string with the motion titles replacing the placeholders
*/
public solveExtensionPlaceHolder(value: string): string {
const beg = value.indexOf('[motion:');
const end = value.indexOf(']');
if (beg > -1 && (end > -1 && end > beg)) {
const id = Number(value.substring(beg + 8, end));
const referedMotion = Number.isNaN(id) ? null : this.getViewModel(id);
const title = referedMotion ? referedMotion.identifier : '??';
value = value.substring(0, beg) + title + value.substring(end + 1);
// recursively check for additional occurrences
return this.solveExtensionPlaceHolder(value);
} else {
return value;
}
public parseMotionPlaceholders(value: string): string {
return value.replace(/\[motion:(\d+)\]/g, (match, id) => {
const motion = this.getViewModel(id);
if (motion) {
return motion.getIdentifierOrTitle();
} else {
return this.translate.instant('<unknown motion>');
}
});
}
}

View File

@ -278,11 +278,11 @@
*ngIf="motion.recommendation && motion.recommendation.show_recommendation_extension_field"
class="spacer-top-10"
>
<form [formGroup]="searchMotionForm">
<form [formGroup]="recommendationExtensionForm">
<mat-form-field>
<input
matInput
[formControl]="searchMotionForm.get('recoExtension')"
[formControl]="recommendationExtensionForm.get('recoExtension')"
placeholder="{{ 'Extension' | translate }}"
/>
</mat-form-field>
@ -291,8 +291,8 @@
</button>
<os-search-value-selector
ngDefaultControl
[form]="searchMotionForm"
[formControl]="searchMotionForm.get('motion_id')"
[form]="recommendationExtensionForm"
[formControl]="recommendationExtensionForm.get('motion_id')"
[multiple]="false"
listname="{{ 'Motions' | translate }}"
[InputListValues]="motionObserver"

View File

@ -70,7 +70,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
/**
* To search other motions as extension via search value selector
*/
public searchMotionForm: FormGroup;
public recommendationExtensionForm: FormGroup;
/**
* Determine if the motion is edited
@ -636,7 +636,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
const statuteAmendmentFieldName = 'statute_amendment';
contentPatch[statuteAmendmentFieldName] = formMotion.isStatuteAmendment();
this.contentForm.patchValue(contentPatch);
this.searchMotionForm.get('recoExtension').setValue(this.motion.recommendationExtension);
this.recommendationExtensionForm.get('recoExtension').setValue(this.motion.recommendationExtension);
}
/**
@ -679,13 +679,13 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
}();
// create the search motion form
this.searchMotionForm = this.formBuilder.group({
this.recommendationExtensionForm = this.formBuilder.group({
motion_id: [],
recoExtension: []
});
// Detect changes in in search motion form
this.searchMotionForm.get('motion_id').valueChanges.subscribe(change => {
this.recommendationExtensionForm.get('motion_id').valueChanges.subscribe(change => {
this.addMotionExtension(change);
});
}
@ -1167,8 +1167,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* @param id the ID of a selected motion returned by a search value selector
*/
public addMotionExtension(id: number): void {
const recoExtensionValue = this.searchMotionForm.get('recoExtension').value;
this.searchMotionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`);
const recoExtensionValue = this.recommendationExtensionForm.get('recoExtension').value || '';
this.recommendationExtensionForm.get('recoExtension').setValue(`${recoExtensionValue}[motion:${id}]`);
}
/**
@ -1185,7 +1185,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
* in {@link newRecommendationExtension}
*/
public setRecommendationExtension(): void {
this.repo.setRecommendationExtension(this.motion, this.searchMotionForm.get('recoExtension').value);
this.repo.setRecommendationExtension(this.motion, this.recommendationExtensionForm.get('recoExtension').value);
}
/**

View File

@ -323,17 +323,27 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
/**
* This is set by the repository
*/
public getAgendaTitle;
public getTitle: () => string;
/**
* This is set by the repository
*/
public getAgendaTitleWithType;
public getIdentifierOrTitle: () => string;
/**
* This is set by the repository
*/
public getVerboseName;
public getAgendaTitle: () => string;
/**
* This is set by the repository
*/
public getAgendaTitleWithType: () => string;
/**
* This is set by the repository
*/
public getVerboseName: () => string;
public constructor(
motion: Motion,
@ -362,14 +372,6 @@ export class ViewMotion extends BaseAgendaViewModel implements Searchable {
this._parent = parent;
}
public getTitle = () => {
if (this.identifier) {
return this.identifier + ': ' + this.title;
} else {
return this.title;
}
};
public getAgendaItem(): ViewItem {
return this.item;
}

View File

@ -1,6 +1,9 @@
export interface MotionBlockSlideMotionRepresentation {
export interface MotionTitleInformation {
title: string;
identifier?: string;
}
export interface MotionBlockSlideMotionRepresentation extends MotionTitleInformation {
recommendation?: {
name: string;
css_class: string;
@ -11,4 +14,5 @@ export interface MotionBlockSlideMotionRepresentation {
export interface MotionBlockSlideData {
title: string;
motions: MotionBlockSlideMotionRepresentation[];
referenced_motions: { [id: number]: MotionTitleInformation };
}

View File

@ -6,7 +6,7 @@
<div *ngFor="let motion of data.data.motions">
<div class="ellipsis-overflow">
{{ motion.identifier }}: {{ motion.title }}
{{ getMotionTitle(motion) }}
</div>
<div class="white ellipsis-overflow">
<mat-basic-chip *ngIf="motion.recommendation" disableRipple [ngClass]="getStateCssColor(motion)">

View File

@ -1,8 +1,11 @@
import { Component } from '@angular/core';
import { BaseSlideComponent } from 'app/slides/base-slide-component';
import { MotionBlockSlideData, MotionBlockSlideMotionRepresentation } from './motion-block-slide-data';
import { Motion } from 'app/shared/models/motions/motion';
import {
MotionBlockSlideData,
MotionBlockSlideMotionRepresentation,
MotionTitleInformation
} from './motion-block-slide-data';
import { MotionRepositoryService } from 'app/core/repositories/motions/motion-repository.service';
import { StateCssClassMapping } from 'app/site/motions/models/view-workflow';
import { TranslateService } from '@ngx-translate/core';
@ -17,8 +20,8 @@ export class MotionBlockSlideComponent extends BaseSlideComponent<MotionBlockSli
super();
}
public getMotionTitle(motion: Partial<Motion>): string {
return this.motionRepo.getAgendaTitle(motion);
public getMotionTitle(motion: MotionTitleInformation): string {
return this.motionRepo.getTitle(motion);
}
public getStateCssColor(motion: MotionBlockSlideMotionRepresentation): string {
@ -28,7 +31,16 @@ export class MotionBlockSlideComponent extends BaseSlideComponent<MotionBlockSli
public getRecommendationLabel(motion: MotionBlockSlideMotionRepresentation): string {
let recommendation = this.translate.instant(motion.recommendation.name);
if (motion.recommendation_extension) {
recommendation += ' ' + this.motionRepo.solveExtensionPlaceHolder(motion.recommendation_extension);
const extension = motion.recommendation_extension.replace(/\[motion:(\d+)\]/g, (match, id) => {
const titleInformation = this.data.data.referenced_motions[id];
if (titleInformation) {
return this.motionRepo.getIdentifierOrTitle(titleInformation);
} else {
return this.translate.instant('<unknown motion>');
}
});
recommendation += ' ' + extension;
}
return recommendation;
}

View File

@ -1,3 +1,4 @@
import re
from typing import Any, Dict
from ..users.projector import get_user_name
@ -224,7 +225,17 @@ def motion_block_slide(
f"motion block with id {motion_block_id} does not exist"
)
# All motions in this motion block
motions = []
# All motions meantioned in recommendations of the above motions.
# Only title_informations will be collected. With this, the client can
# replace the placeholders in the recommendation correctly
# This maps the motion id to the title information
referenced_motions: Dict[int, Dict[str, str]] = {}
motion_placeholder_regex = re.compile(r"\[motion:(\d+)\]")
# Search motions.
for motion in all_data["motions/motion"].values():
if motion["motion_block_id"] == motion_block_id:
motion_object = {
@ -242,13 +253,35 @@ def motion_block_slide(
"css_class": recommendation["css_class"],
}
if recommendation["show_recommendation_extension_field"]:
motion_object["recommendation_extension"] = motion[
"recommendation_extension"
recommendation_extension = motion["recommendation_extension"]
motion_object["recommendation_extension"] = recommendation_extension
# Collect all meantioned motions via [motion:<id>]
referenced_ids = [
int(id)
for id in motion_placeholder_regex.findall(
recommendation_extension
)
]
for id in referenced_ids:
if (
id not in referenced_motions
and id in all_data["motions/motion"]
):
referenced_motions[id] = {
"title": all_data["motions/motion"][id]["title"],
"identifier": all_data["motions/motion"][id][
"identifier"
],
}
motions.append(motion_object)
return {"title": motion_block["title"], "motions": motions}
return {
"title": motion_block["title"],
"motions": motions,
"referenced_motions": referenced_motions,
}
def register_projector_slides() -> None: