Internal flag for motion blocks
- ServerSide - Adds the 'internal'-flag to the edit view of motion blocks
This commit is contained in:
parent
a3b5f083d5
commit
658b1a360d
@ -342,7 +342,7 @@ export class OperatorService implements OnAfterAppsLoaded {
|
|||||||
public isInGroupIds(...groupIds: number[]): boolean {
|
public isInGroupIds(...groupIds: number[]): boolean {
|
||||||
if (!this.isInGroupIdsNonAdminCheck(...groupIds)) {
|
if (!this.isInGroupIdsNonAdminCheck(...groupIds)) {
|
||||||
// An admin has all perms and is technically in every group.
|
// An admin has all perms and is technically in every group.
|
||||||
return this.user.groups_id.includes(2);
|
return this.user && this.user.groups_id.includes(2);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,7 @@ export class MotionBlock extends BaseModelWithAgendaItemAndListOfSpeakers<Motion
|
|||||||
|
|
||||||
public id: number;
|
public id: number;
|
||||||
public title: string;
|
public title: string;
|
||||||
|
public internal: boolean;
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super(MotionBlock.COLLECTIONSTRING, input);
|
super(MotionBlock.COLLECTIONSTRING, input);
|
||||||
|
@ -27,6 +27,10 @@ export class ViewMotionBlock extends BaseViewModelWithAgendaItemAndListOfSpeaker
|
|||||||
return this.motionBlock.title;
|
return this.motionBlock.title;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public get internal(): boolean {
|
||||||
|
return this.motionBlock.internal;
|
||||||
|
}
|
||||||
|
|
||||||
public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
|
public constructor(motionBlock: MotionBlock, agendaItem?: ViewItem, listOfSpeakers?: ViewListOfSpeakers) {
|
||||||
super(MotionBlock.COLLECTIONSTRING, motionBlock, agendaItem, listOfSpeakers);
|
super(MotionBlock.COLLECTIONSTRING, motionBlock, agendaItem, listOfSpeakers);
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,10 @@
|
|||||||
<os-head-bar [nav]="false">
|
<os-head-bar [nav]="false">
|
||||||
<!-- Title -->
|
<!-- Title -->
|
||||||
<div class="title-slot">
|
<div class="title-slot">
|
||||||
<h2 *ngIf="block && !editBlock">{{ block.title }}</h2>
|
<h2 *ngIf="block">
|
||||||
|
<mat-icon *ngIf="block.internal">lock</mat-icon>
|
||||||
<form [formGroup]="blockEditForm" (ngSubmit)="saveBlock()" (keydown)="onKeyDown($event)" *ngIf="editBlock">
|
{{ block.title }}
|
||||||
<mat-form-field>
|
</h2>
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
matInput
|
|
||||||
osAutofocus
|
|
||||||
required
|
|
||||||
formControlName="title"
|
|
||||||
placeholder="{{ 'Title' | translate }}"
|
|
||||||
/>
|
|
||||||
</mat-form-field>
|
|
||||||
</form>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Menu -->
|
<!-- Menu -->
|
||||||
@ -23,10 +13,6 @@
|
|||||||
<mat-icon>more_vert</mat-icon>
|
<mat-icon>more_vert</mat-icon>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- Save button -->
|
|
||||||
<div *ngIf="editBlock" class="extra-controls-slot on-transition-fade">
|
|
||||||
<button mat-button (click)="saveBlock()"><strong translate class="upper">Save</strong></button>
|
|
||||||
</div>
|
|
||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<mat-card>
|
<mat-card>
|
||||||
@ -111,7 +97,7 @@
|
|||||||
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
<div *osPerms="['motions.can_manage', 'motions.can_manage_metadata']">
|
||||||
<button mat-menu-item (click)="toggleEditMode()">
|
<button mat-menu-item (click)="toggleEditMode()">
|
||||||
<mat-icon>edit</mat-icon>
|
<mat-icon>edit</mat-icon>
|
||||||
<span translate>Edit title</span>
|
<span translate>Edit</span>
|
||||||
</button>
|
</button>
|
||||||
<mat-divider></mat-divider>
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
@ -121,3 +107,31 @@
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</mat-menu>
|
</mat-menu>
|
||||||
|
|
||||||
|
<ng-template #editDialog>
|
||||||
|
<h1 mat-dialog-title>
|
||||||
|
<span>{{ 'Edit details for' | translate }} {{ block.title }}</span>
|
||||||
|
</h1>
|
||||||
|
<div class="os-form-card-mobile" mat-dialog-content>
|
||||||
|
<form class="edit-form" [formGroup]="blockEditForm" (ngSubmit)="saveBlock()" (keydown)="onKeyDown($event)">
|
||||||
|
<mat-form-field>
|
||||||
|
<input matInput osAutofocus placeholder="{{ 'Title' | translate }}" formControlName="title" required/>
|
||||||
|
</mat-form-field>
|
||||||
|
<mat-checkbox formControlName="internal">Internal</mat-checkbox>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions>
|
||||||
|
<button
|
||||||
|
type="submit"
|
||||||
|
mat-button
|
||||||
|
[disabled]="!blockEditForm.valid"
|
||||||
|
color="primary"
|
||||||
|
(click)="saveBlock()"
|
||||||
|
>
|
||||||
|
<span translate>Save</span>
|
||||||
|
</button>
|
||||||
|
<button type="button" mat-button [mat-dialog-close]="null">
|
||||||
|
<span translate>Cancel</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</ng-template>
|
||||||
|
@ -51,3 +51,7 @@
|
|||||||
justify-content: flex-end !important;
|
justify-content: flex-end !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.edit-form {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ActivatedRoute, Router } from '@angular/router';
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
import { Component, OnInit, ViewChild } from '@angular/core';
|
import { Component, OnInit, ViewChild, TemplateRef } from '@angular/core';
|
||||||
import { FormGroup, FormControl, Validators } from '@angular/forms';
|
import { FormGroup, Validators, FormBuilder } from '@angular/forms';
|
||||||
import { MatSnackBar } from '@angular/material';
|
import { MatSnackBar, MatDialog } from '@angular/material';
|
||||||
import { Title } from '@angular/platform-browser';
|
import { Title } from '@angular/platform-browser';
|
||||||
|
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
@ -32,17 +32,18 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
*/
|
*/
|
||||||
public block: ViewMotionBlock;
|
public block: ViewMotionBlock;
|
||||||
|
|
||||||
/**
|
|
||||||
* Determine the edit mode
|
|
||||||
*/
|
|
||||||
public editBlock = false;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The form to edit blocks
|
* The form to edit blocks
|
||||||
*/
|
*/
|
||||||
@ViewChild('blockEditForm')
|
@ViewChild('blockEditForm')
|
||||||
public blockEditForm: FormGroup;
|
public blockEditForm: FormGroup;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reference to the template for edit-dialog
|
||||||
|
*/
|
||||||
|
@ViewChild('editDialog')
|
||||||
|
private editDialog: TemplateRef<string>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for motion block details
|
* Constructor for motion block details
|
||||||
*
|
*
|
||||||
@ -66,7 +67,9 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
private router: Router,
|
private router: Router,
|
||||||
protected repo: MotionBlockRepositoryService,
|
protected repo: MotionBlockRepositoryService,
|
||||||
protected motionRepo: MotionRepositoryService,
|
protected motionRepo: MotionRepositoryService,
|
||||||
private promptService: PromptService
|
private promptService: PromptService,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private dialog: MatDialog
|
||||||
) {
|
) {
|
||||||
super(titleService, translate, matSnackBar, motionRepo, route, storage);
|
super(titleService, translate, matSnackBar, motionRepo, route, storage);
|
||||||
}
|
}
|
||||||
@ -80,10 +83,6 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
this.initTable();
|
this.initTable();
|
||||||
const blockId = parseInt(this.route.snapshot.params.id, 10);
|
const blockId = parseInt(this.route.snapshot.params.id, 10);
|
||||||
|
|
||||||
this.blockEditForm = new FormGroup({
|
|
||||||
title: new FormControl('', Validators.required)
|
|
||||||
});
|
|
||||||
|
|
||||||
// pseudo filter
|
// pseudo filter
|
||||||
this.subscriptions.push(
|
this.subscriptions.push(
|
||||||
this.repo.getViewModelObservable(blockId).subscribe(newBlock => {
|
this.repo.getViewModelObservable(blockId).subscribe(newBlock => {
|
||||||
@ -171,7 +170,7 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
*/
|
*/
|
||||||
public onKeyDown(event: KeyboardEvent): void {
|
public onKeyDown(event: KeyboardEvent): void {
|
||||||
if (event.key === 'Escape') {
|
if (event.key === 'Escape') {
|
||||||
this.editBlock = false;
|
this.dialog.closeAll();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,15 +190,33 @@ export class MotionBlockDetailComponent extends ListViewBaseComponent<ViewMotion
|
|||||||
* Save event handler
|
* Save event handler
|
||||||
*/
|
*/
|
||||||
public saveBlock(): void {
|
public saveBlock(): void {
|
||||||
this.editBlock = false;
|
this.repo
|
||||||
this.repo.update(this.blockEditForm.value as MotionBlock, this.block);
|
.update(this.blockEditForm.value as MotionBlock, this.block)
|
||||||
|
.then(() => this.dialog.closeAll())
|
||||||
|
.catch(this.raiseError);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Click handler for the edit button
|
* Click handler for the edit button
|
||||||
*/
|
*/
|
||||||
public toggleEditMode(): void {
|
public toggleEditMode(): void {
|
||||||
this.editBlock = !this.editBlock;
|
this.blockEditForm = this.fb.group({
|
||||||
|
title: [this.block.title, Validators.required],
|
||||||
|
internal: [this.block.internal]
|
||||||
|
});
|
||||||
|
|
||||||
|
const dialogRef = this.dialog.open(this.editDialog, {
|
||||||
|
width: '400px',
|
||||||
|
maxWidth: '90vw',
|
||||||
|
maxHeight: '90vh',
|
||||||
|
disableClose: true
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.keydownEvents().subscribe((event: KeyboardEvent) => {
|
||||||
|
if (event.key === 'Enter' && event.shiftKey) {
|
||||||
|
this.saveBlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
</os-head-bar>
|
</os-head-bar>
|
||||||
|
|
||||||
<!-- Creating a new motion block -->
|
<!-- Creating a new motion block -->
|
||||||
<mat-card class="os-card" *ngIf="blockToCreate">
|
<mat-card class="os-card" *ngIf="isCreatingNewBlock">
|
||||||
<mat-card-title translate>New motion block</mat-card-title>
|
<mat-card-title translate>New motion block</mat-card-title>
|
||||||
<mat-card-content>
|
<mat-card-content>
|
||||||
<form [formGroup]="createBlockForm" (keydown)="onKeyDown($event)">
|
<form [formGroup]="createBlockForm" (keydown)="onKeyDown($event)">
|
||||||
@ -18,6 +18,11 @@
|
|||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<!-- Internal -->
|
||||||
|
<p>
|
||||||
|
<mat-checkbox formControlName="internal"><span translate>Internal</span></mat-checkbox>
|
||||||
|
</p>
|
||||||
|
|
||||||
<!-- Parent item -->
|
<!-- Parent item -->
|
||||||
<p>
|
<p>
|
||||||
<os-search-value-selector
|
<os-search-value-selector
|
||||||
@ -44,7 +49,7 @@
|
|||||||
|
|
||||||
<!-- Save and Cancel buttons -->
|
<!-- Save and Cancel buttons -->
|
||||||
<mat-card-actions>
|
<mat-card-actions>
|
||||||
<button mat-button (click)="onSaveNewButton()"><span translate>Save</span></button>
|
<button mat-button [disabled]="!createBlockForm.valid" (click)="onSaveNewButton()"><span translate>Save</span></button>
|
||||||
<button mat-button (click)="onCancel()"><span translate>Cancel</span></button>
|
<button mat-button (click)="onCancel()"><span translate>Cancel</span></button>
|
||||||
</mat-card-actions>
|
</mat-card-actions>
|
||||||
</mat-card>
|
</mat-card>
|
||||||
@ -65,7 +70,10 @@
|
|||||||
<mat-header-cell *matHeaderCellDef>
|
<mat-header-cell *matHeaderCellDef>
|
||||||
<span translate>Title</span>
|
<span translate>Title</span>
|
||||||
</mat-header-cell>
|
</mat-header-cell>
|
||||||
<mat-cell *matCellDef="let block"> {{ block.title }} </mat-cell>
|
<mat-cell *matCellDef="let block">
|
||||||
|
<mat-icon matTooltip="Internal" *ngIf="block.internal">lock</mat-icon>
|
||||||
|
{{ block.title }}
|
||||||
|
</mat-cell>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
<!-- amount column -->
|
<!-- amount column -->
|
||||||
|
@ -36,9 +36,9 @@ export class MotionBlockListComponent
|
|||||||
public createBlockForm: FormGroup;
|
public createBlockForm: FormGroup;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The new motion block to create
|
* Flag, if the creation panel is open
|
||||||
*/
|
*/
|
||||||
public blockToCreate: MotionBlock | null;
|
public isCreatingNewBlock = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds the agenda items to select the parent item
|
* Holds the agenda items to select the parent item
|
||||||
@ -99,7 +99,8 @@ export class MotionBlockListComponent
|
|||||||
this.createBlockForm = this.formBuilder.group({
|
this.createBlockForm = this.formBuilder.group({
|
||||||
title: ['', Validators.required],
|
title: ['', Validators.required],
|
||||||
agenda_type: ['', Validators.required],
|
agenda_type: ['', Validators.required],
|
||||||
agenda_parent_id: []
|
agenda_parent_id: [],
|
||||||
|
internal: [false]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -164,9 +165,9 @@ export class MotionBlockListComponent
|
|||||||
* Click handler for the plus button
|
* Click handler for the plus button
|
||||||
*/
|
*/
|
||||||
public onPlusButton(): void {
|
public onPlusButton(): void {
|
||||||
if (!this.blockToCreate) {
|
if (!this.isCreatingNewBlock) {
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
this.blockToCreate = new MotionBlock();
|
this.isCreatingNewBlock = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -176,15 +177,18 @@ export class MotionBlockListComponent
|
|||||||
*/
|
*/
|
||||||
public onSaveNewButton(): void {
|
public onSaveNewButton(): void {
|
||||||
if (this.createBlockForm.valid) {
|
if (this.createBlockForm.valid) {
|
||||||
const blockPatch = this.createBlockForm.value;
|
const block = this.createBlockForm.value;
|
||||||
if (!blockPatch.agenda_parent_id) {
|
if (!block.agenda_parent_id) {
|
||||||
delete blockPatch.agenda_parent_id;
|
delete block.agenda_parent_id;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.blockToCreate.patchValues(blockPatch);
|
try {
|
||||||
this.repo.create(this.blockToCreate);
|
this.repo.create(block);
|
||||||
this.resetForm();
|
this.resetForm();
|
||||||
this.blockToCreate = null;
|
this.isCreatingNewBlock = false;
|
||||||
|
} catch (e) {
|
||||||
|
this.raiseError(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// set a form control as "touched" to trigger potential error messages
|
// set a form control as "touched" to trigger potential error messages
|
||||||
this.createBlockForm.get('title').markAsTouched();
|
this.createBlockForm.get('title').markAsTouched();
|
||||||
@ -209,6 +213,6 @@ export class MotionBlockListComponent
|
|||||||
* Cancels the current form action
|
* Cancels the current form action
|
||||||
*/
|
*/
|
||||||
public onCancel(): void {
|
public onCancel(): void {
|
||||||
this.blockToCreate = null;
|
this.isCreatingNewBlock = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,20 @@ class MotionBlockAccessPermissions(BaseAccessPermissions):
|
|||||||
|
|
||||||
base_permission = "motions.can_see"
|
base_permission = "motions.can_see"
|
||||||
|
|
||||||
|
async def get_restricted_data(
|
||||||
|
self, full_data: List[Dict[str, Any]], user_id: int
|
||||||
|
) -> List[Dict[str, Any]]:
|
||||||
|
"""
|
||||||
|
Users without `motions.can_manage` cannot see internal blocks.
|
||||||
|
"""
|
||||||
|
data: List[Dict[str, Any]] = []
|
||||||
|
if await async_has_perm(user_id, "motions.can_manage"):
|
||||||
|
data = full_data
|
||||||
|
else:
|
||||||
|
data = [full for full in full_data if not full["internal"]]
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
|
||||||
class WorkflowAccessPermissions(BaseAccessPermissions):
|
class WorkflowAccessPermissions(BaseAccessPermissions):
|
||||||
"""
|
"""
|
||||||
|
16
openslides/motions/migrations/0027_motion_block_internal.py
Normal file
16
openslides/motions/migrations/0027_motion_block_internal.py
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Generated by Django 2.2 on 2019-05-21 06:41
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [("motions", "0026_rename_restriction")]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="motionblock",
|
||||||
|
name="internal",
|
||||||
|
field=models.BooleanField(default=False),
|
||||||
|
)
|
||||||
|
]
|
@ -833,6 +833,12 @@ class MotionBlock(RESTModelMixin, AgendaItemWithListOfSpeakersMixin, models.Mode
|
|||||||
|
|
||||||
title = models.CharField(max_length=255)
|
title = models.CharField(max_length=255)
|
||||||
|
|
||||||
|
internal = models.BooleanField(default=False)
|
||||||
|
"""
|
||||||
|
If a motion block is internal, only users with `motions.can_manage` can see and
|
||||||
|
manage these blocks.
|
||||||
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
verbose_name = "Motion block"
|
verbose_name = "Motion block"
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
|
@ -82,6 +82,7 @@ class MotionBlockSerializer(ModelSerializer):
|
|||||||
"list_of_speakers_id",
|
"list_of_speakers_id",
|
||||||
"agenda_type",
|
"agenda_type",
|
||||||
"agenda_parent_id",
|
"agenda_parent_id",
|
||||||
|
"internal",
|
||||||
)
|
)
|
||||||
|
|
||||||
def create(self, validated_data):
|
def create(self, validated_data):
|
||||||
|
@ -1867,6 +1867,66 @@ class NumberMotionsInCategory(TestCase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class TestMotionBlock(TestCase):
|
||||||
|
def setUp(self):
|
||||||
|
self.client = APIClient()
|
||||||
|
self.client.login(username="admin", password="admin")
|
||||||
|
|
||||||
|
def make_admin_delegate(self):
|
||||||
|
admin = get_user_model().objects.get(username="admin")
|
||||||
|
admin.groups.add(GROUP_DELEGATE_PK)
|
||||||
|
admin.groups.remove(GROUP_ADMIN_PK)
|
||||||
|
inform_changed_data(admin)
|
||||||
|
|
||||||
|
def test_creation(self):
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionblock-list"), {"title": "test_title_r23098OMFwoqof3if3kO"}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_201_CREATED)
|
||||||
|
self.assertTrue(MotionBlock.objects.exists())
|
||||||
|
self.assertEqual(
|
||||||
|
MotionBlock.objects.get().title, "test_title_r23098OMFwoqof3if3kO"
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_creation_no_data(self):
|
||||||
|
response = self.client.post(reverse("motionblock-list"), {})
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_400_BAD_REQUEST)
|
||||||
|
self.assertFalse(MotionBlock.objects.exists())
|
||||||
|
|
||||||
|
def test_creation_not_authenticated(self):
|
||||||
|
self.make_admin_delegate()
|
||||||
|
response = self.client.post(
|
||||||
|
reverse("motionblock-list"), {"title": "test_title_2PFjpf39ap,38fuMPO§8"}
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
|
||||||
|
self.assertFalse(MotionBlock.objects.exists())
|
||||||
|
|
||||||
|
def test_retrieve_simple(self):
|
||||||
|
motion_block = MotionBlock(title="test_title")
|
||||||
|
motion_block.save()
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("motionblock-detail", args=[motion_block.pk])
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_200_OK)
|
||||||
|
self.assertEqual(
|
||||||
|
sorted(response.data.keys()),
|
||||||
|
sorted(
|
||||||
|
("agenda_item_id", "id", "internal", "list_of_speakers_id", "title")
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_retrieve_internal_non_admin(self):
|
||||||
|
self.make_admin_delegate()
|
||||||
|
motion_block = MotionBlock(title="test_title", internal=True)
|
||||||
|
motion_block.save()
|
||||||
|
|
||||||
|
response = self.client.get(
|
||||||
|
reverse("motionblock-detail", args=[motion_block.pk])
|
||||||
|
)
|
||||||
|
self.assertEqual(response.status_code, status.HTTP_404_NOT_FOUND)
|
||||||
|
|
||||||
|
|
||||||
class FollowRecommendationsForMotionBlock(TestCase):
|
class FollowRecommendationsForMotionBlock(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests following the recommendations of motions in an motion block.
|
Tests following the recommendations of motions in an motion block.
|
||||||
|
Loading…
Reference in New Issue
Block a user