Merge pull request #3869 from FinnStutzenstein/modelInterface

Introduce more basemodel functionality used by the agenda, generic views
This commit is contained in:
Sean 2018-09-14 11:06:30 +02:00 committed by GitHub
commit 9d59da1352
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 346 additions and 185 deletions

View File

@ -1,4 +1,4 @@
import { ModelConstructor, BaseModel } from '../../shared/models/base.model'; import { ModelConstructor, BaseModel } from '../../shared/models/base/base-model';
/** /**
* Registeres the mapping of collection strings <--> actual types. Every Model should register itself here. * Registeres the mapping of collection strings <--> actual types. Every Model should register itself here.

View File

@ -1,6 +1,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http'; import { HttpClient } from '@angular/common/http';
import { BaseModel } from '../../shared/models/base.model'; import { BaseModel } from '../../shared/models/base/base-model';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators'; import { tap } from 'rxjs/operators';

View File

@ -1,7 +1,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { Observable, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { BaseModel, ModelConstructor } from 'app/shared/models/base.model'; import { BaseModel, ModelConstructor } from '../../shared/models/base/base-model';
import { CacheService } from './cache.service'; import { CacheService } from './cache.service';
import { CollectionStringModelMapperService } from './collectionStringModelMapper.service'; import { CollectionStringModelMapperService } from './collectionStringModelMapper.service';

View File

@ -2,7 +2,7 @@
<mat-select [formControl]="formControl" placeholder="{{listname}}" multiple="{{multiple}}" #thisSelector> <mat-select [formControl]="formControl" placeholder="{{listname}}" multiple="{{multiple}}" #thisSelector>
<ngx-mat-select-search [formControl]="filterControl"></ngx-mat-select-search> <ngx-mat-select-search [formControl]="filterControl"></ngx-mat-select-search>
<mat-option *ngFor="let selectedItem of filteredItems | async" [value]="selectedItem"> <mat-option *ngFor="let selectedItem of filteredItems | async" [value]="selectedItem">
{{selectedItem.toString()}} {{selectedItem.getTitle(translate)}}
</mat-option> </mat-option>
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>

View File

@ -2,8 +2,9 @@ import { Component, OnInit, Input, ViewChild } from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms'; import { FormControl, FormGroup } from '@angular/forms';
import { ReplaySubject, Subject } from 'rxjs'; import { ReplaySubject, Subject } from 'rxjs';
import { MatSelect } from '@angular/material'; import { MatSelect } from '@angular/material';
import { SelectorItem } from './search-value-selector.interfaces';
import { takeUntil } from 'rxjs/operators'; import { takeUntil } from 'rxjs/operators';
import { Displayable } from '../../models/base/displayable';
import { TranslateService } from '@ngx-translate/core';
/** /**
* Reusable Searchable Value Selector * Reusable Searchable Value Selector
@ -27,17 +28,6 @@ import { takeUntil } from 'rxjs/operators';
* </os-search-value-selector> * </os-search-value-selector>
* ``` * ```
* *
* ### Declaration of a Selector provided as `[InputListValues]=myListValues`:
*
* Every Class that enherits of BaseModel implements the SelectorItem Interface and can
* therefore be used directly in the Selector Component.
*
* ```ts
* import { SelectorItem } from '../../shared/components/search-value-selector/search-value-selector.interfaces';
*
* const myListValues: SelectorItem[];
* myListValues = this.DS.get(User);
* ```
*/ */
@Component({ @Component({
@ -60,7 +50,7 @@ export class SearchValueSelectorComponent implements OnInit {
/** /**
* List of the filtered content, when entering somithing in the search bar * List of the filtered content, when entering somithing in the search bar
*/ */
public filteredItems: ReplaySubject<SelectorItem[]> = new ReplaySubject<SelectorItem[]>(1); public filteredItems: ReplaySubject<Displayable[]> = new ReplaySubject<Displayable[]>(1);
/** /**
* Decide if this should be a single or multi-select-field * Decide if this should be a single or multi-select-field
@ -72,7 +62,7 @@ export class SearchValueSelectorComponent implements OnInit {
* The Input List Values * The Input List Values
*/ */
@Input() @Input()
public InputListValues: SelectorItem[]; public InputListValues: Displayable[];
/** /**
* Placeholder of the List * Placeholder of the List
@ -115,7 +105,7 @@ export class SearchValueSelectorComponent implements OnInit {
/** /**
* Empty constructor * Empty constructor
*/ */
public constructor() {} public constructor(public translate: TranslateService) {}
/** /**
* onInit with filter ans subscription on filter * onInit with filter ans subscription on filter
@ -164,7 +154,7 @@ export class SearchValueSelectorComponent implements OnInit {
* places, but can't reflect the changes in both places. Until this can be done this will be unused code * places, but can't reflect the changes in both places. Until this can be done this will be unused code
* @param item the selected item to be removed * @param item the selected item to be removed
*/ */
public remove(item: SelectorItem): void { public remove(item: Displayable): void {
const myArr = this.thisSelector.value; const myArr = this.thisSelector.value;
const index = myArr.indexOf(item, 0); const index = myArr.indexOf(item, 0);
// my model was the form according to fix // my model was the form according to fix

View File

@ -1,10 +0,0 @@
/**
* Inteface for the Multi-Value-Selector Component to display and use
* the given values.
*/
export interface SelectorItem {
/**
* translates the displayable part of the function to a String
*/
toString(): string;
}

View File

@ -1,6 +1,10 @@
import { BaseModel } from '../base.model'; import { ProjectableBaseModel } from '../base/projectable-base-model';
import { Speaker } from './speaker'; import { Speaker } from './speaker';
/**
* The representation of the content object for agenda items. The unique combination
* of the collection and id is given.
*/
interface ContentObject { interface ContentObject {
id: number; id: number;
collection: string; collection: string;
@ -10,11 +14,11 @@ interface ContentObject {
* Representations of agenda Item * Representations of agenda Item
* @ignore * @ignore
*/ */
export class Item extends BaseModel { export class Item extends ProjectableBaseModel {
public id: number; public id: number;
public item_number: string; public item_number: string;
public title: string; public title: string;
public list_view_title: string; public title_with_type: string;
public comment: string; public comment: string;
public closed: boolean; public closed: boolean;
public type: number; public type: number;
@ -30,6 +34,23 @@ export class Item extends BaseModel {
super('agenda/item', input); super('agenda/item', input);
} }
// Note: This has to be used in the agenda repository
/*public get contentObject(): AgendaBaseModel {
const contentObject = this.DS.get<BaseModel>(this.content_object.collection, this.content_object.id);
if (!contentObject) {
return null;
}
if (contentObject instanceof AgendaBaseModel) {
return contentObject as AgendaBaseModel;
} else {
throw new Error(
`The content object (${this.content_object.collection}, ${this.content_object.id}) of item ${
this.id
} is not a BaseProjectableModel.`
);
}
}*/
public deserialize(input: any): void { public deserialize(input: any): void {
Object.assign(this, input); Object.assign(this, input);
@ -40,9 +61,32 @@ export class Item extends BaseModel {
} }
} }
public toString(): string { // The repository has to check for the content object and choose which title to use.
// The code below is belongs to the repository
public getTitle(): string {
/*const contentObject: AgendaBaseModel = this.contentObject;
if (contentObject) {
return contentObject.getAgendaTitle();
} else {
return this.title; return this.title;
}*/
return this.title;
}
// Same here. See comment for getTitle()
public getListTitle(): string {
/*const contentObject: AgendaBaseModel = this.contentObject;
if (contentObject) {
return contentObject.getAgendaTitleWithType();
} else {
return this.title_with_type;
}*/
return this.title_with_type;
}
public getProjectorTitle(): string {
return this.getListTitle();
} }
} }
BaseModel.registerCollectionElement('agenda/item', Item); ProjectableBaseModel.registerCollectionElement('agenda/item', Item);

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* Representation of a speaker in an agenda item * Representation of a speaker in an agenda item

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* Content of the 'assignment_related_users' property * Content of the 'assignment_related_users' property

View File

@ -1,12 +1,12 @@
import { BaseModel } from '../base.model';
import { AssignmentUser } from './assignment-user'; import { AssignmentUser } from './assignment-user';
import { Poll } from './poll'; import { Poll } from './poll';
import { AgendaBaseModel } from '../base/agenda-base-model';
/** /**
* Representation of an assignment. * Representation of an assignment.
* @ignore * @ignore
*/ */
export class Assignment extends BaseModel { export class Assignment extends AgendaBaseModel {
public id: number; public id: number;
public title: string; public title: string;
public description: string; public description: string;
@ -19,7 +19,7 @@ export class Assignment extends BaseModel {
public tags_id: number[]; public tags_id: number[];
public constructor(input?: any) { public constructor(input?: any) {
super('assignments/assignment', input); super('assignments/assignment', 'Assignment', input);
} }
public get candidateIds(): number[] { public get candidateIds(): number[] {
@ -48,9 +48,13 @@ export class Assignment extends BaseModel {
} }
} }
public toString(): string { public getTitle(): string {
return this.title; return this.title;
} }
public getDetailStateURL(): string {
return 'TODO';
}
} }
BaseModel.registerCollectionElement('assignments/assignment', Assignment); AgendaBaseModel.registerCollectionElement('assignments/assignment', Assignment);

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* Representation of a poll option * Representation of a poll option

View File

@ -1,5 +1,5 @@
import { PollOption } from './poll-option'; import { PollOption } from './poll-option';
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* Content of the 'polls' property of assignments * Content of the 'polls' property of assignments

View File

@ -0,0 +1,37 @@
import { AgendaInformation } from './agenda-information';
import { ProjectableBaseModel } from './projectable-base-model';
/**
* A base model for models, that can be content objects in the agenda. Provides title and navigation
* information for the agenda.
*/
export abstract class AgendaBaseModel extends ProjectableBaseModel implements AgendaInformation {
protected verboseName: string;
/**
* A Model that inherits from this class should provide a verbose name. It's used by creating
* the agenda title with type.
* @param collectionString
* @param verboseName
* @param input
*/
protected constructor(collectionString: string, verboseName: string, input?: any) {
super(collectionString, input);
this.verboseName = verboseName;
}
public getAgendaTitle(): string {
return this.getTitle();
}
public getAgendaTitleWithType(): string {
// Return the agenda title with the model's verbose name appended
return this.getAgendaTitle() + ' (' + this.verboseName + ')';
}
/**
* Should return the URL to the detail view. Used for the agenda, that the
* user can navigate to the content object.
*/
public abstract getDetailStateURL(): string;
}

View File

@ -0,0 +1,19 @@
/**
* An Interface for all extra information needed for content objects of items.
*/
export interface AgendaInformation {
/**
* Should return the title for the agenda list view.
*/
getAgendaTitle(): string;
/**
* Should return the title for the list of speakers view.
*/
getAgendaTitleWithType(): string;
/**
* Get the url for the detail view, so in the agenda the user can navigate to it.
*/
getDetailStateURL(): string;
}

View File

@ -1,7 +1,7 @@
import { OpenSlidesComponent } from 'app/openslides.component'; import { OpenSlidesComponent } from 'app/openslides.component';
import { Deserializable } from './deserializable.model'; import { Deserializable } from './deserializable';
import { CollectionStringModelMapperService } from '../../core/services/collectionStringModelMapper.service'; import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service';
import { SelectorItem } from '../components/search-value-selector/search-value-selector.interfaces'; import { Displayable } from './displayable';
export interface ModelConstructor<T extends BaseModel> { export interface ModelConstructor<T extends BaseModel> {
new (...args: any[]): T; new (...args: any[]): T;
@ -10,7 +10,7 @@ export interface ModelConstructor<T extends BaseModel> {
/** /**
* Abstract parent class to set rules and functions for all models. * Abstract parent class to set rules and functions for all models.
*/ */
export abstract class BaseModel extends OpenSlidesComponent implements Deserializable, SelectorItem { export abstract class BaseModel extends OpenSlidesComponent implements Deserializable, Displayable {
/** /**
* Register the collection string to the type. * Register the collection string to the type.
* @param collectionString * @param collectionString
@ -56,10 +56,16 @@ export abstract class BaseModel extends OpenSlidesComponent implements Deseriali
} }
}); });
} }
/**
* force children to have a toString() method public abstract getTitle(): string;
*/
public abstract toString(): string; public getListTitle(): string {
return this.getTitle();
}
public toString(): string {
return this.getTitle();
}
/** /**
* returns the collectionString. * returns the collectionString.

View File

@ -1,4 +1,4 @@
import { Deserializable } from './deserializable.model'; import { Deserializable } from './deserializable';
/** /**
* Abstract base class for a basic implementation of Deserializable. * Abstract base class for a basic implementation of Deserializable.

View File

@ -0,0 +1,14 @@
/**
* Every displayble object should have the given functions to give the object's title.
*/
export interface Displayable {
/**
* Should return the title. Alway used except for list view, the agenda and in the projector.
*/
getTitle(): string;
/**
* Should return the title for the list view.
*/
getListTitle(): string;
}

View File

@ -0,0 +1,17 @@
import { BaseModel } from './base-model';
import { Projectable } from './projectable';
export abstract class ProjectableBaseModel extends BaseModel implements Projectable {
protected constructor(collectionString: string, input?: any) {
super(collectionString, input);
}
/**
* This is a Dummy, which should be changed if the projector gets implemented.
*/
public project(): void {}
public getProjectorTitle(): string {
return this.getTitle();
}
}

View File

@ -0,0 +1,14 @@
/**
* Interface for every model, that should be projectable.
*/
export interface Projectable {
/**
* Should return the title for the projector.
*/
getProjectorTitle(): string;
/**
* Dummy. I don't know how the projctor system will be, so this function may change
*/
project(): void;
}

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of chat messages. * Representation of chat messages.
@ -14,8 +14,8 @@ export class ChatMessage extends BaseModel {
super('core/chat-message', input); super('core/chat-message', input);
} }
public toString(): string { public getTitle(): string {
return this.message; return 'Chatmessage';
} }
} }

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a config variable * Representation of a config variable
@ -13,7 +13,7 @@ export class Config extends BaseModel {
super('core/config', input); super('core/config', input);
} }
public toString(): string { public getTitle(): string {
return this.key; return this.key;
} }
} }

View File

@ -1,10 +1,10 @@
import { BaseModel } from '../base.model'; import { ProjectableBaseModel } from '../base/projectable-base-model';
/** /**
* Representation of a countdown * Representation of a countdown
* @ignore * @ignore
*/ */
export class Countdown extends BaseModel { export class Countdown extends ProjectableBaseModel {
public id: number; public id: number;
public description: string; public description: string;
public default_time: number; public default_time: number;
@ -15,9 +15,9 @@ export class Countdown extends BaseModel {
super('core/countdown'); super('core/countdown');
} }
public toString(): string { public getTitle(): string {
return this.description; return this.description;
} }
} }
BaseModel.registerCollectionElement('core/countdown', Countdown); ProjectableBaseModel.registerCollectionElement('core/countdown', Countdown);

View File

@ -1,10 +1,10 @@
import { BaseModel } from '../base.model'; import { ProjectableBaseModel } from '../base/projectable-base-model';
/** /**
* Representation of a projector message. * Representation of a projector message.
* @ignore * @ignore
*/ */
export class ProjectorMessage extends BaseModel { export class ProjectorMessage extends ProjectableBaseModel {
public id: number; public id: number;
public message: string; public message: string;
@ -12,9 +12,9 @@ export class ProjectorMessage extends BaseModel {
super('core/projector-message', input); super('core/projector-message', input);
} }
public toString(): string { public getTitle(): string {
return this.message; return 'Projectormessage';
} }
} }
BaseModel.registerCollectionElement('core/projector-message', ProjectorMessage); ProjectableBaseModel.registerCollectionElement('core/projector-message', ProjectorMessage);

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a projector. Has the nested property "projectiondefaults" * Representation of a projector. Has the nested property "projectiondefaults"
@ -19,7 +19,7 @@ export class Projector extends BaseModel {
super('core/projector', input); super('core/projector', input);
} }
public toString(): string { public getTitle(): string {
return this.name; return this.name;
} }
} }

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a tag. * Representation of a tag.
@ -12,7 +12,7 @@ export class Tag extends BaseModel {
super('core/tag', input); super('core/tag', input);
} }
public toString(): string { public getTitle(): string {
return this.name; return this.name;
} }
} }

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* The name and the type of a mediaFile. * The name and the type of a mediaFile.

View File

@ -1,11 +1,11 @@
import { BaseModel } from '../base.model';
import { File } from './file'; import { File } from './file';
import { ProjectableBaseModel } from '../base/projectable-base-model';
/** /**
* Representation of MediaFile. Has the nested property "File" * Representation of MediaFile. Has the nested property "File"
* @ignore * @ignore
*/ */
export class Mediafile extends BaseModel { export class Mediafile extends ProjectableBaseModel {
public id: number; public id: number;
public title: string; public title: string;
public mediafile: File; public mediafile: File;
@ -24,9 +24,9 @@ export class Mediafile extends BaseModel {
this.mediafile = new File(input.mediafile); this.mediafile = new File(input.mediafile);
} }
public toString(): string { public getTitle(): string {
return this.title; return this.title;
} }
} }
BaseModel.registerCollectionElement('amediafiles/mediafile', Mediafile); ProjectableBaseModel.registerCollectionElement('amediafiles/mediafile', Mediafile);

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a motion category. Has the nested property "File" * Representation of a motion category. Has the nested property "File"
@ -13,7 +13,7 @@ export class Category extends BaseModel {
super('motions/category', input); super('motions/category', input);
} }
public toString(): string { public getTitle(): string {
return this.prefix + ' - ' + this.name; return this.prefix + ' - ' + this.name;
} }
} }

View File

@ -1,22 +1,25 @@
import { BaseModel } from '../base.model'; import { AgendaBaseModel } from '../base/agenda-base-model';
import { Item } from '../agenda/item';
/** /**
* Representation of a motion block. * Representation of a motion block.
* @ignore * @ignore
*/ */
export class MotionBlock extends BaseModel { export class MotionBlock extends AgendaBaseModel {
public id: number; public id: number;
public title: string; public title: string;
public agenda_item_id: number; public agenda_item_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super('motions/motion-block', input); super('motions/motion-block', 'Motion block', input);
} }
public toString(): string { public getTitle(): string {
return this.title; return this.title;
} }
public getDetailStateURL(): string {
return 'TODO';
}
} }
BaseModel.registerCollectionElement('motions/motion-block', MotionBlock); AgendaBaseModel.registerCollectionElement('motions/motion-block', MotionBlock);

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a motion change recommendation. * Representation of a motion change recommendation.
@ -19,8 +19,8 @@ export class MotionChangeReco extends BaseModel {
super('motions/motion-change-recommendation', input); super('motions/motion-change-recommendation', input);
} }
public toString(): string { public getTitle(): string {
return this.text; return 'Changerecommendation';
} }
} }

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of a motion category. Has the nested property "File" * Representation of a motion category. Has the nested property "File"
@ -14,7 +14,7 @@ export class MotionCommentSection extends BaseModel {
super('motions/motion-comment-section', input); super('motions/motion-comment-section', input);
} }
public toString(): string { public getTitle(): string {
return this.name; return this.name;
} }
} }

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* Representation of a Motion Comment. * Representation of a Motion Comment.

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
/** /**
* Representation of a Motion Log. * Representation of a Motion Log.

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
import { User } from '../users/user'; import { User } from '../users/user';
/** /**

View File

@ -1,9 +1,9 @@
import { BaseModel } from '../base.model';
import { MotionSubmitter } from './motion-submitter'; import { MotionSubmitter } from './motion-submitter';
import { MotionLog } from './motion-log'; import { MotionLog } from './motion-log';
import { Category } from './category'; import { Category } from './category';
import { MotionComment } from './motion-comment'; import { MotionComment } from './motion-comment';
import { Workflow } from './workflow'; import { Workflow } from './workflow';
import { AgendaBaseModel } from '../base/agenda-base-model';
/** /**
* Representation of Motion. * Representation of Motion.
@ -12,7 +12,7 @@ import { Workflow } from './workflow';
* *
* @ignore * @ignore
*/ */
export class Motion extends BaseModel { export class Motion extends AgendaBaseModel {
public id: number; public id: number;
public identifier: string; public identifier: string;
public title: string; public title: string;
@ -36,12 +36,12 @@ export class Motion extends BaseModel {
public recommendation_extension: string; public recommendation_extension: string;
public tags_id: number[]; public tags_id: number[];
public attachments_id: number[]; public attachments_id: number[];
public polls: BaseModel[]; public polls: Object[];
public agenda_item_id: number; public agenda_item_id: number;
public log_messages: MotionLog[]; public log_messages: MotionLog[];
public constructor(input?: any) { public constructor(input?: any) {
super('motions/motion', input); super('motions/motion', 'Motion', input);
} }
/** /**
@ -62,13 +62,32 @@ export class Motion extends BaseModel {
.map((submitter: MotionSubmitter) => submitter.user_id); .map((submitter: MotionSubmitter) => submitter.user_id);
} }
/** public getTitle(): string {
* returns the Motion name
*/
public toString(): string {
return this.title; return this.title;
} }
public getAgendaTitle(): string {
// if the identifier is set, the title will be 'Motion <identifier>'.
if (this.identifier) {
return 'Motion ' + this.identifier;
} else {
return this.getTitle();
}
}
public getAgendaTitleWithType(): string {
// Append the verbose name only, if not the special format 'Motion <identifier>' is used.
if (this.identifier) {
return 'Motion ' + this.identifier;
} else {
return this.getTitle() + ' (' + this.verboseName + ')';
}
}
public getDetailStateURL(): string {
return 'TODO';
}
public deserialize(input: any): void { public deserialize(input: any): void {
Object.assign(this, input); Object.assign(this, input);
@ -91,6 +110,6 @@ export class Motion extends BaseModel {
/** /**
* Hack to get them loaded at last * Hack to get them loaded at last
*/ */
BaseModel.registerCollectionElement('motions/motion', Motion); AgendaBaseModel.registerCollectionElement('motions/motion', Motion);
BaseModel.registerCollectionElement('motions/category', Category); AgendaBaseModel.registerCollectionElement('motions/category', Category);
BaseModel.registerCollectionElement('motions/workflow', Workflow); AgendaBaseModel.registerCollectionElement('motions/workflow', Workflow);

View File

@ -1,4 +1,4 @@
import { Deserializer } from '../deserializer.model'; import { Deserializer } from '../base/deserializer';
import { Workflow } from './workflow'; import { Workflow } from './workflow';
/** /**

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
import { WorkflowState } from './workflow-state'; import { WorkflowState } from './workflow-state';
/** /**
@ -54,7 +54,7 @@ export class Workflow extends BaseModel {
} }
} }
public toString(): string { public getTitle(): string {
return this.name; return this.name;
} }
} }

View File

@ -1,10 +1,10 @@
import { BaseModel } from '../base.model'; import { AgendaBaseModel } from '../base/agenda-base-model';
/** /**
* Representation of a topic. * Representation of a topic.
* @ignore * @ignore
*/ */
export class Topic extends BaseModel { export class Topic extends AgendaBaseModel {
public id: number; public id: number;
public title: string; public title: string;
public text: string; public text: string;
@ -12,12 +12,21 @@ export class Topic extends BaseModel {
public agenda_item_id: number; public agenda_item_id: number;
public constructor(input?: any) { public constructor(input?: any) {
super('topics/topic', input); super('topics/topic', 'Topic', input);
} }
public toString(): string { public getTitle(): string {
return this.title; return this.title;
} }
public getAgendaTitleWithType(): string {
// Do not append ' (Topic)' to the title.
return this.getAgendaTitle();
}
public getDetailStateURL(): string {
return 'TODO';
}
} }
BaseModel.registerCollectionElement('topics/topic', Topic); AgendaBaseModel.registerCollectionElement('topics/topic', Topic);

View File

@ -1,4 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
/** /**
* Representation of user group. * Representation of user group.
@ -13,7 +13,7 @@ export class Group extends BaseModel {
super('users/group', input); super('users/group', input);
} }
public toString(): string { public getTitle(): string {
return this.name; return this.name;
} }
} }

View File

@ -1,5 +1,4 @@
import { BaseModel } from '../base.model'; import { BaseModel } from '../base/base-model';
import { User } from './user';
/** /**
* Representation of users personal note. * Representation of users personal note.
@ -14,8 +13,8 @@ export class PersonalNote extends BaseModel {
super('users/personal-note', input); super('users/personal-note', input);
} }
public toString(): string { public getTitle(): string {
return this.notes.toString(); return 'Personal note';
} }
} }

View File

@ -1,10 +1,10 @@
import { BaseModel } from '../base.model'; import { ProjectableBaseModel } from '../base/projectable-base-model';
/** /**
* Representation of a user in contrast to the operator. * Representation of a user in contrast to the operator.
* @ignore * @ignore
*/ */
export class User extends BaseModel { export class User extends ProjectableBaseModel {
public id: number; public id: number;
public username: string; public username: string;
public title: string; public title: string;
@ -77,9 +77,13 @@ export class User extends BaseModel {
return shortName.trim(); return shortName.trim();
} }
public toString(): string { public getTitle(): string {
return this.full_name;
}
public getListViewTitle(): string {
return this.short_name; return this.short_name;
} }
} }
BaseModel.registerCollectionElement('users/user', User); ProjectableBaseModel.registerCollectionElement('users/user', User);

View File

@ -1,7 +1,7 @@
import { OpenSlidesComponent } from '../openslides.component'; import { OpenSlidesComponent } from '../openslides.component';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { BaseViewModel } from './base-view-model'; import { BaseViewModel } from './base-view-model';
import { BaseModel, ModelConstructor } from '../shared/models/base.model'; import { BaseModel, ModelConstructor } from '../shared/models/base/base-model';
import { CollectionStringModelMapperService } from '../core/services/collectionStringModelMapper.service'; import { CollectionStringModelMapperService } from '../core/services/collectionStringModelMapper.service';
import { DataStoreService } from '../core/services/data-store.service'; import { DataStoreService } from '../core/services/data-store.service';

View File

@ -1,39 +1,19 @@
import { TranslateService } from '@ngx-translate/core'; import { BaseModel } from '../shared/models/base/base-model';
import { BaseModel } from '../shared/models/base.model'; import { Displayable } from '../shared/models/base/displayable';
/** /**
* Base class for view models. alls view models should have titles. * Base class for view models. alls view models should have titles.
*/ */
export abstract class BaseViewModel { export abstract class BaseViewModel implements Displayable {
public abstract updateValues(update: BaseModel): void; public abstract updateValues(update: BaseModel): void;
/** public abstract getTitle(): string;
* Should return the title for the detail view.
* @param translate
*/
public abstract getTitle(translate: TranslateService): string;
/** public getListTitle(): string {
* Should return the title for the list view. return this.getTitle();
* @param translate
*/
public getListTitle(translate: TranslateService): string {
return this.getTitle(translate);
} }
/** public toString(): string {
* Should return the title for the projector. return this.getTitle();
* @param translate
*/
public getProjector(translate: TranslateService): string {
return this.getTitle(translate);
}
/**
* Should return the title for the agenda list view.
* @param translate
*/
public getAgendaTitle(translate: TranslateService): string {
return this.getTitle(translate);
} }
} }

View File

@ -153,8 +153,8 @@
</div> </div>
<div *ngIf="!editMotion || !newMotion"> <div *ngIf="!editMotion || !newMotion">
<h3 translate>Submitters</h3> <h3 translate>Submitters</h3>
<ul *ngFor="let submitters of motion.submitters"> <ul *ngFor="let submitter of motion.submitters">
<li>{{submitters}}</li> <li>{{submitter.full_name}}</li>
</ul> </ul>
</div> </div>
</div> </div>
@ -169,8 +169,8 @@
</div> </div>
<div *ngIf="!editMotion && motion.hasSupporters()"> <div *ngIf="!editMotion && motion.hasSupporters()">
<h3 translate>Supporters</h3> <h3 translate>Supporters</h3>
<ul *ngFor="let supporters of motion.supporters"> <ul *ngFor="let supporter of motion.supporters">
<li>{{supporters}}</li> <li>{{supporter.full_name}}</li>
</ul> </ul>
</div> </div>
</div> </div>

View File

@ -3,7 +3,7 @@ import { Category } from '../../../shared/models/motions/category';
import { User } from '../../../shared/models/users/user'; import { User } from '../../../shared/models/users/user';
import { Workflow } from '../../../shared/models/motions/workflow'; import { Workflow } from '../../../shared/models/motions/workflow';
import { WorkflowState } from '../../../shared/models/motions/workflow-state'; import { WorkflowState } from '../../../shared/models/motions/workflow-state';
import { BaseModel } from '../../../shared/models/base.model'; import { BaseModel } from '../../../shared/models/base/base-model';
import { BaseViewModel } from '../../base-view-model'; import { BaseViewModel } from '../../base-view-model';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';

View File

@ -296,14 +296,14 @@ class Item(RESTModelMixin, models.Model):
'method on your related model.') 'method on your related model.')
@property @property
def list_view_title(self): def title_with_type(self):
""" """
Return get_agenda_list_view_title() from the content_object. Return get_agenda_title_with_type() from the content_object.
""" """
try: try:
return self.content_object.get_agenda_list_view_title() return self.content_object.get_agenda_title_with_type()
except AttributeError: except AttributeError:
raise NotImplementedError('You have to provide a get_agenda_list_view_title ' raise NotImplementedError('You have to provide a get_agenda_title_with_type '
'method on your related model.') 'method on your related model.')
def is_internal(self): def is_internal(self):

View File

@ -45,7 +45,7 @@ class ItemSerializer(ModelSerializer):
'id', 'id',
'item_number', 'item_number',
'title', 'title',
'list_view_title', 'title_with_type',
'comment', 'comment',
'closed', 'closed',
'type', 'type',

View File

@ -338,16 +338,17 @@ class Assignment(RESTModelMixin, models.Model):
agenda_item_update_information: Dict[str, Any] = {} agenda_item_update_information: Dict[str, Any] = {}
def get_agenda_title(self): def get_agenda_title(self):
"""
Returns the title for the agenda.
"""
return str(self) return str(self)
def get_agenda_list_view_title(self): def get_agenda_title_with_type(self):
""" """
Return a title string for the agenda list view. Return a title for the agenda with the appended assignment verbose name.
Contains agenda item number, title and assignment verbose name.
Note: It has to be the same return value like in JavaScript. Note: It has to be the same return value like in JavaScript.
""" """
return '%s (%s)' % (self.title, _(self._meta.verbose_name)) return '%s (%s)' % (self.get_agenda_title(), _(self._meta.verbose_name))
@property @property
def agenda_item(self): def agenda_item(self):

View File

@ -436,26 +436,30 @@ class Motion(RESTModelMixin, models.Model):
def get_agenda_title(self): def get_agenda_title(self):
""" """
Return a simple title string for the agenda. Return the title string for the agenda.
Returns only the motion title so that you have only agenda item number If the identifier is given, the title consists of the motion verbose name
and title in the agenda. and the identifier.
"""
return str(self)
def get_agenda_list_view_title(self):
"""
Return a title string for the agenda list view.
Returns only the motion title so that you have agenda item number,
title and motion identifier in the agenda.
Note: It has to be the same return value like in JavaScript. Note: It has to be the same return value like in JavaScript.
""" """
if self.identifier: if self.identifier:
string = '%s %s' % (_(self._meta.verbose_name), self.identifier) title = '%s %s' % (_(self._meta.verbose_name), self.identifier)
else: else:
string = '%s (%s)' % (_(self._meta.verbose_name), self.title) title = self.title
return string return title
def get_agenda_title_with_type(self):
"""
Return a title for the agenda with the type or the modified title if the
identifier is set..
Note: It has to be the same return value like in JavaScript.
"""
if self.identifier:
title = '%s %s' % (_(self._meta.verbose_name), self.identifier)
else:
title = '%s (%s)' % (self.title, _(self._meta.verbose_name))
return title
@property @property
def agenda_item(self): def agenda_item(self):
@ -781,6 +785,7 @@ class MotionBlock(RESTModelMixin, models.Model):
agenda_items = GenericRelation(Item, related_name='topics') agenda_items = GenericRelation(Item, related_name='topics')
class Meta: class Meta:
verbose_name = ugettext_noop('Motion block')
default_permissions = () default_permissions = ()
def __str__(self): def __str__(self):
@ -821,8 +826,8 @@ class MotionBlock(RESTModelMixin, models.Model):
def get_agenda_title(self): def get_agenda_title(self):
return self.title return self.title
def get_agenda_list_view_title(self): def get_agenda_title_with_type(self):
return self.title return '%s (%s)' % (self.get_agenda_title(), _(self._meta.verbose_name))
class MotionLog(RESTModelMixin, models.Model): class MotionLog(RESTModelMixin, models.Model):

View File

@ -78,7 +78,13 @@ class Topic(RESTModelMixin, models.Model):
return self.agenda_item.pk return self.agenda_item.pk
def get_agenda_title(self): def get_agenda_title(self):
"""
Returns the title for the agenda.
"""
return self.title return self.title
def get_agenda_list_view_title(self): def get_agenda_title_with_type(self):
return self.title """
Returns the agenda title. Topicy should not get a type postfix.
"""
return self.get_agenda_title()

View File

@ -69,7 +69,7 @@ class RetrieveItem(TestCase):
'content_object',))) 'content_object',)))
forbidden_keys = ( forbidden_keys = (
'item_number', 'item_number',
'list_view_title', 'title_with_type',
'comment', 'comment',
'closed', 'closed',
'type', 'type',