From 1ee76de4175ce93daaf6e7be4a1fd256886ddfd7 Mon Sep 17 00:00:00 2001 From: FinnStutzenstein Date: Thu, 13 Sep 2018 09:23:57 +0200 Subject: [PATCH] Handling of titles, projection and content objects in items Restructure the titles for motion, motion-block, assignment and topic. Less possibilities for more clear code. Added mote base models enabling functionalities of projection and being a content object for items --- .../collectionStringModelMapper.service.ts | 2 +- .../app/core/services/data-send.service.ts | 2 +- .../app/core/services/data-store.service.ts | 2 +- .../search-value-selector.component.html | 2 +- .../search-value-selector.component.ts | 22 +++----- .../search-value-selector.interfaces.ts | 10 ---- client/src/app/shared/models/agenda/item.ts | 54 +++++++++++++++++-- .../src/app/shared/models/agenda/speaker.ts | 2 +- .../models/assignments/assignment-user.ts | 2 +- .../shared/models/assignments/assignment.ts | 14 +++-- .../shared/models/assignments/poll-option.ts | 2 +- .../src/app/shared/models/assignments/poll.ts | 2 +- .../shared/models/base/agenda-base-model.ts | 37 +++++++++++++ .../shared/models/base/agenda-information.ts | 19 +++++++ .../{base.model.ts => base/base-model.ts} | 22 +++++--- .../deserializable.ts} | 0 .../deserializer.ts} | 2 +- .../src/app/shared/models/base/displayable.ts | 14 +++++ .../models/base/projectable-base-model.ts | 17 ++++++ .../src/app/shared/models/base/projectable.ts | 14 +++++ .../app/shared/models/core/chat-message.ts | 6 +-- client/src/app/shared/models/core/config.ts | 4 +- .../src/app/shared/models/core/countdown.ts | 8 +-- .../shared/models/core/projector-message.ts | 10 ++-- .../src/app/shared/models/core/projector.ts | 4 +- client/src/app/shared/models/core/tag.ts | 4 +- .../src/app/shared/models/mediafiles/file.ts | 2 +- .../app/shared/models/mediafiles/mediafile.ts | 8 +-- .../src/app/shared/models/motions/category.ts | 4 +- .../app/shared/models/motions/motion-block.ts | 15 +++--- .../models/motions/motion-change-reco.ts | 6 +-- .../models/motions/motion-comment-section.ts | 4 +- .../shared/models/motions/motion-comment.ts | 2 +- .../app/shared/models/motions/motion-log.ts | 2 +- .../shared/models/motions/motion-submitter.ts | 2 +- .../src/app/shared/models/motions/motion.ts | 41 ++++++++++---- .../shared/models/motions/workflow-state.ts | 2 +- .../src/app/shared/models/motions/workflow.ts | 4 +- client/src/app/shared/models/topics/topic.ts | 19 +++++-- client/src/app/shared/models/users/group.ts | 4 +- .../app/shared/models/users/personal-note.ts | 7 ++- client/src/app/shared/models/users/user.ts | 12 +++-- client/src/app/site/base-repository.ts | 2 +- client/src/app/site/base-view-model.ts | 36 +++---------- .../motion-detail.component.html | 8 +-- .../app/site/motions/models/view-motion.ts | 2 +- openslides/agenda/models.py | 8 +-- openslides/agenda/serializers.py | 2 +- openslides/assignments/models.py | 11 ++-- openslides/motions/models.py | 39 ++++++++------ openslides/topics/models.py | 10 +++- tests/integration/agenda/test_viewset.py | 2 +- 52 files changed, 346 insertions(+), 185 deletions(-) delete mode 100644 client/src/app/shared/components/search-value-selector/search-value-selector.interfaces.ts create mode 100644 client/src/app/shared/models/base/agenda-base-model.ts create mode 100644 client/src/app/shared/models/base/agenda-information.ts rename client/src/app/shared/models/{base.model.ts => base/base-model.ts} (82%) rename client/src/app/shared/models/{deserializable.model.ts => base/deserializable.ts} (100%) rename client/src/app/shared/models/{deserializer.model.ts => base/deserializer.ts} (95%) create mode 100644 client/src/app/shared/models/base/displayable.ts create mode 100644 client/src/app/shared/models/base/projectable-base-model.ts create mode 100644 client/src/app/shared/models/base/projectable.ts diff --git a/client/src/app/core/services/collectionStringModelMapper.service.ts b/client/src/app/core/services/collectionStringModelMapper.service.ts index 67c78922f..58dfa9e06 100644 --- a/client/src/app/core/services/collectionStringModelMapper.service.ts +++ b/client/src/app/core/services/collectionStringModelMapper.service.ts @@ -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. diff --git a/client/src/app/core/services/data-send.service.ts b/client/src/app/core/services/data-send.service.ts index 4321ec527..0b0d3c3f5 100644 --- a/client/src/app/core/services/data-send.service.ts +++ b/client/src/app/core/services/data-send.service.ts @@ -1,6 +1,6 @@ import { Injectable } from '@angular/core'; 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 { tap } from 'rxjs/operators'; diff --git a/client/src/app/core/services/data-store.service.ts b/client/src/app/core/services/data-store.service.ts index c5c67383d..063703330 100644 --- a/client/src/app/core/services/data-store.service.ts +++ b/client/src/app/core/services/data-store.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@angular/core'; 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 { CollectionStringModelMapperService } from './collectionStringModelMapper.service'; diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.html b/client/src/app/shared/components/search-value-selector/search-value-selector.component.html index 318af3f1b..cfedb9bfb 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.html +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.html @@ -2,7 +2,7 @@ - {{selectedItem.toString()}} + {{selectedItem.getTitle(translate)}} diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts b/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts index b33d464f8..c7f24ab38 100644 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts +++ b/client/src/app/shared/components/search-value-selector/search-value-selector.component.ts @@ -2,8 +2,9 @@ import { Component, OnInit, Input, ViewChild } from '@angular/core'; import { FormControl, FormGroup } from '@angular/forms'; import { ReplaySubject, Subject } from 'rxjs'; import { MatSelect } from '@angular/material'; -import { SelectorItem } from './search-value-selector.interfaces'; import { takeUntil } from 'rxjs/operators'; +import { Displayable } from '../../models/base/displayable'; +import { TranslateService } from '@ngx-translate/core'; /** * Reusable Searchable Value Selector @@ -27,17 +28,6 @@ import { takeUntil } from 'rxjs/operators'; * * ``` * - * ### 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({ @@ -60,7 +50,7 @@ export class SearchValueSelectorComponent implements OnInit { /** * List of the filtered content, when entering somithing in the search bar */ - public filteredItems: ReplaySubject = new ReplaySubject(1); + public filteredItems: ReplaySubject = new ReplaySubject(1); /** * Decide if this should be a single or multi-select-field @@ -72,7 +62,7 @@ export class SearchValueSelectorComponent implements OnInit { * The Input List Values */ @Input() - public InputListValues: SelectorItem[]; + public InputListValues: Displayable[]; /** * Placeholder of the List @@ -115,7 +105,7 @@ export class SearchValueSelectorComponent implements OnInit { /** * Empty constructor */ - public constructor() {} + public constructor(public translate: TranslateService) {} /** * 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 * @param item the selected item to be removed */ - public remove(item: SelectorItem): void { + public remove(item: Displayable): void { const myArr = this.thisSelector.value; const index = myArr.indexOf(item, 0); // my model was the form according to fix diff --git a/client/src/app/shared/components/search-value-selector/search-value-selector.interfaces.ts b/client/src/app/shared/components/search-value-selector/search-value-selector.interfaces.ts deleted file mode 100644 index f60236209..000000000 --- a/client/src/app/shared/components/search-value-selector/search-value-selector.interfaces.ts +++ /dev/null @@ -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; -} diff --git a/client/src/app/shared/models/agenda/item.ts b/client/src/app/shared/models/agenda/item.ts index 6c7931ae0..46faac9ad 100644 --- a/client/src/app/shared/models/agenda/item.ts +++ b/client/src/app/shared/models/agenda/item.ts @@ -1,6 +1,10 @@ -import { BaseModel } from '../base.model'; +import { ProjectableBaseModel } from '../base/projectable-base-model'; 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 { id: number; collection: string; @@ -10,11 +14,11 @@ interface ContentObject { * Representations of agenda Item * @ignore */ -export class Item extends BaseModel { +export class Item extends ProjectableBaseModel { public id: number; public item_number: string; public title: string; - public list_view_title: string; + public title_with_type: string; public comment: string; public closed: boolean; public type: number; @@ -30,6 +34,23 @@ export class Item extends BaseModel { super('agenda/item', input); } + // Note: This has to be used in the agenda repository + /*public get contentObject(): AgendaBaseModel { + const contentObject = this.DS.get(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 { 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; } + + // 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); diff --git a/client/src/app/shared/models/agenda/speaker.ts b/client/src/app/shared/models/agenda/speaker.ts index c1e53d0a8..0348a3e1d 100644 --- a/client/src/app/shared/models/agenda/speaker.ts +++ b/client/src/app/shared/models/agenda/speaker.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * Representation of a speaker in an agenda item diff --git a/client/src/app/shared/models/assignments/assignment-user.ts b/client/src/app/shared/models/assignments/assignment-user.ts index 092f86c32..f2904552a 100644 --- a/client/src/app/shared/models/assignments/assignment-user.ts +++ b/client/src/app/shared/models/assignments/assignment-user.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * Content of the 'assignment_related_users' property diff --git a/client/src/app/shared/models/assignments/assignment.ts b/client/src/app/shared/models/assignments/assignment.ts index aa9260d5a..fd86b28da 100644 --- a/client/src/app/shared/models/assignments/assignment.ts +++ b/client/src/app/shared/models/assignments/assignment.ts @@ -1,12 +1,12 @@ -import { BaseModel } from '../base.model'; import { AssignmentUser } from './assignment-user'; import { Poll } from './poll'; +import { AgendaBaseModel } from '../base/agenda-base-model'; /** * Representation of an assignment. * @ignore */ -export class Assignment extends BaseModel { +export class Assignment extends AgendaBaseModel { public id: number; public title: string; public description: string; @@ -19,7 +19,7 @@ export class Assignment extends BaseModel { public tags_id: number[]; public constructor(input?: any) { - super('assignments/assignment', input); + super('assignments/assignment', 'Assignment', input); } public get candidateIds(): number[] { @@ -48,9 +48,13 @@ export class Assignment extends BaseModel { } } - public toString(): string { + public getTitle(): string { return this.title; } + + public getDetailStateURL(): string { + return 'TODO'; + } } -BaseModel.registerCollectionElement('assignments/assignment', Assignment); +AgendaBaseModel.registerCollectionElement('assignments/assignment', Assignment); diff --git a/client/src/app/shared/models/assignments/poll-option.ts b/client/src/app/shared/models/assignments/poll-option.ts index 5329f73ad..ddbc21cf7 100644 --- a/client/src/app/shared/models/assignments/poll-option.ts +++ b/client/src/app/shared/models/assignments/poll-option.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * Representation of a poll option diff --git a/client/src/app/shared/models/assignments/poll.ts b/client/src/app/shared/models/assignments/poll.ts index 1375cd4a3..9ec0ab57b 100644 --- a/client/src/app/shared/models/assignments/poll.ts +++ b/client/src/app/shared/models/assignments/poll.ts @@ -1,5 +1,5 @@ import { PollOption } from './poll-option'; -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * Content of the 'polls' property of assignments diff --git a/client/src/app/shared/models/base/agenda-base-model.ts b/client/src/app/shared/models/base/agenda-base-model.ts new file mode 100644 index 000000000..755a7bfa7 --- /dev/null +++ b/client/src/app/shared/models/base/agenda-base-model.ts @@ -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; +} diff --git a/client/src/app/shared/models/base/agenda-information.ts b/client/src/app/shared/models/base/agenda-information.ts new file mode 100644 index 000000000..832f1bd45 --- /dev/null +++ b/client/src/app/shared/models/base/agenda-information.ts @@ -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; +} diff --git a/client/src/app/shared/models/base.model.ts b/client/src/app/shared/models/base/base-model.ts similarity index 82% rename from client/src/app/shared/models/base.model.ts rename to client/src/app/shared/models/base/base-model.ts index a2372807d..ddc7fab76 100644 --- a/client/src/app/shared/models/base.model.ts +++ b/client/src/app/shared/models/base/base-model.ts @@ -1,7 +1,7 @@ import { OpenSlidesComponent } from 'app/openslides.component'; -import { Deserializable } from './deserializable.model'; -import { CollectionStringModelMapperService } from '../../core/services/collectionStringModelMapper.service'; -import { SelectorItem } from '../components/search-value-selector/search-value-selector.interfaces'; +import { Deserializable } from './deserializable'; +import { CollectionStringModelMapperService } from '../../../core/services/collectionStringModelMapper.service'; +import { Displayable } from './displayable'; export interface ModelConstructor { new (...args: any[]): T; @@ -10,7 +10,7 @@ export interface ModelConstructor { /** * 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. * @param collectionString @@ -56,10 +56,16 @@ export abstract class BaseModel extends OpenSlidesComponent implements Deseriali } }); } - /** - * force children to have a toString() method - */ - public abstract toString(): string; + + public abstract getTitle(): string; + + public getListTitle(): string { + return this.getTitle(); + } + + public toString(): string { + return this.getTitle(); + } /** * returns the collectionString. diff --git a/client/src/app/shared/models/deserializable.model.ts b/client/src/app/shared/models/base/deserializable.ts similarity index 100% rename from client/src/app/shared/models/deserializable.model.ts rename to client/src/app/shared/models/base/deserializable.ts diff --git a/client/src/app/shared/models/deserializer.model.ts b/client/src/app/shared/models/base/deserializer.ts similarity index 95% rename from client/src/app/shared/models/deserializer.model.ts rename to client/src/app/shared/models/base/deserializer.ts index 33d50ef2a..a21b40373 100644 --- a/client/src/app/shared/models/deserializer.model.ts +++ b/client/src/app/shared/models/base/deserializer.ts @@ -1,4 +1,4 @@ -import { Deserializable } from './deserializable.model'; +import { Deserializable } from './deserializable'; /** * Abstract base class for a basic implementation of Deserializable. diff --git a/client/src/app/shared/models/base/displayable.ts b/client/src/app/shared/models/base/displayable.ts new file mode 100644 index 000000000..9be35f9f4 --- /dev/null +++ b/client/src/app/shared/models/base/displayable.ts @@ -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; +} diff --git a/client/src/app/shared/models/base/projectable-base-model.ts b/client/src/app/shared/models/base/projectable-base-model.ts new file mode 100644 index 000000000..fc10e11d3 --- /dev/null +++ b/client/src/app/shared/models/base/projectable-base-model.ts @@ -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(); + } +} diff --git a/client/src/app/shared/models/base/projectable.ts b/client/src/app/shared/models/base/projectable.ts new file mode 100644 index 000000000..d2f002f76 --- /dev/null +++ b/client/src/app/shared/models/base/projectable.ts @@ -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; +} diff --git a/client/src/app/shared/models/core/chat-message.ts b/client/src/app/shared/models/core/chat-message.ts index d254692f3..146186195 100644 --- a/client/src/app/shared/models/core/chat-message.ts +++ b/client/src/app/shared/models/core/chat-message.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; /** * Representation of chat messages. @@ -14,8 +14,8 @@ export class ChatMessage extends BaseModel { super('core/chat-message', input); } - public toString(): string { - return this.message; + public getTitle(): string { + return 'Chatmessage'; } } diff --git a/client/src/app/shared/models/core/config.ts b/client/src/app/shared/models/core/config.ts index 51e174ca3..2899902b6 100644 --- a/client/src/app/shared/models/core/config.ts +++ b/client/src/app/shared/models/core/config.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; /** * Representation of a config variable @@ -13,7 +13,7 @@ export class Config extends BaseModel { super('core/config', input); } - public toString(): string { + public getTitle(): string { return this.key; } } diff --git a/client/src/app/shared/models/core/countdown.ts b/client/src/app/shared/models/core/countdown.ts index 8aafcd3f4..06245258e 100644 --- a/client/src/app/shared/models/core/countdown.ts +++ b/client/src/app/shared/models/core/countdown.ts @@ -1,10 +1,10 @@ -import { BaseModel } from '../base.model'; +import { ProjectableBaseModel } from '../base/projectable-base-model'; /** * Representation of a countdown * @ignore */ -export class Countdown extends BaseModel { +export class Countdown extends ProjectableBaseModel { public id: number; public description: string; public default_time: number; @@ -15,9 +15,9 @@ export class Countdown extends BaseModel { super('core/countdown'); } - public toString(): string { + public getTitle(): string { return this.description; } } -BaseModel.registerCollectionElement('core/countdown', Countdown); +ProjectableBaseModel.registerCollectionElement('core/countdown', Countdown); diff --git a/client/src/app/shared/models/core/projector-message.ts b/client/src/app/shared/models/core/projector-message.ts index b5b59db7b..a49dcf79e 100644 --- a/client/src/app/shared/models/core/projector-message.ts +++ b/client/src/app/shared/models/core/projector-message.ts @@ -1,10 +1,10 @@ -import { BaseModel } from '../base.model'; +import { ProjectableBaseModel } from '../base/projectable-base-model'; /** * Representation of a projector message. * @ignore */ -export class ProjectorMessage extends BaseModel { +export class ProjectorMessage extends ProjectableBaseModel { public id: number; public message: string; @@ -12,9 +12,9 @@ export class ProjectorMessage extends BaseModel { super('core/projector-message', input); } - public toString(): string { - return this.message; + public getTitle(): string { + return 'Projectormessage'; } } -BaseModel.registerCollectionElement('core/projector-message', ProjectorMessage); +ProjectableBaseModel.registerCollectionElement('core/projector-message', ProjectorMessage); diff --git a/client/src/app/shared/models/core/projector.ts b/client/src/app/shared/models/core/projector.ts index 7af72f9f0..d2b692469 100644 --- a/client/src/app/shared/models/core/projector.ts +++ b/client/src/app/shared/models/core/projector.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; /** * Representation of a projector. Has the nested property "projectiondefaults" @@ -19,7 +19,7 @@ export class Projector extends BaseModel { super('core/projector', input); } - public toString(): string { + public getTitle(): string { return this.name; } } diff --git a/client/src/app/shared/models/core/tag.ts b/client/src/app/shared/models/core/tag.ts index 7447b06f8..44e0fd75a 100644 --- a/client/src/app/shared/models/core/tag.ts +++ b/client/src/app/shared/models/core/tag.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; /** * Representation of a tag. @@ -12,7 +12,7 @@ export class Tag extends BaseModel { super('core/tag', input); } - public toString(): string { + public getTitle(): string { return this.name; } } diff --git a/client/src/app/shared/models/mediafiles/file.ts b/client/src/app/shared/models/mediafiles/file.ts index 8ac48c45d..e9a7f812b 100644 --- a/client/src/app/shared/models/mediafiles/file.ts +++ b/client/src/app/shared/models/mediafiles/file.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * The name and the type of a mediaFile. diff --git a/client/src/app/shared/models/mediafiles/mediafile.ts b/client/src/app/shared/models/mediafiles/mediafile.ts index bb5b63145..dfe9c941f 100644 --- a/client/src/app/shared/models/mediafiles/mediafile.ts +++ b/client/src/app/shared/models/mediafiles/mediafile.ts @@ -1,11 +1,11 @@ -import { BaseModel } from '../base.model'; import { File } from './file'; +import { ProjectableBaseModel } from '../base/projectable-base-model'; /** * Representation of MediaFile. Has the nested property "File" * @ignore */ -export class Mediafile extends BaseModel { +export class Mediafile extends ProjectableBaseModel { public id: number; public title: string; public mediafile: File; @@ -24,9 +24,9 @@ export class Mediafile extends BaseModel { this.mediafile = new File(input.mediafile); } - public toString(): string { + public getTitle(): string { return this.title; } } -BaseModel.registerCollectionElement('amediafiles/mediafile', Mediafile); +ProjectableBaseModel.registerCollectionElement('amediafiles/mediafile', Mediafile); diff --git a/client/src/app/shared/models/motions/category.ts b/client/src/app/shared/models/motions/category.ts index ed7243086..d772a8925 100644 --- a/client/src/app/shared/models/motions/category.ts +++ b/client/src/app/shared/models/motions/category.ts @@ -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" @@ -13,7 +13,7 @@ export class Category extends BaseModel { super('motions/category', input); } - public toString(): string { + public getTitle(): string { return this.prefix + ' - ' + this.name; } } diff --git a/client/src/app/shared/models/motions/motion-block.ts b/client/src/app/shared/models/motions/motion-block.ts index dc11f8606..da2eb781f 100644 --- a/client/src/app/shared/models/motions/motion-block.ts +++ b/client/src/app/shared/models/motions/motion-block.ts @@ -1,22 +1,25 @@ -import { BaseModel } from '../base.model'; -import { Item } from '../agenda/item'; +import { AgendaBaseModel } from '../base/agenda-base-model'; /** * Representation of a motion block. * @ignore */ -export class MotionBlock extends BaseModel { +export class MotionBlock extends AgendaBaseModel { public id: number; public title: string; public agenda_item_id: number; 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; } + + public getDetailStateURL(): string { + return 'TODO'; + } } -BaseModel.registerCollectionElement('motions/motion-block', MotionBlock); +AgendaBaseModel.registerCollectionElement('motions/motion-block', MotionBlock); diff --git a/client/src/app/shared/models/motions/motion-change-reco.ts b/client/src/app/shared/models/motions/motion-change-reco.ts index a46e05996..a562f7b36 100644 --- a/client/src/app/shared/models/motions/motion-change-reco.ts +++ b/client/src/app/shared/models/motions/motion-change-reco.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; /** * Representation of a motion change recommendation. @@ -19,8 +19,8 @@ export class MotionChangeReco extends BaseModel { super('motions/motion-change-recommendation', input); } - public toString(): string { - return this.text; + public getTitle(): string { + return 'Changerecommendation'; } } diff --git a/client/src/app/shared/models/motions/motion-comment-section.ts b/client/src/app/shared/models/motions/motion-comment-section.ts index 81e178e7c..24fff9c02 100644 --- a/client/src/app/shared/models/motions/motion-comment-section.ts +++ b/client/src/app/shared/models/motions/motion-comment-section.ts @@ -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" @@ -14,7 +14,7 @@ export class MotionCommentSection extends BaseModel { super('motions/motion-comment-section', input); } - public toString(): string { + public getTitle(): string { return this.name; } } diff --git a/client/src/app/shared/models/motions/motion-comment.ts b/client/src/app/shared/models/motions/motion-comment.ts index 78447574b..69c1379ee 100644 --- a/client/src/app/shared/models/motions/motion-comment.ts +++ b/client/src/app/shared/models/motions/motion-comment.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * Representation of a Motion Comment. diff --git a/client/src/app/shared/models/motions/motion-log.ts b/client/src/app/shared/models/motions/motion-log.ts index 952f00cec..1dc1fd15d 100644 --- a/client/src/app/shared/models/motions/motion-log.ts +++ b/client/src/app/shared/models/motions/motion-log.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; /** * Representation of a Motion Log. diff --git a/client/src/app/shared/models/motions/motion-submitter.ts b/client/src/app/shared/models/motions/motion-submitter.ts index 50422eec9..d5bb9e8ac 100644 --- a/client/src/app/shared/models/motions/motion-submitter.ts +++ b/client/src/app/shared/models/motions/motion-submitter.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; import { User } from '../users/user'; /** diff --git a/client/src/app/shared/models/motions/motion.ts b/client/src/app/shared/models/motions/motion.ts index 99f3b5e5e..9779caa2a 100644 --- a/client/src/app/shared/models/motions/motion.ts +++ b/client/src/app/shared/models/motions/motion.ts @@ -1,9 +1,9 @@ -import { BaseModel } from '../base.model'; import { MotionSubmitter } from './motion-submitter'; import { MotionLog } from './motion-log'; import { Category } from './category'; import { MotionComment } from './motion-comment'; import { Workflow } from './workflow'; +import { AgendaBaseModel } from '../base/agenda-base-model'; /** * Representation of Motion. @@ -12,7 +12,7 @@ import { Workflow } from './workflow'; * * @ignore */ -export class Motion extends BaseModel { +export class Motion extends AgendaBaseModel { public id: number; public identifier: string; public title: string; @@ -36,12 +36,12 @@ export class Motion extends BaseModel { public recommendation_extension: string; public tags_id: number[]; public attachments_id: number[]; - public polls: BaseModel[]; + public polls: Object[]; public agenda_item_id: number; public log_messages: MotionLog[]; 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); } - /** - * returns the Motion name - */ - public toString(): string { + public getTitle(): string { return this.title; } + public getAgendaTitle(): string { + // if the identifier is set, the title will be 'Motion '. + 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 ' 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 { Object.assign(this, input); @@ -91,6 +110,6 @@ export class Motion extends BaseModel { /** * Hack to get them loaded at last */ -BaseModel.registerCollectionElement('motions/motion', Motion); -BaseModel.registerCollectionElement('motions/category', Category); -BaseModel.registerCollectionElement('motions/workflow', Workflow); +AgendaBaseModel.registerCollectionElement('motions/motion', Motion); +AgendaBaseModel.registerCollectionElement('motions/category', Category); +AgendaBaseModel.registerCollectionElement('motions/workflow', Workflow); diff --git a/client/src/app/shared/models/motions/workflow-state.ts b/client/src/app/shared/models/motions/workflow-state.ts index 99bf6f27d..75e9c5d13 100644 --- a/client/src/app/shared/models/motions/workflow-state.ts +++ b/client/src/app/shared/models/motions/workflow-state.ts @@ -1,4 +1,4 @@ -import { Deserializer } from '../deserializer.model'; +import { Deserializer } from '../base/deserializer'; import { Workflow } from './workflow'; /** diff --git a/client/src/app/shared/models/motions/workflow.ts b/client/src/app/shared/models/motions/workflow.ts index ba54d6193..8e8f30ac7 100644 --- a/client/src/app/shared/models/motions/workflow.ts +++ b/client/src/app/shared/models/motions/workflow.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; import { WorkflowState } from './workflow-state'; /** @@ -54,7 +54,7 @@ export class Workflow extends BaseModel { } } - public toString(): string { + public getTitle(): string { return this.name; } } diff --git a/client/src/app/shared/models/topics/topic.ts b/client/src/app/shared/models/topics/topic.ts index 71b1671d0..96a228a3a 100644 --- a/client/src/app/shared/models/topics/topic.ts +++ b/client/src/app/shared/models/topics/topic.ts @@ -1,10 +1,10 @@ -import { BaseModel } from '../base.model'; +import { AgendaBaseModel } from '../base/agenda-base-model'; /** * Representation of a topic. * @ignore */ -export class Topic extends BaseModel { +export class Topic extends AgendaBaseModel { public id: number; public title: string; public text: string; @@ -12,12 +12,21 @@ export class Topic extends BaseModel { public agenda_item_id: number; public constructor(input?: any) { - super('topics/topic', input); + super('topics/topic', 'Topic', input); } - public toString(): string { + public getTitle(): string { 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); diff --git a/client/src/app/shared/models/users/group.ts b/client/src/app/shared/models/users/group.ts index fed9c4df4..4b52724d6 100644 --- a/client/src/app/shared/models/users/group.ts +++ b/client/src/app/shared/models/users/group.ts @@ -1,4 +1,4 @@ -import { BaseModel } from '../base.model'; +import { BaseModel } from '../base/base-model'; /** * Representation of user group. @@ -13,7 +13,7 @@ export class Group extends BaseModel { super('users/group', input); } - public toString(): string { + public getTitle(): string { return this.name; } } diff --git a/client/src/app/shared/models/users/personal-note.ts b/client/src/app/shared/models/users/personal-note.ts index 48510110c..a9a0bd434 100644 --- a/client/src/app/shared/models/users/personal-note.ts +++ b/client/src/app/shared/models/users/personal-note.ts @@ -1,5 +1,4 @@ -import { BaseModel } from '../base.model'; -import { User } from './user'; +import { BaseModel } from '../base/base-model'; /** * Representation of users personal note. @@ -14,8 +13,8 @@ export class PersonalNote extends BaseModel { super('users/personal-note', input); } - public toString(): string { - return this.notes.toString(); + public getTitle(): string { + return 'Personal note'; } } diff --git a/client/src/app/shared/models/users/user.ts b/client/src/app/shared/models/users/user.ts index b6fd2c3e3..c49b7ae7f 100644 --- a/client/src/app/shared/models/users/user.ts +++ b/client/src/app/shared/models/users/user.ts @@ -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. * @ignore */ -export class User extends BaseModel { +export class User extends ProjectableBaseModel { public id: number; public username: string; public title: string; @@ -77,9 +77,13 @@ export class User extends BaseModel { return shortName.trim(); } - public toString(): string { + public getTitle(): string { + return this.full_name; + } + + public getListViewTitle(): string { return this.short_name; } } -BaseModel.registerCollectionElement('users/user', User); +ProjectableBaseModel.registerCollectionElement('users/user', User); diff --git a/client/src/app/site/base-repository.ts b/client/src/app/site/base-repository.ts index 75f15ef90..25d0d2998 100644 --- a/client/src/app/site/base-repository.ts +++ b/client/src/app/site/base-repository.ts @@ -1,7 +1,7 @@ import { OpenSlidesComponent } from '../openslides.component'; import { BehaviorSubject, Observable } from 'rxjs'; 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 { DataStoreService } from '../core/services/data-store.service'; diff --git a/client/src/app/site/base-view-model.ts b/client/src/app/site/base-view-model.ts index 5f1749184..7e71b7662 100644 --- a/client/src/app/site/base-view-model.ts +++ b/client/src/app/site/base-view-model.ts @@ -1,39 +1,19 @@ -import { TranslateService } from '@ngx-translate/core'; -import { BaseModel } from '../shared/models/base.model'; +import { BaseModel } from '../shared/models/base/base-model'; +import { Displayable } from '../shared/models/base/displayable'; /** * 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; - /** - * Should return the title for the detail view. - * @param translate - */ - public abstract getTitle(translate: TranslateService): string; + public abstract getTitle(): string; - /** - * Should return the title for the list view. - * @param translate - */ - public getListTitle(translate: TranslateService): string { - return this.getTitle(translate); + public getListTitle(): string { + return this.getTitle(); } - /** - * Should return the title for the projector. - * @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); + public toString(): string { + return this.getTitle(); } } diff --git a/client/src/app/site/motions/components/motion-detail/motion-detail.component.html b/client/src/app/site/motions/components/motion-detail/motion-detail.component.html index fe3978d7a..d445f640b 100644 --- a/client/src/app/site/motions/components/motion-detail/motion-detail.component.html +++ b/client/src/app/site/motions/components/motion-detail/motion-detail.component.html @@ -153,8 +153,8 @@

Submitters

-
    -
  • {{submitters}}
  • +
      +
    • {{submitter.full_name}}
@@ -169,8 +169,8 @@

Supporters

-
    -
  • {{supporters}}
  • +
      +
    • {{supporter.full_name}}
diff --git a/client/src/app/site/motions/models/view-motion.ts b/client/src/app/site/motions/models/view-motion.ts index 0149b4f55..66671ea25 100644 --- a/client/src/app/site/motions/models/view-motion.ts +++ b/client/src/app/site/motions/models/view-motion.ts @@ -3,7 +3,7 @@ import { Category } from '../../../shared/models/motions/category'; import { User } from '../../../shared/models/users/user'; import { Workflow } from '../../../shared/models/motions/workflow'; 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 { TranslateService } from '@ngx-translate/core'; diff --git a/openslides/agenda/models.py b/openslides/agenda/models.py index a6d5a161d..45ecae422 100644 --- a/openslides/agenda/models.py +++ b/openslides/agenda/models.py @@ -296,14 +296,14 @@ class Item(RESTModelMixin, models.Model): 'method on your related model.') @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: - return self.content_object.get_agenda_list_view_title() + return self.content_object.get_agenda_title_with_type() 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.') def is_internal(self): diff --git a/openslides/agenda/serializers.py b/openslides/agenda/serializers.py index 13dfebc63..edcb49ea1 100644 --- a/openslides/agenda/serializers.py +++ b/openslides/agenda/serializers.py @@ -45,7 +45,7 @@ class ItemSerializer(ModelSerializer): 'id', 'item_number', 'title', - 'list_view_title', + 'title_with_type', 'comment', 'closed', 'type', diff --git a/openslides/assignments/models.py b/openslides/assignments/models.py index aa387e25b..b970f8e56 100644 --- a/openslides/assignments/models.py +++ b/openslides/assignments/models.py @@ -338,16 +338,17 @@ class Assignment(RESTModelMixin, models.Model): agenda_item_update_information: Dict[str, Any] = {} def get_agenda_title(self): + """ + Returns the title for the agenda. + """ 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. - - Contains agenda item number, title and assignment verbose name. + Return a title for the agenda with the appended assignment verbose name. 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 def agenda_item(self): diff --git a/openslides/motions/models.py b/openslides/motions/models.py index 0223d624d..c38392080 100644 --- a/openslides/motions/models.py +++ b/openslides/motions/models.py @@ -436,26 +436,30 @@ class Motion(RESTModelMixin, models.Model): 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 - and title in the agenda. - """ - 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. + If the identifier is given, the title consists of the motion verbose name + and the identifier. Note: It has to be the same return value like in JavaScript. """ if self.identifier: - string = '%s %s' % (_(self._meta.verbose_name), self.identifier) + title = '%s %s' % (_(self._meta.verbose_name), self.identifier) else: - string = '%s (%s)' % (_(self._meta.verbose_name), self.title) - return string + title = self.title + 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 def agenda_item(self): @@ -781,6 +785,7 @@ class MotionBlock(RESTModelMixin, models.Model): agenda_items = GenericRelation(Item, related_name='topics') class Meta: + verbose_name = ugettext_noop('Motion block') default_permissions = () def __str__(self): @@ -821,8 +826,8 @@ class MotionBlock(RESTModelMixin, models.Model): def get_agenda_title(self): return self.title - def get_agenda_list_view_title(self): - return self.title + def get_agenda_title_with_type(self): + return '%s (%s)' % (self.get_agenda_title(), _(self._meta.verbose_name)) class MotionLog(RESTModelMixin, models.Model): diff --git a/openslides/topics/models.py b/openslides/topics/models.py index ecee8331f..ff9185bc3 100644 --- a/openslides/topics/models.py +++ b/openslides/topics/models.py @@ -78,7 +78,13 @@ class Topic(RESTModelMixin, models.Model): return self.agenda_item.pk def get_agenda_title(self): + """ + Returns the title for the agenda. + """ return self.title - def get_agenda_list_view_title(self): - return self.title + def get_agenda_title_with_type(self): + """ + Returns the agenda title. Topicy should not get a type postfix. + """ + return self.get_agenda_title() diff --git a/tests/integration/agenda/test_viewset.py b/tests/integration/agenda/test_viewset.py index 678aa49c9..9a44ad069 100644 --- a/tests/integration/agenda/test_viewset.py +++ b/tests/integration/agenda/test_viewset.py @@ -69,7 +69,7 @@ class RetrieveItem(TestCase): 'content_object',))) forbidden_keys = ( 'item_number', - 'list_view_title', + 'title_with_type', 'comment', 'closed', 'type',