Merge pull request #5902 from FinnStutzenstein/ChatAccessGroups

Change chat access groups
This commit is contained in:
Emanuel Schütze 2021-02-19 15:18:08 +01:00 committed by GitHub
commit baad950698
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 218 additions and 76 deletions

View File

@ -9,8 +9,10 @@ run-dev: | build-dev
stop-dev: stop-dev:
docker-compose -f docker/docker-compose.dev.yml down docker-compose -f docker/docker-compose.dev.yml down
get-server-shell: server-shell:
docker-compose -f docker/docker-compose.dev.yml run server bash docker-compose -f docker/docker-compose.dev.yml run --entrypoint="" server docker/wait-for-dev-dependencies.sh
UID=$$(id -u $${USER}) GID=$$(id -g $${USER}) docker-compose -f docker/docker-compose.dev.yml run --entrypoint="" server bash
docker-compose -f docker/docker-compose.dev.yml down
reload-proxy: reload-proxy:
docker-compose -f docker/docker-compose.dev.yml exec -w /etc/caddy proxy caddy reload docker-compose -f docker/docker-compose.dev.yml exec -w /etc/caddy proxy caddy reload

@ -1 +1 @@
Subproject commit 0197c762d94c0723b377b0b2773fb329b7ccaeca Subproject commit 76d3f5385e0a76e79d09d573041a0839d8f483d0

View File

@ -18,8 +18,14 @@ import { DataStoreService } from '../../core-services/data-store.service';
const ChatGroupRelations: RelationDefinition[] = [ const ChatGroupRelations: RelationDefinition[] = [
{ {
type: 'M2M', type: 'M2M',
ownIdKey: 'access_groups_id', ownIdKey: 'read_groups_id',
ownKey: 'access_groups', ownKey: 'read_groups',
foreignViewModel: ViewGroup
},
{
type: 'M2M',
ownIdKey: 'write_groups_id',
ownKey: 'write_groups',
foreignViewModel: ViewGroup foreignViewModel: ViewGroup
} }
]; ];

View File

@ -1,5 +1,10 @@
import { animate, state, style, transition, trigger } from '@angular/animations'; import { animate, state, style, transition, trigger } from '@angular/animations';
const fadeSpeed = {
fast: 200,
slow: 600
};
const slideIn = [style({ transform: 'translateX(-85%)' }), animate('600ms ease')]; const slideIn = [style({ transform: 'translateX(-85%)' }), animate('600ms ease')];
const slideOut = [ const slideOut = [
style({ transform: 'translateX(0)' }), style({ transform: 'translateX(0)' }),
@ -11,9 +16,15 @@ const slideOut = [
) )
]; ];
export const collapseAndFade = trigger('collapse', [
state('in', style({ opacity: 1, height: '100%' })),
transition(':enter', [style({ opacity: 0, height: 0 }), animate(fadeSpeed.fast)]),
transition(':leave', animate(fadeSpeed.fast, style({ opacity: 0, height: 0 })))
]);
export const fadeAnimation = trigger('fade', [ export const fadeAnimation = trigger('fade', [
state('in', style({ opacity: 1 })), state('in', style({ opacity: 1 })),
transition(':enter', [style({ opacity: 0 }), animate(600)]), transition(':enter', [style({ opacity: 0 }), animate(fadeSpeed.slow)]),
transition(':leave', animate(600, style({ opacity: 0 }))) transition(':leave', animate(fadeSpeed.slow, style({ opacity: 0 })))
]); ]);
export const navItemAnim = trigger('navItemAnim', [transition(':enter', slideIn), transition(':leave', slideOut)]); export const navItemAnim = trigger('navItemAnim', [transition(':enter', slideIn), transition(':leave', slideOut)]);

View File

@ -18,10 +18,6 @@ os-icon-container {
display: flex; display: flex;
align-items: center; align-items: center;
& + & {
margin-top: 5px;
}
&.small-container { &.small-container {
@include icon-container(12px, 12px, 400); @include icon-container(12px, 12px, 400);
} }

View File

@ -5,7 +5,8 @@ export class ChatGroup extends BaseModel<ChatGroup> {
public id: number; public id: number;
public name: string; public name: string;
public access_groups_id: number[]; public read_groups_id: number[];
public write_groups_id: number[];
public constructor(input?: any) { public constructor(input?: any) {
super(ChatGroup.COLLECTIONSTRING, input); super(ChatGroup.COLLECTIONSTRING, input);

View File

@ -1,17 +1,29 @@
<div class="chat-header" *osPerms="permission.chatCanManage"> <div class="chat-header" *osPerms="permission.chatCanManage">
<os-icon-container <os-icon-container
icon="group" icon="group"
*ngIf="chatGroup.access_groups.length" *ngIf="chatGroup.write_groups.length"
matTooltip="{{ 'Access groups' | translate }}" matTooltip="{{ 'Groups with write permissions' | translate }}"
> >
<span *ngFor="let group of chatGroup.access_groups | slice: 0:3; let last = last"> <span *ngFor="let group of chatGroup.write_groups | slice: 0:3; let last = last">
<span>{{ group.getTitle() | translate }}</span> <span>{{ group.getTitle() | translate }}</span>
<span *ngIf="!last">, </span> <span *ngIf="!last">, </span>
<span *ngIf="last && chatGroup.access_groups.length > 3">...</span> <span *ngIf="last && chatGroup.write_groups.length > 3">...</span>
</span>
</os-icon-container>
<os-icon-container
icon="remove_red_eye"
*ngIf="readOnlyGroups.length"
matTooltip="{{ 'Groups with read permissions' | translate }}"
>
<span *ngFor="let group of readOnlyGroups | slice: 0:3; let last = last">
<span>{{ group.getTitle() | translate }}</span>
<span *ngIf="!last">, </span>
<span *ngIf="last && chatGroup.read_groups.length > 3">...</span>
</span> </span>
</os-icon-container> </os-icon-container>
<button class="chat-options" mat-icon-button [matMenuTriggerFor]="chatgroupMenu"> <button class="chat-options" mat-icon-button [matMenuTriggerFor]="chatgroupMenu">
<mat-icon> more_vert </mat-icon> <mat-icon>more_vert</mat-icon>
</button> </button>
</div> </div>
@ -41,5 +53,4 @@
<span>{{ 'Delete' | translate }}</span> <span>{{ 'Delete' | translate }}</span>
</button> </button>
</ng-container> </ng-container>
<!-- edit -->
</mat-menu> </mat-menu>

View File

@ -22,6 +22,7 @@ import { ChatGroup } from 'app/shared/models/chat/chat-group';
import { infoDialogSettings } from 'app/shared/utils/dialog-settings'; import { infoDialogSettings } from 'app/shared/utils/dialog-settings';
import { BaseViewComponentDirective } from 'app/site/base/base-view'; import { BaseViewComponentDirective } from 'app/site/base/base-view';
import { ChatNotificationService } from 'app/site/chat/services/chat-notification.service'; import { ChatNotificationService } from 'app/site/chat/services/chat-notification.service';
import { ViewGroup } from 'app/site/users/models/view-group';
import { import {
ChatGroupData, ChatGroupData,
EditChatGroupDialogComponent EditChatGroupDialogComponent
@ -51,6 +52,12 @@ export class ChatGroupDetailComponent extends BaseViewComponentDirective impleme
return isOnBottom; return isOnBottom;
} }
public get readOnlyGroups(): ViewGroup[] {
const readGroups = this.chatGroup?.read_groups;
const writeGrous = this.chatGroup?.write_groups;
return readGroups?.filter(group => !writeGrous.includes(group)) || [];
}
public constructor( public constructor(
titleService: Title, titleService: Title,
protected translate: TranslateService, protected translate: TranslateService,
@ -111,7 +118,8 @@ export class ChatGroupDetailComponent extends BaseViewComponentDirective impleme
public editChat(): void { public editChat(): void {
const chatData: ChatGroupData = { const chatData: ChatGroupData = {
name: this.chatGroup.name, name: this.chatGroup.name,
access_groups_id: this.chatGroup.access_groups_id read_groups_id: this.chatGroup.read_groups_id,
write_groups_id: this.chatGroup.write_groups_id
}; };
const dialogRef = this.dialog.open(EditChatGroupDialogComponent, { const dialogRef = this.dialog.open(EditChatGroupDialogComponent, {

View File

@ -4,8 +4,12 @@
</div> </div>
<div <div
class="chat-text" class="chat-text"
[ngClass]="{ 'background-primary': !isOwnMessage, 'background-primary-darkest': isOwnMessage }" [ngClass]="{
[matMenuTriggerFor]="canDelete ? singleChatMenu : null" disabled: !canDelete,
'background-primary': !isOwnMessage,
'background-primary-darkest': isOwnMessage
}"
[matMenuTriggerFor]="singleChatMenu"
> >
{{ text }} {{ text }}
<div class="timestamp">{{ date | localizedDate }}</div> <div class="timestamp">{{ date | localizedDate }}</div>

View File

@ -1,9 +1,9 @@
<mat-tab-group (selectedTabChange)="selectedTabChange($event)" *ngIf="chatGroupsExist()"> <mat-tab-group (selectedTabChange)="selectedTabChange($event)" *ngIf="chatGroupsExist">
<mat-tab *ngFor="let chat of chatGroupSubject | async"> <mat-tab *ngFor="let chat of chatGroupSubject | async">
<ng-template mat-tab-label> <ng-template mat-tab-label>
<span <span
[matBadgeHidden]="!getNotidficationsForChatId(chat.id)" [matBadgeHidden]="!getNotificationsForChatId(chat.id)"
[matBadge]="getNotidficationsForChatId(chat.id)" [matBadge]="getNotificationsForChatId(chat.id)"
matBadgeColor="accent" matBadgeColor="accent"
matBadgeOverlap="false" matBadgeOverlap="false"
> >
@ -16,14 +16,14 @@
</mat-tab> </mat-tab>
</mat-tab-group> </mat-tab-group>
<div *ngIf="!chatGroupsExist()"> <div *ngIf="!chatGroupsExist">
<span> <span>
{{ 'No chat groups available' | translate }} {{ 'No chat groups available' | translate }}
</span> </span>
</div> </div>
<!-- send chat --> <!-- send chat -->
<form [formGroup]="newMessageForm" (ngSubmit)="send()" *ngIf="chatGroupsExist()"> <form [formGroup]="newMessageForm" (ngSubmit)="send()" *ngIf="chatGroupsExist && canSendInSelectedChat" [@collapse]>
<mat-form-field appearance="outline" class="chat-form-field"> <mat-form-field appearance="outline" class="chat-form-field">
<input class="chat-input" type="text" matInput formControlName="text" /> <input class="chat-input" type="text" matInput formControlName="text" />
<mat-label>{{ 'Message' | translate }}</mat-label> <mat-label>{{ 'Message' | translate }}</mat-label>

View File

@ -7,8 +7,10 @@ import { Title } from '@angular/platform-browser';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { OperatorService } from 'app/core/core-services/operator.service';
import { ChatGroupRepositoryService } from 'app/core/repositories/chat/chat-group-repository.service'; import { ChatGroupRepositoryService } from 'app/core/repositories/chat/chat-group-repository.service';
import { ChatMessageRepositoryService } from 'app/core/repositories/chat/chat-message-repository.service'; import { ChatMessageRepositoryService } from 'app/core/repositories/chat/chat-message-repository.service';
import { collapseAndFade } from 'app/shared/animations';
import { ChatMessage } from 'app/shared/models/chat/chat-message'; import { ChatMessage } from 'app/shared/models/chat/chat-message';
import { BaseViewComponentDirective } from 'app/site/base/base-view'; import { BaseViewComponentDirective } from 'app/site/base/base-view';
import { ChatNotificationService, NotificationAmount } from '../../services/chat-notification.service'; import { ChatNotificationService, NotificationAmount } from '../../services/chat-notification.service';
@ -18,7 +20,8 @@ import { ViewChatGroup } from '../../models/view-chat-group';
selector: 'os-chat-tabs', selector: 'os-chat-tabs',
templateUrl: './chat-tabs.component.html', templateUrl: './chat-tabs.component.html',
styleUrls: ['./chat-tabs.component.scss'], styleUrls: ['./chat-tabs.component.scss'],
encapsulation: ViewEncapsulation.None encapsulation: ViewEncapsulation.None,
animations: [collapseAndFade]
}) })
export class ChatTabsComponent extends BaseViewComponentDirective implements OnInit { export class ChatTabsComponent extends BaseViewComponentDirective implements OnInit {
public chatGroupSubject: BehaviorSubject<ViewChatGroup[]>; public chatGroupSubject: BehaviorSubject<ViewChatGroup[]>;
@ -27,6 +30,21 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
private notifications: NotificationAmount; private notifications: NotificationAmount;
private get chatGroupFromIndex(): ViewChatGroup {
return this.chatGroupSubject.value[this.selectedTabIndex];
}
public get chatGroupsExist(): boolean {
return this.chatGroupSubject.value.length > 0;
}
public get canSendInSelectedChat(): boolean {
if (!this.chatGroupFromIndex) {
return false;
}
return this.operator.isInGroupIds(...this.chatGroupFromIndex?.write_groups_id) || false;
}
public constructor( public constructor(
titleService: Title, titleService: Title,
translate: TranslateService, translate: TranslateService,
@ -34,6 +52,7 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
private repo: ChatGroupRepositoryService, private repo: ChatGroupRepositoryService,
private chatMessageRepo: ChatMessageRepositoryService, private chatMessageRepo: ChatMessageRepositoryService,
private chatNotificationService: ChatNotificationService, private chatNotificationService: ChatNotificationService,
private operator: OperatorService,
formBuilder: FormBuilder formBuilder: FormBuilder
) { ) {
super(titleService, translate, matSnackBar); super(titleService, translate, matSnackBar);
@ -55,14 +74,10 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
this.selectedTabIndex = event.index; this.selectedTabIndex = event.index;
} }
public getNotidficationsForChatId(chatId: number): number { public getNotificationsForChatId(chatId: number): number {
return this.notifications?.[chatId] ?? 0; return this.notifications?.[chatId] ?? 0;
} }
public chatGroupsExist(): boolean {
return this.chatGroupSubject.value.length > 0;
}
public isChatMessageEmpty(): boolean { public isChatMessageEmpty(): boolean {
return !this.newMessageForm?.value?.text?.trim(); return !this.newMessageForm?.value?.text?.trim();
} }
@ -70,7 +85,7 @@ export class ChatTabsComponent extends BaseViewComponentDirective implements OnI
public send(): void { public send(): void {
const payload = { const payload = {
text: this.newMessageForm.value.text, text: this.newMessageForm.value.text,
chatgroup_id: this.chatGroupSubject.value[this.selectedTabIndex].id chatgroup_id: this.chatGroupFromIndex.id
}; };
this.chatMessageRepo this.chatMessageRepo
.create(payload as ChatMessage) .create(payload as ChatMessage)

View File

@ -24,9 +24,17 @@
<!-- Groups --> <!-- Groups -->
<mat-form-field> <mat-form-field>
<os-search-value-selector <os-search-value-selector
formControlName="access_groups_id" formControlName="read_groups_id"
[multiple]="true" [multiple]="true"
placeholder="{{ 'Access groups' | translate }}" placeholder="{{ 'Groups with read permissions' | translate }}"
[inputListValues]="groupsBehaviorSubject"
></os-search-value-selector>
</mat-form-field>
<mat-form-field>
<os-search-value-selector
formControlName="write_groups_id"
[multiple]="true"
placeholder="{{ 'Groups with write permissions' | translate }}"
[inputListValues]="groupsBehaviorSubject" [inputListValues]="groupsBehaviorSubject"
></os-search-value-selector> ></os-search-value-selector>
</mat-form-field> </mat-form-field>

View File

@ -10,7 +10,8 @@ import { ViewGroup } from 'app/site/users/models/view-group';
export interface ChatGroupData { export interface ChatGroupData {
name: string; name: string;
access_groups_id: number[]; read_groups_id: number[];
write_groups_id: number[];
} }
@Component({ @Component({
@ -36,7 +37,8 @@ export class EditChatGroupDialogComponent {
this.createMode = !data; this.createMode = !data;
this.createUpdateForm = formBuilder.group({ this.createUpdateForm = formBuilder.group({
name: [data?.name || '', Validators.required], name: [data?.name || '', Validators.required],
access_groups_id: [data?.access_groups_id || []] read_groups_id: [data?.read_groups_id || []],
write_groups_id: [data?.write_groups_id || []]
}); });
this.groupsBehaviorSubject = groupRepo.getViewModelListBehaviorSubject(); this.groupsBehaviorSubject = groupRepo.getViewModelListBehaviorSubject();
} }

View File

@ -15,5 +15,6 @@ export class ViewChatGroup extends BaseViewModel<ChatGroup> implements ChatGroup
} }
} }
export interface ViewChatGroup extends ChatGroup { export interface ViewChatGroup extends ChatGroup {
access_groups: ViewGroup[]; read_groups: ViewGroup[];
write_groups: ViewGroup[];
} }

View File

@ -18,9 +18,9 @@ export class ChatService {
private canSeeSomeChatGroup = false; private canSeeSomeChatGroup = false;
private canManage = false; private canManage = false;
private canSeeChat = new BehaviorSubject<boolean>(false); private canSeeChatSubject = new BehaviorSubject<boolean>(false);
public get canSeeChatObservable(): Observable<boolean> { public get canSeeChatObservable(): Observable<boolean> {
return this.canSeeChat.asObservable(); return this.canSeeChatSubject.asObservable();
} }
public constructor( public constructor(
@ -34,7 +34,7 @@ export class ChatService {
}); });
this.repo.getViewModelListBehaviorSubject().subscribe(groups => { this.repo.getViewModelListBehaviorSubject().subscribe(groups => {
this.canSeeSomeChatGroup = !!groups && groups.length > 0; this.canSeeSomeChatGroup = groups?.length > 0;
this.update(); this.update();
}); });
@ -45,6 +45,6 @@ export class ChatService {
} }
private update(): void { private update(): void {
this.canSeeChat.next(this.chatEnabled && (this.canSeeSomeChatGroup || this.canManage)); this.canSeeChatSubject.next(this.chatEnabled && (this.canSeeSomeChatGroup || this.canManage));
} }
} }

View File

@ -18,6 +18,7 @@ services:
- ../server:/app - ../server:/app
depends_on: depends_on:
- redis - redis
- postgres
postgres: postgres:
image: postgres:11 image: postgres:11

View File

@ -19,12 +19,7 @@ function isSettingsFileOk() {
return 0; return 0;
} }
wait-for-it -t 0 redis:6379 /app/docker/wait-for-dev-dependencies.sh
until pg_isready -h postgres -p 5432 -U openslides; do
echo "Waiting for Postgres to become available..."
sleep 3
done
if [[ ! -f "/app/personal_data/var/settings.py" ]]; then if [[ ! -f "/app/personal_data/var/settings.py" ]]; then
echo "Create settings" echo "Create settings"

View File

@ -0,0 +1,10 @@
#!/bin/bash
set -e
wait-for-it -t 0 redis:6379
until pg_isready -h postgres -p 5432 -U openslides; do
echo "Waiting for Postgres to become available..."
sleep 3
done

0
server/manage.py Executable file → Normal file
View File

View File

@ -7,7 +7,7 @@ from openslides.utils.auth import async_has_perm, async_in_some_groups
class ChatGroupAccessPermissions(BaseAccessPermissions): class ChatGroupAccessPermissions(BaseAccessPermissions):
""" """
Access permissions container for ChatGroup and ChatGroupViewSet. Access permissions container for ChatGroup and ChatGroupViewSet.
No base perm: The access permissions are done with the access groups No base perm: The access permissions are done with the read/write groups.
""" """
async def get_restricted_data( async def get_restricted_data(
@ -22,10 +22,11 @@ class ChatGroupAccessPermissions(BaseAccessPermissions):
data = full_data data = full_data
else: else:
for full in full_data: for full in full_data:
access_groups = full.get("access_groups_id", []) read_groups = full.get("read_groups_id", [])
if len( write_groups = full.get("write_groups_id", [])
full.get("access_groups_id", []) if await async_in_some_groups(
) == 0 or await async_in_some_groups(user_id, access_groups): user_id, read_groups
) or await async_in_some_groups(user_id, write_groups):
data.append(full) data.append(full)
return data return data

View File

@ -0,0 +1,33 @@
# Generated by Finn Stutzenstein on 2021-02-18 08:12
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
("chat", "0001_initial"),
("users", "0016_remove_user_ordering"),
]
operations = [
migrations.RenameField(
model_name="chatgroup",
old_name="access_groups",
new_name="read_groups",
),
migrations.AlterField(
model_name="chatgroup",
name="read_groups",
field=models.ManyToManyField(
blank=True, related_name="chat_read_groups", to="users.Group"
),
),
migrations.AddField(
model_name="chatgroup",
name="write_groups",
field=models.ManyToManyField(
blank=True, related_name="chat_write_groups", to="users.Group"
),
),
]

View File

@ -0,0 +1,19 @@
# Generated by Finn Stutzenstein on 2021-02-18 08:12
from django.db import migrations
def copy_read_groups_to_write_groups(apps, schema_editor):
ChatGroup = apps.get_model("chat", "ChatGroup")
for chatgroup in ChatGroup.objects.all():
chatgroup.write_groups.add(*chatgroup.read_groups.all())
class Migration(migrations.Migration):
dependencies = [
("chat", "0002_new_access_groups_1"),
]
operations = [migrations.RunPython(copy_read_groups_to_write_groups)]

View File

@ -18,7 +18,7 @@ class ChatGroupManager(BaseManager):
return ( return (
super() super()
.get_prefetched_queryset(*args, **kwargs) .get_prefetched_queryset(*args, **kwargs)
.prefetch_related("access_groups") .prefetch_related("read_groups", "write_groups")
) )
@ -30,8 +30,11 @@ class ChatGroup(RESTModelMixin, models.Model):
objects = ChatGroupManager() objects = ChatGroupManager()
name = models.CharField(max_length=256) name = models.CharField(max_length=256)
access_groups = models.ManyToManyField( read_groups = models.ManyToManyField(
settings.AUTH_GROUP_MODEL, blank=True, related_name="chat_access_groups" settings.AUTH_GROUP_MODEL, blank=True, related_name="chat_read_groups"
)
write_groups = models.ManyToManyField(
settings.AUTH_GROUP_MODEL, blank=True, related_name="chat_write_groups"
) )
class Meta: class Meta:
@ -41,14 +44,11 @@ class ChatGroup(RESTModelMixin, models.Model):
def __str__(self): def __str__(self):
return self.name return self.name
def can_access(self, user): def can_write(self, user):
if has_perm(user.id, "chat.can_manage"): if has_perm(user.id, "chat.can_manage"):
return True return True
if not self.access_groups.exists(): return in_some_groups(user.id, self.write_groups.values_list(flat=True))
return True
return in_some_groups(user.id, self.access_groups.values_list(flat=True))
class ChatMessageManager(BaseManager): class ChatMessageManager(BaseManager):
@ -61,7 +61,9 @@ class ChatMessageManager(BaseManager):
return ( return (
super() super()
.get_prefetched_queryset(*args, **kwargs) .get_prefetched_queryset(*args, **kwargs)
.prefetch_related("chatgroup", "chatgroup__access_groups") .prefetch_related(
"chatgroup", "chatgroup__read_groups", "chatgroup__write_groups"
)
) )

View File

@ -14,7 +14,10 @@ class ChatGroupSerializer(ModelSerializer):
Serializer for chat.models.ChatGroup objects. Serializer for chat.models.ChatGroup objects.
""" """
access_groups = IdPrimaryKeyRelatedField( read_groups = IdPrimaryKeyRelatedField(
many=True, required=False, queryset=get_group_model().objects.all()
)
write_groups = IdPrimaryKeyRelatedField(
many=True, required=False, queryset=get_group_model().objects.all() many=True, required=False, queryset=get_group_model().objects.all()
) )
@ -23,7 +26,8 @@ class ChatGroupSerializer(ModelSerializer):
fields = ( fields = (
"id", "id",
"name", "name",
"access_groups", "read_groups",
"write_groups",
) )
@ -35,7 +39,8 @@ class ChatMessageSerializer(ModelSerializer):
chatgroup = IdPrimaryKeyRelatedField( chatgroup = IdPrimaryKeyRelatedField(
required=False, queryset=ChatGroup.objects.all() required=False, queryset=ChatGroup.objects.all()
) )
access_groups_id = SerializerMethodField() read_groups_id = SerializerMethodField()
write_groups_id = SerializerMethodField()
class Meta: class Meta:
model = ChatMessage model = ChatMessage
@ -46,7 +51,8 @@ class ChatMessageSerializer(ModelSerializer):
"timestamp", "timestamp",
"username", "username",
"user_id", "user_id",
"access_groups_id", "read_groups_id",
"write_groups_id",
) )
read_only_fields = ( read_only_fields = (
"username", "username",
@ -58,5 +64,8 @@ class ChatMessageSerializer(ModelSerializer):
data["text"] = validate_html_strict(data["text"]) data["text"] = validate_html_strict(data["text"])
return data return data
def get_access_groups_id(self, chatmessage): def get_read_groups_id(self, chatmessage):
return [group.id for group in chatmessage.chatgroup.access_groups.all()] return [group.id for group in chatmessage.chatgroup.read_groups.all()]
def get_write_groups_id(self, chatmessage):
return [group.id for group in chatmessage.chatgroup.write_groups.all()]

View File

@ -51,8 +51,8 @@ class ChatGroupViewSet(ModelViewSet):
def update(self, *args, **kwargs): def update(self, *args, **kwargs):
response = super().update(*args, **kwargs) response = super().update(*args, **kwargs)
# Update all affected chatmessages to update their `access_groups_id` field, # Update all affected chatmessages to update their `read_groups_id` and
# which is taken from the updated chatgroup. # `write_groups_id` field, which is taken from the updated chatgroup.
inform_changed_data(ChatMessage.objects.filter(chatgroup=self.get_object())) inform_changed_data(ChatMessage.objects.filter(chatgroup=self.get_object()))
return response return response
@ -93,7 +93,7 @@ class ChatMessageViewSet(
serializer = self.get_serializer(data=request.data) serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True) serializer.is_valid(raise_exception=True)
if not serializer.validated_data["chatgroup"].can_access(self.request.user): if not serializer.validated_data["chatgroup"].can_write(self.request.user):
self.permission_denied(self.request) self.permission_denied(self.request)
# Do not use the serializer.save since it will put the model in the history. # Do not use the serializer.save since it will put the model in the history.

View File

@ -22,6 +22,7 @@ from django.http.request import QueryDict
from django.utils.encoding import force_bytes, force_text from django.utils.encoding import force_bytes, force_text
from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode from django.utils.http import urlsafe_base64_decode, urlsafe_base64_encode
from openslides.chat.models import ChatGroup
from openslides.saml import SAML_ENABLED from openslides.saml import SAML_ENABLED
from openslides.utils import logging from openslides.utils import logging
@ -229,6 +230,7 @@ class UserViewSet(ModelViewSet):
old_delegation_user.save() old_delegation_user.save()
inform_changed_data(user) inform_changed_data(user)
inform_changed_data(ChatGroup.objects.all())
return response return response
def assert_vote_not_delegated(self, user): def assert_vote_not_delegated(self, user):

View File

@ -10,19 +10,24 @@ def test_motion_db_queries():
""" """
Tests that only the following db queries for chat groups are done: Tests that only the following db queries for chat groups are done:
* 1 request to get all chat groups * 1 request to get all chat groups
* 1 request to get all access groups * 1 request to get all read groups
* 1 request to get all write groups
Tests that only the following db queries for chat messages are done: Tests that only the following db queries for chat messages are done:
* 1 request to fet all chat messages * 1 request to fet all chat messages
* 1 request to get all chat groups * 1 request to get all chat groups
* 1 request to get all access groups * 1 request to get all read groups
* 1 request to get all write groups
""" """
group1 = get_group_model().objects.create(name="group1") group1 = get_group_model().objects.create(name="group1")
group2 = get_group_model().objects.create(name="group2") group2 = get_group_model().objects.create(name="group2")
group3 = get_group_model().objects.create(name="group3")
group4 = get_group_model().objects.create(name="group4")
for i1 in range(5): for i1 in range(5):
chatgroup = ChatGroup.objects.create(name=f"motion{i1}") chatgroup = ChatGroup.objects.create(name=f"motion{i1}")
chatgroup.access_groups.add(group1, group2) chatgroup.read_groups.add(group1, group2)
chatgroup.write_groups.add(group3, group4)
for i2 in range(10): for i2 in range(10):
ChatMessage.objects.create( ChatMessage.objects.create(
@ -32,5 +37,5 @@ def test_motion_db_queries():
chatgroup=chatgroup, chatgroup=chatgroup,
) )
assert count_queries(ChatGroup.get_elements)() == 2 assert count_queries(ChatGroup.get_elements)() == 3
assert count_queries(ChatMessage.get_elements)() == 3 assert count_queries(ChatMessage.get_elements)() == 4