Optional agenda items
This commit is contained in:
parent
a5e1646f3c
commit
8ff0f73477
@ -1,7 +1,5 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
|
||||
import { map } from 'rxjs/operators';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { CollectionStringMapperService } from '../../core-services/collection-string-mapper.service';
|
||||
@ -14,7 +12,8 @@ import { TreeIdNode } from 'app/core/ui-services/tree.service';
|
||||
import { ViewItem, ItemTitleInformation } from 'app/site/agenda/models/view-item';
|
||||
import {
|
||||
BaseViewModelWithAgendaItem,
|
||||
isBaseViewModelWithAgendaItem
|
||||
isBaseViewModelWithAgendaItem,
|
||||
IBaseViewModelWithAgendaItem
|
||||
} from 'app/site/base/base-view-model-with-agenda-item';
|
||||
import { ViewModelStoreService } from 'app/core/core-services/view-model-store.service';
|
||||
import { BaseViewModel } from 'app/site/base/base-view-model';
|
||||
@ -24,6 +23,7 @@ import { Topic } from 'app/shared/models/topics/topic';
|
||||
import { Assignment } from 'app/shared/models/assignments/assignment';
|
||||
import { BaseIsAgendaItemContentObjectRepository } from '../base-is-agenda-item-content-object-repository';
|
||||
import { BaseHasContentObjectRepository } from '../base-has-content-object-repository';
|
||||
import { Identifiable } from 'app/shared/models/base/identifiable';
|
||||
|
||||
/**
|
||||
* Repository service for items
|
||||
@ -105,16 +105,10 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
agendaItem.content_object.collection,
|
||||
agendaItem.content_object.id
|
||||
);
|
||||
if (!contentObject) {
|
||||
if (!contentObject || !isBaseViewModelWithAgendaItem(contentObject)) {
|
||||
return null;
|
||||
}
|
||||
if (isBaseViewModelWithAgendaItem(contentObject)) {
|
||||
return contentObject;
|
||||
} else {
|
||||
throw new Error(
|
||||
`The content object (${agendaItem.content_object.collection}, ${agendaItem.content_object.id}) of item ${agendaItem.id} is not a BaseAgendaItemViewModel.`
|
||||
);
|
||||
}
|
||||
return contentObject;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -124,19 +118,6 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
await this.httpService.post('/rest/agenda/item/numbering/');
|
||||
}
|
||||
|
||||
/**
|
||||
* @ignore
|
||||
*
|
||||
* TODO: Usually, agenda items are deleted with their corresponding content object
|
||||
* However, deleting an agenda item might be interpretet with "removing an item
|
||||
* from the agenda" permanently. Usually, items might juse be hidden but not
|
||||
* deleted (right now)
|
||||
*/
|
||||
public async delete(item: ViewItem): Promise<void> {
|
||||
const restUrl = `/rest/${item.contentObject.collectionString}/${item.contentObject.id}/`;
|
||||
await this.httpService.delete(restUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: Copied from BaseRepository and added the cloned model to write back the
|
||||
* item_number correctly. This must be reversed with #4738 (indroduced with #4639)
|
||||
@ -158,13 +139,23 @@ export class ItemRepositoryService extends BaseHasContentObjectRepository<
|
||||
return await this.httpService.put(restPath, clone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agenda visibility from the config
|
||||
*
|
||||
* @return An observable to the default agenda visibility
|
||||
*/
|
||||
public getDefaultAgendaVisibility(): Observable<number> {
|
||||
return this.config.get('agenda_new_items_default_visibility').pipe(map(key => +key));
|
||||
public async addItemToAgenda(contentObject: IBaseViewModelWithAgendaItem<any>): Promise<Identifiable> {
|
||||
return await this.httpService.post('/rest/agenda/item/', {
|
||||
collection: contentObject.collectionString,
|
||||
id: contentObject.id
|
||||
});
|
||||
}
|
||||
|
||||
public async removeFromAgenda(item: ViewItem): Promise<void> {
|
||||
return await this.httpService.delete(`/rest/agenda/item/${item.id}/`);
|
||||
}
|
||||
|
||||
public async create(item: Item): Promise<Identifiable> {
|
||||
throw new Error('Use `addItemToAgenda` for creations');
|
||||
}
|
||||
|
||||
public async delete(item: ViewItem): Promise<void> {
|
||||
throw new Error('Use `removeFromAgenda` for deletions');
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -243,10 +243,10 @@ export abstract class BaseRepository<V extends BaseViewModel & T, M extends Base
|
||||
const sendModel = new this.baseModelCtor();
|
||||
sendModel.patchValues(model);
|
||||
|
||||
// Strips empty fields from the sending mode data.
|
||||
// Strips empty fields from the sending mode data (except false)
|
||||
// required for i.e. users, since group list is mandatory
|
||||
Object.keys(sendModel).forEach(key => {
|
||||
if (!sendModel[key]) {
|
||||
if (!sendModel[key] && sendModel[key] !== false) {
|
||||
delete sendModel[key];
|
||||
}
|
||||
});
|
||||
|
@ -0,0 +1,33 @@
|
||||
<ng-container *ngIf="showForm">
|
||||
<div [formGroup]="form">
|
||||
<mat-checkbox formControlName="agenda_create">
|
||||
<span translate>Add to agenda</span>
|
||||
</mat-checkbox>
|
||||
</div>
|
||||
|
||||
<ng-container *ngIf="!!checkbox.value">
|
||||
<!-- Visibility -->
|
||||
<div>
|
||||
<mat-form-field [formGroup]="form">
|
||||
<mat-select formControlName="agenda_type" placeholder="{{ 'Agenda visibility' | translate }}">
|
||||
<mat-option *ngFor="let type of ItemVisibilityChoices" [value]="type.key">
|
||||
<span>{{ type.name | translate }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Parent item -->
|
||||
<div *ngIf="itemObserver.value.length > 0">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[form]="form"
|
||||
[formControl]="form.get('agenda_parent_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
listname="{{ 'Parent agenda item' | translate }}"
|
||||
[InputListValues]="itemObserver"
|
||||
></os-search-value-selector>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ng-container>
|
@ -0,0 +1,33 @@
|
||||
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { FormBuilder } from '@angular/forms';
|
||||
|
||||
import { E2EImportsModule } from 'e2e-imports.module';
|
||||
import { AgendaContentObjectFormComponent } from './agenda-content-object-form.component';
|
||||
|
||||
describe('AgendaContentObjectFormComponent', () => {
|
||||
let component: AgendaContentObjectFormComponent;
|
||||
let fixture: ComponentFixture<AgendaContentObjectFormComponent>;
|
||||
|
||||
beforeEach(async(() => {
|
||||
TestBed.configureTestingModule({
|
||||
imports: [E2EImportsModule]
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(AgendaContentObjectFormComponent);
|
||||
component = fixture.componentInstance;
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
const formBuilder: FormBuilder = TestBed.get(FormBuilder);
|
||||
component.form = formBuilder.group({
|
||||
agenda_create: [''],
|
||||
agenda_parent_id: [],
|
||||
agenda_type: ['']
|
||||
});
|
||||
fixture.detectChanges();
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,66 @@
|
||||
import { Component, Input, OnInit } from '@angular/core';
|
||||
import { FormGroup, FormControl } from '@angular/forms';
|
||||
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
|
||||
type AgendaItemCreateChoices = 'always' | 'never' | 'default_yes' | 'default_no';
|
||||
|
||||
@Component({
|
||||
selector: 'os-agenda-content-object-form',
|
||||
templateUrl: './agenda-content-object-form.component.html',
|
||||
styleUrls: ['./agenda-content-object-form.component.scss']
|
||||
})
|
||||
export class AgendaContentObjectFormComponent implements OnInit {
|
||||
@Input()
|
||||
public form: FormGroup;
|
||||
|
||||
public showForm = false;
|
||||
|
||||
public checkbox: FormControl;
|
||||
|
||||
/**
|
||||
* Determine visibility states for the agenda that will be created implicitly
|
||||
*/
|
||||
public ItemVisibilityChoices = ItemVisibilityChoices;
|
||||
|
||||
/**
|
||||
* Subject for agenda items
|
||||
*/
|
||||
public itemObserver: BehaviorSubject<ViewItem[]>;
|
||||
|
||||
public constructor(private configService: ConfigService, private itemRepo: ItemRepositoryService) {}
|
||||
|
||||
public ngOnInit(): void {
|
||||
this.checkbox = this.form.controls.agenda_create as FormControl;
|
||||
|
||||
this.configService.get<AgendaItemCreateChoices>('agenda_item_creation').subscribe(value => {
|
||||
if (value === 'always') {
|
||||
this.showForm = true;
|
||||
this.checkbox.disable();
|
||||
this.form.patchValue({ agenda_create: true });
|
||||
} else if (value === 'never') {
|
||||
this.showForm = false;
|
||||
this.checkbox.disable();
|
||||
this.form.patchValue({ agenda_create: false });
|
||||
} else {
|
||||
const defaultValue = value === 'default_yes';
|
||||
// check if alrady touched..
|
||||
this.showForm = true;
|
||||
this.checkbox.enable();
|
||||
this.form.patchValue({ agenda_create: defaultValue });
|
||||
}
|
||||
});
|
||||
|
||||
// Set the default visibility using observers
|
||||
this.configService.get('agenda_new_items_default_visibility').subscribe(visibility => {
|
||||
this.form.get('agenda_type').setValue(+visibility);
|
||||
});
|
||||
|
||||
this.itemObserver = this.itemRepo.getViewModelListBehaviorSubject();
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import { BaseModelWithContentObject } from '../base/base-model-with-content-obje
|
||||
* Determine visibility states for agenda items
|
||||
* Coming from "ConfigVariables" property "agenda_hide_internal_items_on_projector"
|
||||
*/
|
||||
export const itemVisibilityChoices = [
|
||||
export const ItemVisibilityChoices = [
|
||||
{ key: 1, name: 'public', csvName: '' },
|
||||
{ key: 2, name: 'internal', csvName: 'internal' },
|
||||
{ key: 3, name: 'hidden', csvName: 'hidden' }
|
||||
|
@ -94,6 +94,7 @@ import { TileComponent } from './components/tile/tile.component';
|
||||
import { BlockTileComponent } from './components/block-tile/block-tile.component';
|
||||
import { IconContainerComponent } from './components/icon-container/icon-container.component';
|
||||
import { ListViewTableComponent } from './components/list-view-table/list-view-table.component';
|
||||
import { AgendaContentObjectFormComponent } from './components/agenda-content-object-form/agenda-content-object-form.component';
|
||||
|
||||
/**
|
||||
* Share Module for all "dumb" components and pipes.
|
||||
@ -230,7 +231,8 @@ import { ListViewTableComponent } from './components/list-view-table/list-view-t
|
||||
SpeakerButtonComponent,
|
||||
PblNgridModule,
|
||||
PblNgridMaterialModule,
|
||||
ListViewTableComponent
|
||||
ListViewTableComponent,
|
||||
AgendaContentObjectFormComponent
|
||||
],
|
||||
declarations: [
|
||||
PermsDirective,
|
||||
@ -264,7 +266,8 @@ import { ListViewTableComponent } from './components/list-view-table/list-view-t
|
||||
TileComponent,
|
||||
BlockTileComponent,
|
||||
IconContainerComponent,
|
||||
ListViewTableComponent
|
||||
ListViewTableComponent,
|
||||
AgendaContentObjectFormComponent
|
||||
],
|
||||
providers: [
|
||||
{ provide: DateAdapter, useClass: OpenSlidesDateAdapter },
|
||||
|
@ -6,7 +6,7 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { BaseImportService, NewEntry } from 'app/core/ui-services/base-import.service';
|
||||
import { CreateTopic } from './models/create-topic';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { TopicRepositoryService } from '../../core/repositories/topics/topic-repository.service';
|
||||
import { ViewCreateTopic } from './models/view-create-topic';
|
||||
|
||||
@ -162,13 +162,13 @@ export class AgendaImportService extends BaseImportService<ViewCreateTopic> {
|
||||
if (!input) {
|
||||
return 1; // default, public item
|
||||
} else if (typeof input === 'string') {
|
||||
const visibility = itemVisibilityChoices.find(choice => choice.csvName === input);
|
||||
const visibility = ItemVisibilityChoices.find(choice => choice.csvName === input);
|
||||
if (visibility) {
|
||||
return visibility.key;
|
||||
}
|
||||
} else if (input === 1) {
|
||||
// Compatibility with the old client's isInternal column
|
||||
const visibility = itemVisibilityChoices.find(choice => choice.name === 'Internal item');
|
||||
const visibility = ItemVisibilityChoices.find(choice => choice.name === 'Internal item');
|
||||
if (visibility) {
|
||||
return visibility.key;
|
||||
}
|
||||
|
@ -8,7 +8,7 @@ import { AgendaImportService } from '../../agenda-import.service';
|
||||
import { BaseImportListComponent } from 'app/site/base/base-import-list';
|
||||
import { CsvExportService } from 'app/core/ui-services/csv-export.service';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ViewCreateTopic } from '../../models/view-create-topic';
|
||||
|
||||
/**
|
||||
@ -72,7 +72,7 @@ export class AgendaImportListComponent extends BaseImportListComponent<ViewCreat
|
||||
* @returns A string, which may be empty if the type is not found in the visibilityChoices
|
||||
*/
|
||||
public getTypeString(type: number): string {
|
||||
const visibility = itemVisibilityChoices.find(choice => choice.key === type);
|
||||
const visibility = ItemVisibilityChoices.find(choice => choice.key === type);
|
||||
return visibility ? visibility.name : '';
|
||||
}
|
||||
|
||||
|
@ -175,9 +175,9 @@
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<!-- Delete selected -->
|
||||
<button mat-menu-item [disabled]="!selectedRows.length" class="red-warning-text" (click)="deleteSelected()">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
<button mat-menu-item [disabled]="!selectedRows.length" (click)="removeSelected()">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -198,7 +198,12 @@
|
||||
</button>
|
||||
|
||||
<!-- Delete Button -->
|
||||
<button mat-menu-item class="red-warning-text" (click)="onDelete(item)">
|
||||
<button mat-menu-item (click)="removeFromAgenda(item)" *ngIf="item.contentObjectData.collection !== 'topics/topic'">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
|
||||
<button mat-menu-item class="red-warning-text" (click)="deleteTopic(item)" *ngIf="item.contentObjectData.collection === 'topics/topic'">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span translate>Delete</span>
|
||||
</button>
|
||||
|
@ -24,6 +24,8 @@ import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
import { ViewListOfSpeakers } from '../../models/view-list-of-speakers';
|
||||
import { _ } from 'app/core/translate/translation-marker';
|
||||
import { TopicRepositoryService } from 'app/core/repositories/topics/topic-repository.service';
|
||||
import { ViewTopic } from '../../models/view-topic';
|
||||
|
||||
/**
|
||||
* List view for the agenda.
|
||||
@ -123,7 +125,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
public filterService: AgendaFilterListService,
|
||||
private agendaPdfService: AgendaPdfService,
|
||||
private pdfService: PdfDocumentService,
|
||||
private listOfSpeakersRepo: ListOfSpeakersRepositoryService
|
||||
private listOfSpeakersRepo: ListOfSpeakersRepositoryService,
|
||||
private topicRepo: TopicRepositoryService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar, storage);
|
||||
this.canMultiSelect = true;
|
||||
@ -194,7 +197,7 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
*/
|
||||
public async onAutoNumbering(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to number all agenda items?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
await this.repo.autoNumbering().then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
@ -215,15 +218,26 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete handler for a single item
|
||||
* Remove handler for a single item
|
||||
*
|
||||
* @param item The item to delete
|
||||
* @param item The item to remove from the agenda
|
||||
*/
|
||||
public async onDelete(item: ViewItem): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this entry?');
|
||||
public async removeFromAgenda(item: ViewItem): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to remove this entry from the agenda?');
|
||||
const content = item.contentObject.getTitle();
|
||||
if (await this.promptService.open(title, content)) {
|
||||
await this.repo.delete(item).then(null, this.raiseError);
|
||||
await this.repo.removeFromAgenda(item).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
public async deleteTopic(item: ViewItem): Promise<void> {
|
||||
if (!(item.contentObject instanceof ViewTopic)) {
|
||||
return;
|
||||
}
|
||||
const title = this.translate.instant('Are you sure you want to delete this topic?');
|
||||
const content = item.contentObject.getTitle();
|
||||
if (await this.promptService.open(title, content)) {
|
||||
await this.topicRepo.delete(item.contentObject).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
@ -231,11 +245,16 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
* Handler for deleting multiple entries. Needs items in selectedRows, which
|
||||
* is only filled with any data in multiSelect mode
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete all selected items?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
for (const agenda of this.selectedRows) {
|
||||
await this.repo.delete(agenda);
|
||||
public async removeSelected(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to remove all selected items from the agenda?');
|
||||
const content = this.translate.instant("All topics will be deleted and won't be accessible afterwards.");
|
||||
if (await this.promptService.open(title, content)) {
|
||||
for (const item of this.selectedRows) {
|
||||
if (item.contentObject instanceof ViewTopic) {
|
||||
await this.topicRepo.delete(item.contentObject);
|
||||
} else {
|
||||
await this.repo.removeFromAgenda(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -247,8 +266,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
* @param closed true if the item is to be considered done
|
||||
*/
|
||||
public async setClosedSelected(closed: boolean): Promise<void> {
|
||||
for (const agenda of this.selectedRows) {
|
||||
await this.repo.update({ closed: closed }, agenda);
|
||||
for (const item of this.selectedRows) {
|
||||
await this.repo.update({ closed: closed }, item);
|
||||
}
|
||||
}
|
||||
|
||||
@ -259,8 +278,8 @@ export class AgendaListComponent extends ListViewBaseComponent<ViewItem> impleme
|
||||
* @param visible true if the item is to be shown
|
||||
*/
|
||||
public async setAgendaType(agendaType: number): Promise<void> {
|
||||
for (const agenda of this.selectedRows) {
|
||||
await this.repo.update({ type: agendaType }, agenda).then(null, this.raiseError);
|
||||
for (const item of this.selectedRows) {
|
||||
await this.repo.update({ type: agendaType }, item).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,11 +5,11 @@ import { MatSnackBar } from '@angular/material';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { SortTreeViewComponent, SortTreeFilterOption } from 'app/site/base/sort-tree.component';
|
||||
import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
|
||||
/**
|
||||
* Sort view for the agenda.
|
||||
@ -30,7 +30,7 @@ export class AgendaSortComponent extends SortTreeViewComponent<ViewItem> impleme
|
||||
* Adds the property `state` to identify if the option is marked as active.
|
||||
* When reset the filters, the option `state` will be set to `false`.
|
||||
*/
|
||||
public filterOptions: SortTreeFilterOption[] = itemVisibilityChoices.map(item => {
|
||||
public filterOptions: SortTreeFilterOption[] = ItemVisibilityChoices.map(item => {
|
||||
return { label: item.name, id: item.key, state: false };
|
||||
});
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { FormBuilder, FormGroup } from '@angular/forms';
|
||||
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
|
||||
|
||||
import { ViewItem } from '../../models/view-item';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { DurationService } from 'app/core/ui-services/duration.service';
|
||||
|
||||
/**
|
||||
@ -23,7 +23,7 @@ export class ItemInfoDialogComponent {
|
||||
/**
|
||||
* Hold item visibility
|
||||
*/
|
||||
public itemVisibility = itemVisibilityChoices;
|
||||
public itemVisibility = ItemVisibilityChoices;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -402,7 +402,7 @@ export class ListOfSpeakersComponent extends BaseViewComponent implements OnInit
|
||||
const title = this.translate.instant(
|
||||
'Are you sure you want to delete all speakers from this list of speakers?'
|
||||
);
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
this.listOfSpeakersRepo.deleteAllSpeakers(this.viewListOfSpeakers);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ import { TopicRepositoryService } from 'app/core/repositories/topics/topic-repos
|
||||
import { ViewTopic } from '../../models/view-topic';
|
||||
import { OperatorService } from 'app/core/core-services/operator.service';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { CreateTopic } from '../../models/create-topic';
|
||||
import { Topic } from 'app/shared/models/topics/topic';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
@ -62,7 +62,7 @@ export class TopicDetailComponent extends BaseViewComponent {
|
||||
/**
|
||||
* Determine visibility states for the agenda that will be created implicitly
|
||||
*/
|
||||
public itemVisibility = itemVisibilityChoices;
|
||||
public itemVisibility = ItemVisibilityChoices;
|
||||
|
||||
/**
|
||||
* Constructor for the topic detail page.
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Item, itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { Item, ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import {
|
||||
BaseViewModelWithAgendaItem,
|
||||
isBaseViewModelWithAgendaItem
|
||||
@ -56,7 +56,7 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
|
||||
if (!this.type) {
|
||||
return '';
|
||||
}
|
||||
const type = itemVisibilityChoices.find(choice => choice.key === this.type);
|
||||
const type = ItemVisibilityChoices.find(choice => choice.key === this.type);
|
||||
return type ? type.name : '';
|
||||
}
|
||||
|
||||
@ -68,7 +68,7 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
|
||||
if (!this.type) {
|
||||
return '';
|
||||
}
|
||||
const type = itemVisibilityChoices.find(choice => choice.key === this.type);
|
||||
const type = ItemVisibilityChoices.find(choice => choice.key === this.type);
|
||||
return type ? type.csvName : '';
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@ import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
|
||||
import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ViewItem } from '../models/view-item';
|
||||
import { StorageService } from 'app/core/core-services/storage.service';
|
||||
|
||||
@ -60,7 +60,7 @@ export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
||||
* @returns a list of choices to filter from
|
||||
*/
|
||||
private createVisibilityFilterOptions(): OsFilterOption[] {
|
||||
return itemVisibilityChoices.map(choice => ({
|
||||
return ItemVisibilityChoices.map(choice => ({
|
||||
condition: choice.key as number,
|
||||
label: choice.name
|
||||
}));
|
||||
|
@ -33,6 +33,18 @@
|
||||
<!-- Project -->
|
||||
<os-projector-button [object]="assignment" [menuItem]="true"></os-projector-button>
|
||||
|
||||
<!-- Add/remove to/from agenda -->
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="assignment && !assignment.agendaItem">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add to agenda</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="assignment && assignment.agendaItem">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Delete -->
|
||||
<div *ngIf="assignment && hasPerms('manage')">
|
||||
<!-- Delete -->
|
||||
@ -263,18 +275,7 @@
|
||||
></os-search-value-selector>
|
||||
</div>
|
||||
|
||||
<!-- searchValueSelector: agendaItem -->
|
||||
<div class="content-field" *ngIf="parentsAvailable">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[form]="assignmentForm"
|
||||
[formControl]="assignmentForm.get('agenda_item_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="false"
|
||||
listname="{{ 'Parent agenda item' | translate }}"
|
||||
[InputListValues]="agendaObserver"
|
||||
></os-search-value-selector>
|
||||
</div>
|
||||
<os-agenda-content-object-form *ngIf="newAssignment" [form]="assignmentForm"></os-agenda-content-object-form>
|
||||
|
||||
<!-- poll_description_default -->
|
||||
<div>
|
||||
|
@ -161,7 +161,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
* @param repo
|
||||
* @param userRepo
|
||||
* @param pollService
|
||||
* @param agendaRepo
|
||||
* @param itemRepo
|
||||
* @param tagRepo
|
||||
* @param promptService
|
||||
*/
|
||||
@ -177,7 +177,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
public repo: AssignmentRepositoryService,
|
||||
private userRepo: UserRepositoryService,
|
||||
public pollService: AssignmentPollService,
|
||||
private agendaRepo: ItemRepositoryService,
|
||||
private itemRepo: ItemRepositoryService,
|
||||
private tagRepo: TagRepositoryService,
|
||||
private promptService: PromptService,
|
||||
private pdfService: AssignmentPdfExportService,
|
||||
@ -200,7 +200,9 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
description: '',
|
||||
poll_description_default: '',
|
||||
open_posts: 0,
|
||||
agenda_item_id: '' // create agenda item
|
||||
agenda_create: [''],
|
||||
agenda_parent_id: [],
|
||||
agenda_type: ['']
|
||||
});
|
||||
this.candidatesForm = formBuilder.group({
|
||||
userId: null
|
||||
@ -212,7 +214,7 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
*/
|
||||
public ngOnInit(): void {
|
||||
this.getAssignmentByUrl();
|
||||
this.agendaObserver = this.agendaRepo.getViewModelListBehaviorSubject();
|
||||
this.agendaObserver = this.itemRepo.getViewModelListBehaviorSubject();
|
||||
this.tagsObserver = this.tagRepo.getViewModelListBehaviorSubject();
|
||||
this.mediafilesObserver = this.mediafileRepo.getViewModelListBehaviorSubject();
|
||||
}
|
||||
@ -292,7 +294,6 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
title: assignment.title || '',
|
||||
tags_id: assignment.assignment.tags_id || [],
|
||||
attachments_id: assignment.assignment.attachments_id || [],
|
||||
agendaItem: assignment.assignment.agenda_item_id || null,
|
||||
phase: assignment.phase, // todo default: 0?
|
||||
description: assignment.assignment.description || '',
|
||||
poll_description_default: assignment.assignment.poll_description_default,
|
||||
@ -516,4 +517,12 @@ export class AssignmentDetailComponent extends BaseViewComponent implements OnIn
|
||||
public getSanitizedText(text: string): SafeHtml {
|
||||
return this.sanitizer.bypassSecurityTrustHtml(text);
|
||||
}
|
||||
|
||||
public addToAgenda(): void {
|
||||
this.itemRepo.addItemToAgenda(this.assignment).then(null, this.raiseError);
|
||||
}
|
||||
|
||||
public removeFromAgenda(): void {
|
||||
this.itemRepo.removeFromAgenda(this.assignment.agendaItem).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -114,7 +114,7 @@ export class AssignmentListComponent extends ListViewBaseComponent<ViewAssignmen
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete all selected elections?');
|
||||
if (await this.promptService.open(title, '')) {
|
||||
if (await this.promptService.open(title)) {
|
||||
for (const assignment of this.selectedRows) {
|
||||
await this.repo.delete(assignment);
|
||||
}
|
||||
|
@ -157,7 +157,7 @@ export class AssignmentPollComponent extends BaseViewComponent implements OnInit
|
||||
*/
|
||||
public async onDeletePoll(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this ballot?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
await this.assignmentRepo.deletePoll(this.poll).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -99,10 +99,10 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<os-speaker-button [object]="file" [menuItem]="true"></os-speaker-button>
|
||||
|
||||
<!-- Edit and delete for all images -->
|
||||
<mat-divider></mat-divider>
|
||||
|
||||
<os-speaker-button [object]="file" [menuItem]="true"></os-speaker-button>
|
||||
<button mat-menu-item (click)="onEditFile(file)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span translate>Edit</span>
|
||||
|
@ -218,7 +218,7 @@ export class MediafileListComponent extends ListViewBaseComponent<ViewMediafile>
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete all selected files?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
for (const mediafile of this.selectedRows) {
|
||||
await this.repo.delete(mediafile);
|
||||
}
|
||||
|
@ -185,7 +185,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
return this.motion.workflow_id;
|
||||
}
|
||||
|
||||
public get state(): WorkflowState {
|
||||
public get state(): WorkflowState | null {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
@ -349,7 +349,7 @@ export class ViewMotion extends BaseViewModelWithAgendaItemAndListOfSpeakers<Mot
|
||||
* @returns a string representing a color
|
||||
*/
|
||||
public get stateCssColor(): string {
|
||||
return StateCssClassMapping[this.state.css_class] || '';
|
||||
return this.state ? StateCssClassMapping[this.state.css_class] : '';
|
||||
}
|
||||
|
||||
// This is set by the repository
|
||||
|
@ -157,7 +157,7 @@ export class CategoryMotionsSortComponent extends BaseViewComponent implements O
|
||||
*/
|
||||
public async sendUpdate(): Promise<void> {
|
||||
const title = this.translate.instant('Do you really want to save your changes?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
const ids = this.motionsCopy.map(motion => motion.id);
|
||||
this.repo.sortMotionsInCategory(this.category.category, ids);
|
||||
this.hasChanged = false;
|
||||
|
@ -81,6 +81,17 @@
|
||||
|
||||
<os-projector-button *ngIf="block" [object]="block" [menuItem]="true"></os-projector-button>
|
||||
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="block && !block.agendaItem">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add to agenda</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="block && block.agendaItem">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
||||
<button mat-menu-item (click)="toggleEditMode()">
|
||||
<mat-icon>edit</mat-icon>
|
||||
|
@ -14,6 +14,7 @@ import { PromptService } from 'app/core/ui-services/prompt.service';
|
||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||
import { BaseViewComponent } from 'app/site/base/base-view';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
|
||||
/**
|
||||
* Detail component to display one motion block
|
||||
@ -105,7 +106,8 @@ export class MotionBlockDetailComponent extends BaseViewComponent implements OnI
|
||||
protected motionRepo: MotionRepositoryService,
|
||||
private promptService: PromptService,
|
||||
private fb: FormBuilder,
|
||||
private dialog: MatDialog
|
||||
private dialog: MatDialog,
|
||||
private itemRepo: ItemRepositoryService
|
||||
) {
|
||||
super(titleService, translate, matSnackBar);
|
||||
}
|
||||
@ -248,4 +250,12 @@ export class MotionBlockDetailComponent extends BaseViewComponent implements OnI
|
||||
public getStateLabel(motion: ViewMotion): string {
|
||||
return this.motionRepo.getExtendedStateLabel(motion);
|
||||
}
|
||||
|
||||
public addToAgenda(): void {
|
||||
this.itemRepo.addItemToAgenda(this.block).then(null, this.raiseError);
|
||||
}
|
||||
|
||||
public removeFromAgenda(): void {
|
||||
this.itemRepo.removeFromAgenda(this.block.agendaItem).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -23,27 +23,7 @@
|
||||
<mat-checkbox formControlName="internal"><span translate>Internal</span></mat-checkbox>
|
||||
</p>
|
||||
|
||||
<!-- Parent item -->
|
||||
<p>
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
listname="{{ 'Parent agenda item' | translate }}"
|
||||
[form]="createBlockForm"
|
||||
[formControl]="createBlockForm.get('agenda_parent_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
[InputListValues]="items"
|
||||
></os-search-value-selector>
|
||||
</p>
|
||||
|
||||
<!-- Visibility -->
|
||||
<mat-form-field>
|
||||
<mat-select formControlName="agenda_type" placeholder="{{ 'Agenda visibility' | translate }}">
|
||||
<mat-option *ngFor="let type of itemVisibility" [value]="type.key">
|
||||
<span>{{ type.name | translate }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<os-agenda-content-object-form [form]="createBlockForm"></os-agenda-content-object-form>
|
||||
</form>
|
||||
</mat-card-content>
|
||||
|
||||
|
@ -8,7 +8,6 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { PblColumnDefinition } from '@pebula/ngrid';
|
||||
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { ListViewBaseComponent } from 'app/site/base/list-view-base';
|
||||
import { MotionBlock } from 'app/shared/models/motions/motion-block';
|
||||
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
||||
@ -47,11 +46,6 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
||||
*/
|
||||
public defaultVisibility: number;
|
||||
|
||||
/**
|
||||
* Determine visibility states for the agenda that will be created implicitly
|
||||
*/
|
||||
public itemVisibility = itemVisibilityChoices;
|
||||
|
||||
/**
|
||||
* helper for permission checks
|
||||
*
|
||||
@ -82,23 +76,21 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
||||
* @param titleService sets the title
|
||||
* @param translate translpations
|
||||
* @param matSnackBar display errors in the snack bar
|
||||
* @param router routing to children
|
||||
* @param route determine the local route
|
||||
* @param storage
|
||||
* @param repo the motion block repository
|
||||
* @param agendaRepo the agenda repository service
|
||||
* @param DS the dataStore
|
||||
* @param formBuilder creates forms
|
||||
* @param promptService the delete prompt
|
||||
* @param itemRepo
|
||||
* @param operator permission checks
|
||||
* @param sortService
|
||||
*/
|
||||
public constructor(
|
||||
titleService: Title,
|
||||
translate: TranslateService,
|
||||
matSnackBar: MatSnackBar,
|
||||
storage: StorageService,
|
||||
public repo: MotionBlockRepositoryService,
|
||||
private agendaRepo: ItemRepositoryService,
|
||||
private repo: MotionBlockRepositoryService,
|
||||
private formBuilder: FormBuilder,
|
||||
private itemRepo: ItemRepositoryService,
|
||||
private operator: OperatorService,
|
||||
@ -108,8 +100,9 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
||||
|
||||
this.createBlockForm = this.formBuilder.group({
|
||||
title: ['', Validators.required],
|
||||
agenda_type: ['', Validators.required],
|
||||
agenda_create: [''],
|
||||
agenda_parent_id: [],
|
||||
agenda_type: [''],
|
||||
internal: [false]
|
||||
});
|
||||
}
|
||||
@ -120,7 +113,6 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
||||
public ngOnInit(): void {
|
||||
super.setTitle('Motion blocks');
|
||||
this.items = this.itemRepo.getViewModelListBehaviorSubject();
|
||||
this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => (this.defaultVisibility = visibility));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -155,7 +147,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
||||
* Click handler for the save button.
|
||||
* Sends the block to create to the repository and resets the form.
|
||||
*/
|
||||
public onSaveNewButton(): void {
|
||||
public async onSaveNewButton(): Promise<void> {
|
||||
if (this.createBlockForm.valid) {
|
||||
const block = this.createBlockForm.value;
|
||||
if (!block.agenda_parent_id) {
|
||||
@ -163,7 +155,7 @@ export class MotionBlockListComponent extends ListViewBaseComponent<ViewMotionBl
|
||||
}
|
||||
|
||||
try {
|
||||
this.repo.create(block);
|
||||
await this.repo.create(block);
|
||||
this.resetForm();
|
||||
this.isCreatingNewBlock = false;
|
||||
} catch (e) {
|
||||
|
@ -269,7 +269,7 @@ export class MotionDetailDiffComponent extends BaseViewComponent implements Afte
|
||||
$event.stopPropagation();
|
||||
$event.preventDefault();
|
||||
const title = this.translate.instant('Are you sure you want to delete this change recommendation?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
this.recoRepo.delete(reco).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -75,6 +75,17 @@
|
||||
[menuItem]="true"
|
||||
*osPerms="'core.can_manage_projector'"
|
||||
></os-projector-button>
|
||||
<!-- Add/remove to/from agenda -->
|
||||
<div *osPerms="'agenda.can_manage'">
|
||||
<button mat-menu-item (click)="addToAgenda()" *ngIf="motion && !motion.agendaItem">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span translate>Add to agenda</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="removeFromAgenda()" *ngIf="motion && motion.agendaItem">
|
||||
<mat-icon>remove</mat-icon>
|
||||
<span translate>Remove from agenda</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- New amendment -->
|
||||
<button mat-menu-item (click)="createAmendment()" *ngIf="perms.isAllowed('can_create_amendments', motion)">
|
||||
<mat-icon>add</mat-icon>
|
||||
@ -89,6 +100,7 @@
|
||||
<mat-icon>{{ !showAmendmentContext ? 'check_box_outline_blank' : 'check_box' }}</mat-icon>
|
||||
<span translate>Show entire motion text</span>
|
||||
</button>
|
||||
|
||||
<div *ngIf="perms.isAllowed('manage')">
|
||||
<mat-divider></mat-divider>
|
||||
<!-- Delete -->
|
||||
@ -791,28 +803,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Visibility -->
|
||||
<div class="content-field" *ngIf="newMotion">
|
||||
<mat-form-field>
|
||||
<mat-select formControlName="agenda_type" placeholder="{{ 'Agenda visibility' | translate }}">
|
||||
<mat-option *ngFor="let type of itemVisibility" [value]="type.key">
|
||||
<span>{{ type.name | translate }}</span>
|
||||
</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
</div>
|
||||
|
||||
<!-- Parent item -->
|
||||
<div class="content-field" *ngIf="newMotion && agendaItemObserver.value.length > 0">
|
||||
<os-search-value-selector
|
||||
ngDefaultControl
|
||||
[form]="contentForm"
|
||||
[formControl]="contentForm.get('agenda_parent_id')"
|
||||
[multiple]="false"
|
||||
[includeNone]="true"
|
||||
listname="{{ 'Parent agenda item' | translate }}"
|
||||
[InputListValues]="agendaItemObserver"
|
||||
></os-search-value-selector>
|
||||
<div *ngIf="newMotion">
|
||||
<os-agenda-content-object-form [form]="contentForm"></os-agenda-content-object-form>
|
||||
</div>
|
||||
|
||||
<!-- Supporter form -->
|
||||
|
@ -13,8 +13,6 @@ import { ChangeRecommendationRepositoryService } from 'app/core/repositories/mot
|
||||
import { CreateMotion } from 'app/site/motions/models/create-motion';
|
||||
import { ConfigService } from 'app/core/ui-services/config.service';
|
||||
import { DiffLinesInParagraph, LineRange } from 'app/core/ui-services/diff.service';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
import { itemVisibilityChoices } from 'app/shared/models/agenda/item';
|
||||
import { LinenumberingService } from 'app/core/ui-services/linenumbering.service';
|
||||
import { LocalPermissionsService } from 'app/site/motions/services/local-permissions.service';
|
||||
import { Mediafile } from 'app/shared/models/mediafiles/mediafile';
|
||||
@ -37,7 +35,6 @@ import { ViewWorkflow } from 'app/site/motions/models/view-workflow';
|
||||
import { ViewUser } from 'app/site/users/models/view-user';
|
||||
import { ViewCategory } from 'app/site/motions/models/view-category';
|
||||
import { ViewCreateMotion } from 'app/site/motions/models/view-create-motion';
|
||||
import { ViewItem } from 'app/site/agenda/models/view-item';
|
||||
import { ViewportService } from 'app/core/ui-services/viewport.service';
|
||||
import { ViewMediafile } from 'app/site/mediafiles/models/view-mediafile';
|
||||
import { ViewMotionChangeRecommendation } from 'app/site/motions/models/view-motion-change-recommendation';
|
||||
@ -58,6 +55,7 @@ import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.
|
||||
import { WorkflowRepositoryService } from 'app/core/repositories/motions/workflow-repository.service';
|
||||
import { MotionBlockRepositoryService } from 'app/core/repositories/motions/motion-block-repository.service';
|
||||
import { MotionSortListService } from 'app/site/motions/services/motion-sort-list.service';
|
||||
import { ItemRepositoryService } from 'app/core/repositories/agenda/item-repository.service';
|
||||
|
||||
/**
|
||||
* Component for the motion detail view
|
||||
@ -254,11 +252,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
public mediafilesObserver: BehaviorSubject<ViewMediafile[]>;
|
||||
|
||||
/**
|
||||
* Subject for agenda items
|
||||
*/
|
||||
public agendaItemObserver: BehaviorSubject<ViewItem[]>;
|
||||
|
||||
/**
|
||||
* Subject for tags
|
||||
*/
|
||||
@ -294,16 +287,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
public showAmendmentContext = false;
|
||||
|
||||
/**
|
||||
* Determines the default agenda item visibility
|
||||
*/
|
||||
public defaultVisibility: number;
|
||||
|
||||
/**
|
||||
* Determine visibility states for the agenda that will be created implicitly
|
||||
*/
|
||||
public itemVisibility = itemVisibilityChoices;
|
||||
|
||||
/**
|
||||
* For using the enum constants from the template
|
||||
*/
|
||||
@ -395,7 +378,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
* @param dialogService For opening dialogs
|
||||
* @param el The native element
|
||||
* @param repo Motion Repository
|
||||
* @param agendaRepo Read out agenda variables
|
||||
* @param changeRecoRepo Change Recommendation Repository
|
||||
* @param statuteRepo: Statute Paragraph Repository
|
||||
* @param mediafileRepo Mediafile Repository
|
||||
@ -431,7 +413,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
private dialogService: MatDialog,
|
||||
private el: ElementRef,
|
||||
public repo: MotionRepositoryService,
|
||||
private agendaRepo: ItemRepositoryService,
|
||||
private changeRecoRepo: ChangeRecommendationRepositoryService,
|
||||
private statuteRepo: StatuteParagraphRepositoryService,
|
||||
private configService: ConfigService,
|
||||
@ -447,8 +428,8 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
private mediaFilerepo: MediafileRepositoryService,
|
||||
private workflowRepo: WorkflowRepositoryService,
|
||||
private blockRepo: MotionBlockRepositoryService,
|
||||
private itemRepo: ItemRepositoryService,
|
||||
private motionSortService: MotionSortListService
|
||||
private motionSortService: MotionSortListService,
|
||||
private itemRepo: ItemRepositoryService
|
||||
) {
|
||||
super(title, translate, matSnackBar);
|
||||
}
|
||||
@ -463,7 +444,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
this.mediafilesObserver = this.mediaFilerepo.getViewModelListBehaviorSubject();
|
||||
this.workflowObserver = this.workflowRepo.getViewModelListBehaviorSubject();
|
||||
this.blockObserver = this.blockRepo.getViewModelListBehaviorSubject();
|
||||
this.agendaItemObserver = this.itemRepo.getViewModelListBehaviorSubject();
|
||||
this.motionObserver = this.repo.getViewModelListBehaviorSubject();
|
||||
this.submitterObserver = this.userRepo.getViewModelListBehaviorSubject();
|
||||
this.supporterObserver = this.userRepo.getViewModelListBehaviorSubject();
|
||||
@ -513,13 +493,6 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
}
|
||||
});
|
||||
|
||||
// Set the default visibility using observers
|
||||
this.agendaRepo.getDefaultAgendaVisibility().subscribe(visibility => {
|
||||
if (visibility && this.newMotion) {
|
||||
this.contentForm.get('agenda_type').setValue(visibility);
|
||||
}
|
||||
});
|
||||
|
||||
// Update statute paragraphs
|
||||
this.statuteRepo.getViewModelListObservable().subscribe(newViewStatuteParagraphs => {
|
||||
this.statuteParagraphs = newViewStatuteParagraphs;
|
||||
@ -724,6 +697,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
reason: reason,
|
||||
category_id: [''],
|
||||
attachments_id: [[]],
|
||||
agenda_create: [''],
|
||||
agenda_parent_id: [],
|
||||
agenda_type: [''],
|
||||
submitters_id: [],
|
||||
@ -1092,7 +1066,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
const title = this.translate.instant(
|
||||
'Are you sure you want to copy the final version to the print template?'
|
||||
);
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
this.updateMotion({ modified_final_version: finalVersion }, this.motion).then(
|
||||
() => this.setChangeRecoMode(ChangeRecoMode.ModifiedFinal),
|
||||
this.raiseError
|
||||
@ -1111,7 +1085,7 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
*/
|
||||
public async deleteModifiedFinalVersion(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete the print template?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
this.finalEditMode = false;
|
||||
this.updateMotion({ modified_final_version: '' }, this.motion).then(
|
||||
() => this.setChangeRecoMode(ChangeRecoMode.Final),
|
||||
@ -1610,4 +1584,12 @@ export class MotionDetailComponent extends BaseViewComponent implements OnInit,
|
||||
public editModifiedFinal(): void {
|
||||
this.finalEditMode = true;
|
||||
}
|
||||
|
||||
public addToAgenda(): void {
|
||||
this.itemRepo.addItemToAgenda(this.motion).then(null, this.raiseError);
|
||||
}
|
||||
|
||||
public removeFromAgenda(): void {
|
||||
this.itemRepo.removeFromAgenda(this.motion.agendaItem).then(null, this.raiseError);
|
||||
}
|
||||
}
|
||||
|
@ -119,7 +119,7 @@ export class MotionPollComponent implements OnInit {
|
||||
*/
|
||||
public async deletePoll(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete this vote?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
this.motionRepo.deletePoll(this.poll);
|
||||
}
|
||||
}
|
||||
|
@ -71,7 +71,7 @@ export class MotionMultiselectService {
|
||||
*/
|
||||
public async delete(motions: ViewMotion[]): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete all selected motions?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
let i = 0;
|
||||
|
||||
for (const motion of motions) {
|
||||
|
@ -280,7 +280,7 @@ export class UserListComponent extends ListViewBaseComponent<ViewUser> implement
|
||||
*/
|
||||
public async deleteSelected(): Promise<void> {
|
||||
const title = this.translate.instant('Are you sure you want to delete all selected participants?');
|
||||
if (await this.promptService.open(title, null)) {
|
||||
if (await this.promptService.open(title)) {
|
||||
for (const user of this.selectedRows) {
|
||||
await this.repo.delete(user);
|
||||
}
|
||||
|
@ -30,6 +30,22 @@ def get_config_variables():
|
||||
validators=(MaxLengthValidator(20),),
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="agenda_item_creation",
|
||||
label="Auto add to agenda",
|
||||
default_value="always",
|
||||
input_type="choice",
|
||||
choices=(
|
||||
{"value": "always", "display_name": "Always"},
|
||||
{"value": "never", "display_name": "Never"},
|
||||
{"value": "default_yes", "display_name": "Ask, default yes"},
|
||||
{"value": "default_no", "display_name": "Ask, default no"},
|
||||
),
|
||||
weight=212,
|
||||
group="Agenda",
|
||||
subgroup="General",
|
||||
)
|
||||
|
||||
yield ConfigVariable(
|
||||
name="agenda_numeral_system",
|
||||
default_value="arabic",
|
||||
|
@ -23,6 +23,9 @@ class AgendaItemMixin(models.Model):
|
||||
|
||||
"""
|
||||
Container for runtime information for agenda app (on create or update of this instance).
|
||||
Can be an attribute of an item, e.g. "type", "parent_id", "comment", "duration", "weight",
|
||||
or "create", which determinates, if the items should be created. If not given, the
|
||||
config value is used.
|
||||
"""
|
||||
agenda_item_update_information: Dict[str, Any] = {}
|
||||
|
||||
@ -31,17 +34,20 @@ class AgendaItemMixin(models.Model):
|
||||
@property
|
||||
def agenda_item(self):
|
||||
"""
|
||||
Returns the related agenda item.
|
||||
Returns the related agenda item, if it exists.
|
||||
"""
|
||||
# We support only one agenda item so just return the first element of
|
||||
# the queryset.
|
||||
return self.agenda_items.all()[0]
|
||||
try:
|
||||
return self.agenda_items.all()[0]
|
||||
except IndexError:
|
||||
return None
|
||||
|
||||
@property
|
||||
def agenda_item_id(self):
|
||||
"""
|
||||
Returns the id of the agenda item object related to this object.
|
||||
"""
|
||||
if self.agenda_item is None:
|
||||
return None
|
||||
return self.agenda_item.pk
|
||||
|
||||
def get_agenda_title_information(self):
|
||||
|
@ -4,7 +4,9 @@ from django.apps import apps
|
||||
from django.contrib.contenttypes.models import ContentType
|
||||
from django.db import models
|
||||
|
||||
from ..core.config import config
|
||||
from ..utils.autoupdate import inform_changed_data
|
||||
from ..utils.rest_api import ValidationError
|
||||
from .models import Item, ListOfSpeakers
|
||||
|
||||
|
||||
@ -33,16 +35,51 @@ def listen_to_related_object_post_save(sender, instance, created, **kwargs):
|
||||
|
||||
if is_agenda_item_content_object:
|
||||
if created:
|
||||
attrs = {}
|
||||
for attr in ("type", "parent_id", "comment", "duration", "weight"):
|
||||
if instance.agenda_item_update_information.get(attr):
|
||||
attrs[attr] = instance.agenda_item_update_information.get(attr)
|
||||
Item.objects.create(content_object=instance, **attrs)
|
||||
|
||||
if not instance.agenda_item_skip_autoupdate:
|
||||
instance_inform_changed_data = True
|
||||
if instance.get_collection_string() == "topics/topic":
|
||||
should_create_item = True
|
||||
elif config["agenda_item_creation"] == "always":
|
||||
should_create_item = True
|
||||
elif config["agenda_item_creation"] == "never":
|
||||
should_create_item = False
|
||||
else:
|
||||
should_create_item = instance.agenda_item_update_information.get(
|
||||
"create"
|
||||
)
|
||||
if should_create_item is None:
|
||||
should_create_item = config["agenda_item_creation"] == "default_yes"
|
||||
|
||||
elif not instance.agenda_item_skip_autoupdate:
|
||||
if should_create_item:
|
||||
attrs = {}
|
||||
for attr in ("type", "parent_id", "comment", "duration", "weight"):
|
||||
if instance.agenda_item_update_information.get(attr):
|
||||
attrs[attr] = instance.agenda_item_update_information.get(attr)
|
||||
# Validation: The type is validated in the serializers (to be between 1 and 3).
|
||||
# If the parent id is given, set the weight to the parent's weight +1 to
|
||||
# ensure the right placement in the tree. Also validate the parent_id!
|
||||
parent_id = attrs.get("parent_id")
|
||||
if parent_id is not None:
|
||||
try:
|
||||
parent = Item.objects.get(pk=parent_id)
|
||||
except Item.DoesNotExist:
|
||||
raise ValidationError(
|
||||
{
|
||||
"detail": f"The parent item with id {parent_id} does not exist"
|
||||
}
|
||||
)
|
||||
attrs["weight"] = parent.weight + 1
|
||||
Item.objects.create(content_object=instance, **attrs)
|
||||
|
||||
if not instance.agenda_item_skip_autoupdate:
|
||||
instance_inform_changed_data = True
|
||||
else:
|
||||
is_agenda_item_content_object = False
|
||||
# important for the check for item and list of speakers together.
|
||||
|
||||
elif (
|
||||
not instance.agenda_item_skip_autoupdate
|
||||
and instance.agenda_item is not None
|
||||
):
|
||||
# If the object has changed, then also the agenda item has to be sent.
|
||||
inform_changed_data(instance.agenda_item)
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import jsonschema
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.db import transaction
|
||||
from django.db.utils import IntegrityError
|
||||
|
||||
from openslides.core.config import config
|
||||
from openslides.utils.autoupdate import inform_changed_data
|
||||
@ -15,10 +16,12 @@ from openslides.utils.rest_api import (
|
||||
ValidationError,
|
||||
detail_route,
|
||||
list_route,
|
||||
status,
|
||||
)
|
||||
from openslides.utils.views import TreeSortMixin
|
||||
|
||||
from ..utils.auth import has_perm
|
||||
from ..utils.utils import get_model_from_collection_string
|
||||
from .access_permissions import ItemAccessPermissions
|
||||
from .models import Item, ListOfSpeakers, Speaker
|
||||
|
||||
@ -42,7 +45,14 @@ class ItemViewSet(ModelViewSet, TreeSortMixin):
|
||||
"""
|
||||
if self.action in ("list", "retrieve", "metadata"):
|
||||
result = self.get_access_permissions().check_permissions(self.request.user)
|
||||
elif self.action in ("partial_update", "update", "destroy", "sort", "assign"):
|
||||
elif self.action in (
|
||||
"partial_update",
|
||||
"update",
|
||||
"destroy",
|
||||
"sort",
|
||||
"assign",
|
||||
"create",
|
||||
):
|
||||
result = (
|
||||
has_perm(self.request.user, "agenda.can_see")
|
||||
and has_perm(self.request.user, "agenda.can_see_internal_items")
|
||||
@ -56,6 +66,62 @@ class ItemViewSet(ModelViewSet, TreeSortMixin):
|
||||
result = False
|
||||
return result
|
||||
|
||||
def create(self, request, *args, **kwargs):
|
||||
"""
|
||||
Creates an agenda item and adds the content object to the agenda.
|
||||
Request args should specify the content object:
|
||||
{
|
||||
"collection": <The collection string>,
|
||||
"id": <The content object id>
|
||||
}
|
||||
"""
|
||||
collection = request.data.get("collection")
|
||||
id = request.data.get("id")
|
||||
|
||||
if not isinstance(collection, str):
|
||||
raise ValidationError({"detail": "The collection needs to be a string"})
|
||||
if not isinstance(id, int):
|
||||
raise ValidationError({"detail": "The id needs to be an int"})
|
||||
|
||||
try:
|
||||
model = get_model_from_collection_string(collection)
|
||||
except ValueError:
|
||||
raise ValidationError("Invalid collection")
|
||||
|
||||
try:
|
||||
content_object = model.objects.get(pk=id)
|
||||
except model.DoesNotExist:
|
||||
raise ValidationError({"detail": "The id is invalid"})
|
||||
|
||||
if not hasattr(content_object, "get_agenda_title_information"):
|
||||
raise ValidationError(
|
||||
{"detail": "The collection does not have agenda items"}
|
||||
)
|
||||
|
||||
try:
|
||||
item = Item.objects.create(content_object=content_object)
|
||||
except IntegrityError:
|
||||
raise ValidationError({"detail": "The item is already in the agenda"})
|
||||
|
||||
inform_changed_data(content_object)
|
||||
return Response({id: item.id})
|
||||
|
||||
def destroy(self, request, *args, **kwargs):
|
||||
"""
|
||||
Removes the item from the agenda. This does not delete the content
|
||||
object. Also, the deletion is denied for items with topics as content objects.
|
||||
"""
|
||||
item = self.get_object()
|
||||
content_object = item.content_object
|
||||
if content_object.get_collection_string() == "topics/topic":
|
||||
raise ValidationError(
|
||||
{"detail": "You cannot delete the agenda item to a topic"}
|
||||
)
|
||||
|
||||
item.delete()
|
||||
inform_changed_data(content_object)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
def update(self, *args, **kwargs):
|
||||
"""
|
||||
Customized view endpoint to update all children if the item type has changed.
|
||||
|
@ -8,6 +8,7 @@ from ..poll.serializers import default_votes_validator
|
||||
from ..utils.auth import get_group_model
|
||||
from ..utils.autoupdate import inform_changed_data
|
||||
from ..utils.rest_api import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
DecimalField,
|
||||
DictField,
|
||||
@ -69,6 +70,7 @@ class MotionBlockSerializer(ModelSerializer):
|
||||
Serializer for motion.models.Category objects.
|
||||
"""
|
||||
|
||||
agenda_create = BooleanField(write_only=True, required=False, allow_null=True)
|
||||
agenda_type = IntegerField(
|
||||
write_only=True, required=False, min_value=1, max_value=3, allow_null=True
|
||||
)
|
||||
@ -81,6 +83,7 @@ class MotionBlockSerializer(ModelSerializer):
|
||||
"title",
|
||||
"agenda_item_id",
|
||||
"list_of_speakers_id",
|
||||
"agenda_create",
|
||||
"agenda_type",
|
||||
"agenda_parent_id",
|
||||
"internal",
|
||||
@ -91,9 +94,11 @@ class MotionBlockSerializer(ModelSerializer):
|
||||
Customized create method. Set information about related agenda item
|
||||
into agenda_item_update_information container.
|
||||
"""
|
||||
agenda_create = validated_data.pop("agenda_create", None)
|
||||
agenda_type = validated_data.pop("agenda_type", None)
|
||||
agenda_parent_id = validated_data.pop("agenda_parent_id", None)
|
||||
motion_block = MotionBlock(**validated_data)
|
||||
motion_block.agenda_item_update_information["create"] = agenda_create
|
||||
motion_block.agenda_item_update_information["type"] = agenda_type
|
||||
motion_block.agenda_item_update_information["parent_id"] = agenda_parent_id
|
||||
motion_block.save()
|
||||
@ -417,6 +422,7 @@ class MotionSerializer(ModelSerializer):
|
||||
workflow_id = IntegerField(
|
||||
min_value=1, required=False, validators=[validate_workflow_field]
|
||||
)
|
||||
agenda_create = BooleanField(write_only=True, required=False, allow_null=True)
|
||||
agenda_type = IntegerField(
|
||||
write_only=True, required=False, min_value=1, max_value=3, allow_null=True
|
||||
)
|
||||
@ -456,6 +462,7 @@ class MotionSerializer(ModelSerializer):
|
||||
"polls",
|
||||
"agenda_item_id",
|
||||
"list_of_speakers_id",
|
||||
"agenda_create",
|
||||
"agenda_type",
|
||||
"agenda_parent_id",
|
||||
"sort_parent",
|
||||
@ -528,6 +535,9 @@ class MotionSerializer(ModelSerializer):
|
||||
motion.parent = validated_data.get("parent")
|
||||
motion.statute_paragraph = validated_data.get("statute_paragraph")
|
||||
motion.reset_state(validated_data.get("workflow_id"))
|
||||
motion.agenda_item_update_information["create"] = validated_data.get(
|
||||
"agenda_create"
|
||||
)
|
||||
motion.agenda_item_update_information["type"] = validated_data.get(
|
||||
"agenda_type"
|
||||
)
|
||||
|
@ -19,6 +19,7 @@ from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.routers import DefaultRouter
|
||||
from rest_framework.serializers import (
|
||||
BooleanField,
|
||||
CharField,
|
||||
DecimalField,
|
||||
DictField,
|
||||
@ -55,6 +56,7 @@ __all__ = [
|
||||
"DestroyModelMixin",
|
||||
"CharField",
|
||||
"DictField",
|
||||
"BooleanField",
|
||||
"FileField",
|
||||
"IntegerField",
|
||||
"JSONField",
|
||||
|
@ -77,14 +77,20 @@ class TreeSortMixin:
|
||||
# layer) and a weight.
|
||||
nodes_to_check = [fake_root]
|
||||
# Traverse and check, if every id is given, valid and there are no duplicate ids.
|
||||
weight = 1
|
||||
|
||||
# The weight values are 2, 4, 6, 8,... to "make space" between entries. This is
|
||||
# some work around for the agenda: If one creates a content object with an item
|
||||
# and gives the item's parent, than the weight can be set to the parent's one +1.
|
||||
# If multiple content objects witht he same parent are created, the ordering is not
|
||||
# guaranteed.
|
||||
weight = 2
|
||||
while len(nodes_to_check) > 0:
|
||||
node = nodes_to_check.pop()
|
||||
id = node["id"]
|
||||
|
||||
if id is not None: # exclude the fake_root
|
||||
node[weight_key] = weight
|
||||
weight += 1
|
||||
weight += 2
|
||||
if id in ids_found:
|
||||
raise ValidationError(f"Duplicate id: {id}")
|
||||
if id not in all_model_ids:
|
||||
|
@ -27,6 +27,8 @@ class ContentObjects(TestCase):
|
||||
lists of speakers. Asserts, that it is recognizes as a content
|
||||
object and tests creation and deletion of it and the related item
|
||||
and list of speaker.
|
||||
Tests optional agenda items with motions, e.g. motion as a content
|
||||
object without an item.
|
||||
"""
|
||||
|
||||
def setUp(self):
|
||||
@ -39,7 +41,15 @@ class ContentObjects(TestCase):
|
||||
def test_topic_is_list_of_speakers_content_object(self):
|
||||
assert hasattr(Topic(), "get_list_of_speakers_title_information")
|
||||
|
||||
def test_create_content_object(self):
|
||||
def test_motion_is_agenda_item_content_object(self):
|
||||
assert hasattr(Motion(), "get_agenda_title_information")
|
||||
|
||||
def test_motion_is_list_of_speakers_content_object(self):
|
||||
assert hasattr(Motion(), "get_list_of_speakers_title_information")
|
||||
|
||||
def test_create_topic(self):
|
||||
# Disable autocreation. Topics should create agenda items anyways.
|
||||
config["agenda_item_creation"] = "never"
|
||||
topic = Topic.objects.create(title="test_title_fk3Oc209JDiunw2!wwoH")
|
||||
|
||||
assert topic.agenda_item is not None
|
||||
@ -51,7 +61,7 @@ class ContentObjects(TestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||
|
||||
def test_delete_content_object(self):
|
||||
def test_delete_topic(self):
|
||||
topic = Topic.objects.create(title="test_title_lwOCK32jZGFb37DpmoP(")
|
||||
item_id = topic.agenda_item_id
|
||||
list_of_speakers_id = topic.list_of_speakers_id
|
||||
@ -63,6 +73,65 @@ class ContentObjects(TestCase):
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||
|
||||
def test_prevent_removing_topic_from_agenda(self):
|
||||
topic = Topic.objects.create(title="test_title_lwOCK32jZGFb37DpmoP(")
|
||||
item_id = topic.agenda_item_id
|
||||
response = self.client.delete(reverse("item-detail", args=[item_id]))
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_adding_topic_twice(self):
|
||||
topic = Topic.objects.create(title="test_title_lwOCK32jZGFb37DpmoP(")
|
||||
response = self.client.post(
|
||||
reverse("item-list"),
|
||||
{"collection": topic.get_collection_string(), "id": topic.id},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||
|
||||
def test_enabled_auto_adding_item_for_motion(self):
|
||||
config["agenda_item_creation"] = "always"
|
||||
response = self.client.post(
|
||||
reverse("motion-list"),
|
||||
{
|
||||
"title": "test_title_F3pApc3em9zIGCie2iwf",
|
||||
"text": "test_text_wcnLVzezeLcnqlqlC(31",
|
||||
"agenda_create": False,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
motion = Motion.objects.get()
|
||||
self.assertTrue(motion.agenda_item is not None)
|
||||
self.assertEqual(motion.agenda_item_id, motion.agenda_item.id)
|
||||
|
||||
def test_disabled_auto_adding_item_for_motion(self):
|
||||
config["agenda_item_creation"] = "never"
|
||||
response = self.client.post(
|
||||
reverse("motion-list"),
|
||||
{
|
||||
"title": "test_title_OoCoo3MeiT9li5Iengu9",
|
||||
"text": "test_text_thuoz0iecheiheereiCi",
|
||||
"agenda_create": True,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
motion = Motion.objects.get()
|
||||
self.assertTrue(motion.agenda_item is None)
|
||||
self.assertTrue(motion.agenda_item_id is None)
|
||||
|
||||
def test_ask_auto_adding_item_for_motion(self):
|
||||
config["agenda_item_creation"] = "default_no"
|
||||
response = self.client.post(
|
||||
reverse("motion-list"),
|
||||
{
|
||||
"title": "test_title_wvlvowievgbpypoOV332",
|
||||
"text": "test_text_tvewpxxcw9r72qNVV3uq",
|
||||
"agenda_create": True,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||
motion = Motion.objects.get()
|
||||
self.assertTrue(motion.agenda_item is not None)
|
||||
self.assertEqual(motion.agenda_item_id, motion.agenda_item.id)
|
||||
|
||||
|
||||
class RetrieveItem(TestCase):
|
||||
"""
|
||||
|
Loading…
Reference in New Issue
Block a user