Merge pull request #5370 from tsiegleauq/tags-for-agenda
Add tags for agenda items
This commit is contained in:
commit
1ca3196a75
@ -20,6 +20,7 @@ import {
|
|||||||
} from 'app/site/base/base-view-model-with-agenda-item';
|
} from 'app/site/base/base-view-model-with-agenda-item';
|
||||||
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
import { ViewMotion } from 'app/site/motions/models/view-motion';
|
||||||
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
import { ViewMotionBlock } from 'app/site/motions/models/view-motion-block';
|
||||||
|
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||||
import { ViewTopic } from 'app/site/topics/models/view-topic';
|
import { ViewTopic } from 'app/site/topics/models/view-topic';
|
||||||
import { BaseHasContentObjectRepository } from '../base-has-content-object-repository';
|
import { BaseHasContentObjectRepository } from '../base-has-content-object-repository';
|
||||||
import { BaseIsAgendaItemContentObjectRepository } from '../base-is-agenda-item-content-object-repository';
|
import { BaseIsAgendaItemContentObjectRepository } from '../base-is-agenda-item-content-object-repository';
|
||||||
@ -34,6 +35,12 @@ const ItemRelations: RelationDefinition[] = [
|
|||||||
VForeignVerbose: 'BaseViewModelWithAgendaItem',
|
VForeignVerbose: 'BaseViewModelWithAgendaItem',
|
||||||
ownContentObjectDataKey: 'contentObjectData',
|
ownContentObjectDataKey: 'contentObjectData',
|
||||||
ownKey: 'contentObject'
|
ownKey: 'contentObject'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'M2M',
|
||||||
|
ownIdKey: 'tags_id',
|
||||||
|
ownKey: 'tags',
|
||||||
|
foreignViewModel: ViewTag
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -49,6 +49,7 @@ export class Item extends BaseModelWithContentObject<Item> {
|
|||||||
public weight: number;
|
public weight: number;
|
||||||
public parent_id: number;
|
public parent_id: number;
|
||||||
public level: number;
|
public level: number;
|
||||||
|
public tags_id: number[];
|
||||||
|
|
||||||
public constructor(input?: any) {
|
public constructor(input?: any) {
|
||||||
super(Item.COLLECTIONSTRING, input);
|
super(Item.COLLECTIONSTRING, input);
|
||||||
|
@ -33,7 +33,6 @@
|
|||||||
<div *pblNgridCellDef="'title'; row as item; rowContext as rowContext" class="cell-slot fill">
|
<div *pblNgridCellDef="'title'; row as item; rowContext as rowContext" class="cell-slot fill">
|
||||||
<a class="detail-link" [routerLink]="getDetailUrl(item)" *ngIf="!isMultiSelect"></a>
|
<a class="detail-link" [routerLink]="getDetailUrl(item)" *ngIf="!isMultiSelect"></a>
|
||||||
<div [ngStyle]="{ 'margin-left': item.level * 25 + 'px' }" class="innerTable">
|
<div [ngStyle]="{ 'margin-left': item.level * 25 + 'px' }" class="innerTable">
|
||||||
|
|
||||||
<!-- Title line -->
|
<!-- Title line -->
|
||||||
<div class="title-line ellipsis-overflow">
|
<div class="title-line ellipsis-overflow">
|
||||||
<!-- Is Closed -->
|
<!-- Is Closed -->
|
||||||
@ -64,9 +63,22 @@
|
|||||||
<!-- Info Column -->
|
<!-- Info Column -->
|
||||||
<div *pblNgridCellDef="'info'; row as item" class="cell-slot fill clickable" (click)="openEditInfo(item)">
|
<div *pblNgridCellDef="'info'; row as item" class="cell-slot fill clickable" (click)="openEditInfo(item)">
|
||||||
<div class="info-col-items">
|
<div class="info-col-items">
|
||||||
<div *osPerms="'agenda.can_manage'; and: item.verboseType">
|
<!-- Tags -->
|
||||||
|
<div *ngIf="item.tags && item.tags.length">
|
||||||
|
<os-icon-container icon="local_offer">
|
||||||
|
<span *ngFor="let tag of item.tags; let last = last">
|
||||||
|
{{ tag.getTitle() }}
|
||||||
|
<span *ngIf="!last">, </span>
|
||||||
|
</span>
|
||||||
|
</os-icon-container>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Visibility (Internal, Public, Hidden) -->
|
||||||
|
<div *osPerms="'agenda.can_manage'; and: item.verboseType" class="spacer-top-5">
|
||||||
<os-icon-container icon="visibility">{{ item.verboseType | translate }}</os-icon-container>
|
<os-icon-container icon="visibility">{{ item.verboseType | translate }}</os-icon-container>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Duration -->
|
||||||
<div *ngIf="item.duration" class="spacer-top-5">
|
<div *ngIf="item.duration" class="spacer-top-5">
|
||||||
<os-icon-container icon="access_time">
|
<os-icon-container icon="access_time">
|
||||||
{{ durationService.durationToString(item.duration, 'h') }}
|
{{ durationService.durationToString(item.duration, 'h') }}
|
||||||
@ -98,6 +110,8 @@
|
|||||||
<span>{{ 'Multiselect' | translate }}</span>
|
<span>{{ 'Multiselect' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
<!-- automatic numbering -->
|
<!-- automatic numbering -->
|
||||||
<button mat-menu-item *ngIf="isNumberingAllowed" (click)="onAutoNumbering()">
|
<button mat-menu-item *ngIf="isNumberingAllowed" (click)="onAutoNumbering()">
|
||||||
<mat-icon>format_list_numbered</mat-icon>
|
<mat-icon>format_list_numbered</mat-icon>
|
||||||
@ -117,6 +131,15 @@
|
|||||||
<mat-icon>mic</mat-icon>
|
<mat-icon>mic</mat-icon>
|
||||||
<span>{{ 'Current list of speakers' | translate }}</span>
|
<span>{{ 'Current list of speakers' | translate }}</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
|
<!-- Tags -->
|
||||||
|
<button mat-menu-item routerLink="/tags" *osPerms="'core.can_manage_tags'">
|
||||||
|
<mat-icon>local_offer</mat-icon>
|
||||||
|
<span>{{ 'Tags' | translate }}</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
<!-- CSV export -->
|
<!-- CSV export -->
|
||||||
<button mat-menu-item *osPerms="'agenda.can_manage'" (click)="csvExportItemList()">
|
<button mat-menu-item *osPerms="'agenda.can_manage'" (click)="csvExportItemList()">
|
||||||
<mat-icon>archive</mat-icon>
|
<mat-icon>archive</mat-icon>
|
||||||
|
@ -1,6 +1,15 @@
|
|||||||
<h1 mat-dialog-title *ngIf="item">{{ 'Edit details for' | translate }} {{ item.getTitle() }}</h1>
|
<h1 mat-dialog-title *ngIf="item">{{ 'Edit details for' | translate }} {{ item.getTitle() }}</h1>
|
||||||
<div mat-dialog-content>
|
<div mat-dialog-content>
|
||||||
<form class="item-dialog-form" [formGroup]="agendaInfoForm" (keydown)="onKeyDown($event)">
|
<form class="item-dialog-form" [formGroup]="agendaInfoForm" (keydown)="onKeyDown($event)">
|
||||||
|
<!-- Tag -->
|
||||||
|
<mat-form-field *ngIf="isTagAvailable()">
|
||||||
|
<mat-select formControlName="tags_id" multiple placeholder="{{ 'Tags' | translate }}">
|
||||||
|
<mat-option *ngFor="let tag of tags" [value]="tag.id">
|
||||||
|
{{ tag.getTitle() | translate }}
|
||||||
|
</mat-option>
|
||||||
|
</mat-select>
|
||||||
|
</mat-form-field>
|
||||||
|
|
||||||
<!-- Visibility -->
|
<!-- Visibility -->
|
||||||
<mat-form-field>
|
<mat-form-field>
|
||||||
<mat-select formControlName="type" placeholder="{{ 'Agenda visibility' | translate }}">
|
<mat-select formControlName="type" placeholder="{{ 'Agenda visibility' | translate }}">
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import { Component, Inject } from '@angular/core';
|
import { Component, Inject, OnInit } from '@angular/core';
|
||||||
import { FormBuilder, FormGroup } from '@angular/forms';
|
import { FormBuilder, FormGroup } from '@angular/forms';
|
||||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||||
import { DurationService } from 'app/core/ui-services/duration.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 { durationValidator } from 'app/shared/validators/custom-validators';
|
import { durationValidator } from 'app/shared/validators/custom-validators';
|
||||||
|
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||||
import { ViewItem } from '../../models/view-item';
|
import { ViewItem } from '../../models/view-item';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -15,7 +17,7 @@ import { ViewItem } from '../../models/view-item';
|
|||||||
templateUrl: './item-info-dialog.component.html',
|
templateUrl: './item-info-dialog.component.html',
|
||||||
styleUrls: ['./item-info-dialog.component.scss']
|
styleUrls: ['./item-info-dialog.component.scss']
|
||||||
})
|
})
|
||||||
export class ItemInfoDialogComponent {
|
export class ItemInfoDialogComponent implements OnInit {
|
||||||
/**
|
/**
|
||||||
* Holds the agenda item form
|
* Holds the agenda item form
|
||||||
*/
|
*/
|
||||||
@ -26,6 +28,8 @@ export class ItemInfoDialogComponent {
|
|||||||
*/
|
*/
|
||||||
public itemVisibility = ItemVisibilityChoices;
|
public itemVisibility = ItemVisibilityChoices;
|
||||||
|
|
||||||
|
public tags: ViewTag[] = [];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
@ -38,22 +42,42 @@ export class ItemInfoDialogComponent {
|
|||||||
public formBuilder: FormBuilder,
|
public formBuilder: FormBuilder,
|
||||||
public durationService: DurationService,
|
public durationService: DurationService,
|
||||||
public dialogRef: MatDialogRef<ItemInfoDialogComponent>,
|
public dialogRef: MatDialogRef<ItemInfoDialogComponent>,
|
||||||
|
public tagRepo: TagRepositoryService,
|
||||||
@Inject(MAT_DIALOG_DATA) public item: ViewItem
|
@Inject(MAT_DIALOG_DATA) public item: ViewItem
|
||||||
) {
|
) {
|
||||||
this.agendaInfoForm = this.formBuilder.group({
|
this.agendaInfoForm = this.formBuilder.group({
|
||||||
|
tags_id: [],
|
||||||
type: [''],
|
type: [''],
|
||||||
durationText: ['', durationValidator],
|
durationText: ['', durationValidator],
|
||||||
item_number: [''],
|
item_number: [''],
|
||||||
comment: ['']
|
comment: ['']
|
||||||
});
|
});
|
||||||
|
|
||||||
// load current values
|
|
||||||
if (item) {
|
|
||||||
this.agendaInfoForm.get('type').setValue(item.type);
|
|
||||||
this.agendaInfoForm.get('durationText').setValue(this.durationService.durationToString(item.duration, 'h'));
|
|
||||||
this.agendaInfoForm.get('item_number').setValue(item.item_number);
|
|
||||||
this.agendaInfoForm.get('comment').setValue(item.comment);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ngOnInit(): void {
|
||||||
|
// load current values
|
||||||
|
if (this.item) {
|
||||||
|
this.agendaInfoForm.get('tags_id').setValue(this.item.tags_id);
|
||||||
|
this.agendaInfoForm.get('type').setValue(this.item.type);
|
||||||
|
this.agendaInfoForm
|
||||||
|
.get('durationText')
|
||||||
|
.setValue(this.durationService.durationToString(this.item.duration, 'h'));
|
||||||
|
this.agendaInfoForm.get('item_number').setValue(this.item.item_number);
|
||||||
|
this.agendaInfoForm.get('comment').setValue(this.item.comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.tagRepo.getViewModelListObservable().subscribe(tags => {
|
||||||
|
this.tags = tags;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if tags are available.
|
||||||
|
*
|
||||||
|
* @returns A boolean if they are available.
|
||||||
|
*/
|
||||||
|
public isTagAvailable(): boolean {
|
||||||
|
return !!this.tags && this.tags.length > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,6 +2,7 @@ import { Item, ItemVisibilityChoices } from 'app/shared/models/agenda/item';
|
|||||||
import { ContentObject } from 'app/shared/models/base/content-object';
|
import { ContentObject } from 'app/shared/models/base/content-object';
|
||||||
import { BaseViewModelWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
|
import { BaseViewModelWithAgendaItem } from 'app/site/base/base-view-model-with-agenda-item';
|
||||||
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
|
import { BaseViewModelWithContentObject } from 'app/site/base/base-view-model-with-content-object';
|
||||||
|
import { ViewTag } from 'app/site/tags/models/view-tag';
|
||||||
|
|
||||||
export interface ItemTitleInformation {
|
export interface ItemTitleInformation {
|
||||||
contentObject: BaseViewModelWithAgendaItem;
|
contentObject: BaseViewModelWithAgendaItem;
|
||||||
@ -53,4 +54,7 @@ export class ViewItem extends BaseViewModelWithContentObject<Item, BaseViewModel
|
|||||||
return this.contentObjectData.collection;
|
return this.contentObjectData.collection;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
export interface ViewItem extends Item {}
|
interface IItemRelations {
|
||||||
|
tags: ViewTag[];
|
||||||
|
}
|
||||||
|
export interface ViewItem extends Item, IItemRelations {}
|
||||||
|
@ -36,7 +36,8 @@ export class AgendaCsvExportService {
|
|||||||
},
|
},
|
||||||
{ label: 'Duration', property: 'duration' },
|
{ label: 'Duration', property: 'duration' },
|
||||||
{ label: 'Comment', property: 'comment' },
|
{ label: 'Comment', property: 'comment' },
|
||||||
{ label: 'Item type', property: 'verboseCsvType' }
|
{ label: 'Item type', property: 'verboseCsvType' },
|
||||||
|
{ label: 'Tags', property: 'tags' }
|
||||||
],
|
],
|
||||||
this.translate.instant('Agenda') + '.csv'
|
this.translate.instant('Agenda') + '.csv'
|
||||||
);
|
);
|
||||||
|
@ -4,6 +4,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||||||
|
|
||||||
import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service';
|
import { OpenSlidesStatusService } from 'app/core/core-services/openslides-status.service';
|
||||||
import { StorageService } from 'app/core/core-services/storage.service';
|
import { StorageService } from 'app/core/core-services/storage.service';
|
||||||
|
import { TagRepositoryService } from 'app/core/repositories/tags/tag-repository.service';
|
||||||
import { BaseFilterListService, OsFilter, OsFilterOption } from 'app/core/ui-services/base-filter-list.service';
|
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 { ViewItem } from '../models/view-item';
|
||||||
@ -20,14 +21,27 @@ export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
|||||||
*/
|
*/
|
||||||
protected storageKey = 'AgendaList';
|
protected storageKey = 'AgendaList';
|
||||||
|
|
||||||
|
public tagFilterOptions: OsFilter = {
|
||||||
|
property: 'tags_id',
|
||||||
|
label: 'Tags',
|
||||||
|
options: []
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor. Also creates the dynamic filter options
|
* Constructor. Also creates the dynamic filter options
|
||||||
*
|
*
|
||||||
* @param store
|
* @param store
|
||||||
* @param translate Translation service
|
* @param translate Translation service
|
||||||
*/
|
*/
|
||||||
public constructor(store: StorageService, OSStatus: OpenSlidesStatusService, private translate: TranslateService) {
|
public constructor(
|
||||||
|
store: StorageService,
|
||||||
|
OSStatus: OpenSlidesStatusService,
|
||||||
|
private translate: TranslateService,
|
||||||
|
tagRepo: TagRepositoryService
|
||||||
|
) {
|
||||||
super(store, OSStatus);
|
super(store, OSStatus);
|
||||||
|
|
||||||
|
this.updateFilterForRepo(tagRepo, this.tagFilterOptions, this.translate.instant('No tags'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,11 +49,6 @@ export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
|||||||
*/
|
*/
|
||||||
protected getFilterDefinitions(): OsFilter[] {
|
protected getFilterDefinitions(): OsFilter[] {
|
||||||
return [
|
return [
|
||||||
{
|
|
||||||
label: 'Visibility',
|
|
||||||
property: 'type',
|
|
||||||
options: this.createVisibilityFilterOptions()
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
label: 'Status',
|
label: 'Status',
|
||||||
property: 'closed',
|
property: 'closed',
|
||||||
@ -48,6 +57,12 @@ export class AgendaFilterListService extends BaseFilterListService<ViewItem> {
|
|||||||
{ label: this.translate.instant('Closed items'), condition: true }
|
{ label: this.translate.instant('Closed items'), condition: true }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
this.tagFilterOptions,
|
||||||
|
{
|
||||||
|
label: 'Visibility',
|
||||||
|
property: 'type',
|
||||||
|
options: this.createVisibilityFilterOptions()
|
||||||
|
},
|
||||||
{
|
{
|
||||||
label: 'Type',
|
label: 'Type',
|
||||||
property: 'collection',
|
property: 'collection',
|
||||||
|
19
openslides/agenda/migrations/0009_item_tags.py
Normal file
19
openslides/agenda/migrations/0009_item_tags.py
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
# Generated by Django 2.2.12 on 2020-05-14 10:39
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
("core", "0031_projector_default_height"),
|
||||||
|
("agenda", "0008_default_ordering_item"),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.AddField(
|
||||||
|
model_name="item",
|
||||||
|
name="tags",
|
||||||
|
field=models.ManyToManyField(blank=True, to="core.Tag"),
|
||||||
|
),
|
||||||
|
]
|
@ -9,7 +9,7 @@ from django.db import models, transaction
|
|||||||
from django.utils import timezone
|
from django.utils import timezone
|
||||||
|
|
||||||
from openslides.core.config import config
|
from openslides.core.config import config
|
||||||
from openslides.core.models import Countdown
|
from openslides.core.models import Countdown, Tag
|
||||||
from openslides.utils.autoupdate import inform_changed_data
|
from openslides.utils.autoupdate import inform_changed_data
|
||||||
from openslides.utils.exceptions import OpenSlidesError
|
from openslides.utils.exceptions import OpenSlidesError
|
||||||
from openslides.utils.manager import BaseManager
|
from openslides.utils.manager import BaseManager
|
||||||
@ -41,7 +41,7 @@ class ItemManager(BaseManager):
|
|||||||
return (
|
return (
|
||||||
super()
|
super()
|
||||||
.get_prefetched_queryset(*args, **kwargs)
|
.get_prefetched_queryset(*args, **kwargs)
|
||||||
.prefetch_related("content_object", "parent")
|
.prefetch_related("content_object", "parent", "tags")
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_only_non_public_items(self):
|
def get_only_non_public_items(self):
|
||||||
@ -276,6 +276,11 @@ class Item(RESTModelMixin, models.Model):
|
|||||||
Field for generic relation to a related object. General field to the related object.
|
Field for generic relation to a related object. General field to the related object.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
tags = models.ManyToManyField(Tag, blank=True)
|
||||||
|
"""
|
||||||
|
Tags for the agenda item.
|
||||||
|
"""
|
||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
permissions = (
|
permissions = (
|
||||||
|
@ -51,6 +51,7 @@ class ItemSerializer(ModelSerializer):
|
|||||||
"weight",
|
"weight",
|
||||||
"parent",
|
"parent",
|
||||||
"level",
|
"level",
|
||||||
|
"tags",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -32,6 +32,7 @@ def test_agenda_item_db_queries():
|
|||||||
* 1 request to get all topics,
|
* 1 request to get all topics,
|
||||||
* 1 request to get all motion blocks and
|
* 1 request to get all motion blocks and
|
||||||
* 1 request to get all parents
|
* 1 request to get all parents
|
||||||
|
* 1 request to get all tags
|
||||||
"""
|
"""
|
||||||
parent = Topic.objects.create(title="parent").agenda_item
|
parent = Topic.objects.create(title="parent").agenda_item
|
||||||
for index in range(10):
|
for index in range(10):
|
||||||
@ -45,7 +46,7 @@ def test_agenda_item_db_queries():
|
|||||||
MotionBlock.objects.create(title="block1")
|
MotionBlock.objects.create(title="block1")
|
||||||
MotionBlock.objects.create(title="block1")
|
MotionBlock.objects.create(title="block1")
|
||||||
|
|
||||||
assert count_queries(Item.get_elements)() == 6
|
assert count_queries(Item.get_elements)() == 7
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.django_db(transaction=False)
|
@pytest.mark.django_db(transaction=False)
|
||||||
|
@ -109,7 +109,7 @@ class CreateMotion(TestCase):
|
|||||||
The created motion should have an identifier and the admin user should
|
The created motion should have an identifier and the admin user should
|
||||||
be the submitter.
|
be the submitter.
|
||||||
"""
|
"""
|
||||||
with self.assertNumQueries(51):
|
with self.assertNumQueries(52):
|
||||||
response = self.client.post(
|
response = self.client.post(
|
||||||
reverse("motion-list"),
|
reverse("motion-list"),
|
||||||
{
|
{
|
||||||
@ -185,6 +185,7 @@ class CreateMotion(TestCase):
|
|||||||
"weight": 10000,
|
"weight": 10000,
|
||||||
"parent_id": None,
|
"parent_id": None,
|
||||||
"level": 0,
|
"level": 0,
|
||||||
|
"tags_id": [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
Loading…
Reference in New Issue
Block a user