Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 43 additions & 37 deletions apps/users/api/user_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,22 @@ def get_response():
class DeleteUserGroupApi(APIMixin):
@staticmethod
def get_parameters():
return [OpenApiParameter(
name='workspace_id',
type=OpenApiTypes.STR,
description=_('Workspace ID'),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
return [
OpenApiParameter(
name="workspace_id",
type=OpenApiTypes.STR,
description=_("Workspace ID"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
OpenApiParameter(
name="user_group_id",
description=_("User Group ID"),
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH, # type: ignore
required=True,
)]
),
]

@staticmethod
def get_response():
Expand All @@ -60,12 +62,13 @@ def get_response():
def get_parameters():
return [
OpenApiParameter(
name='workspace_id',
name="workspace_id",
type=OpenApiTypes.STR,
description=_('Workspace ID'),
description=_("Workspace ID"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
)]
)
]


class UserGroupListPageResponse(ResultSerializer):
Expand All @@ -78,44 +81,51 @@ class UserGroupListPageApi(APIMixin):
def get_parameters():
return [
OpenApiParameter(
name='workspace_id',
name="workspace_id",
type=OpenApiTypes.STR,
description=_('Workspace ID'),
description=_("Workspace ID"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
OpenApiParameter(
name='user_group_id',
name="user_group_id",
type=OpenApiTypes.STR,
description=_('Group ID'),
description=_("Group ID"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
OpenApiParameter(
name='current_page',
name="current_page",
type=OpenApiTypes.INT,
description=_('Current page'),
description=_("Current page"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
OpenApiParameter(
name='page_size',
name="page_size",
type=OpenApiTypes.INT,
description=_('Page size'),
description=_("Page size"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
OpenApiParameter(
name='username',
name="username",
type=OpenApiTypes.STR,
description=_('Username'),
description=_("Username"),
required=False,
location=OpenApiParameter.QUERY, # type: ignore
),
OpenApiParameter(
name='nick_name',
name="nick_name",
type=OpenApiTypes.STR,
description=_('Nickname'),
description=_("Nickname"),
required=False,
location=OpenApiParameter.QUERY, # type: ignore
),
OpenApiParameter(
name="role",
type=OpenApiTypes.STR,
description=_("Role"),
required=False,
location=OpenApiParameter.QUERY, # type: ignore
),
Expand All @@ -127,21 +137,17 @@ def get_response():


class AddMemberRequest(serializers.Serializer):
user_ids = serializers.ListField(
child=serializers.CharField(required=True),
required=True,
label=_('User IDs')
)
user_ids = serializers.ListField(child=serializers.CharField(required=True), required=True, label=_("User IDs"))


class AddMemberApi(APIMixin):
@staticmethod
def get_parameters():
return [
OpenApiParameter(
name='workspace_id',
name="workspace_id",
type=OpenApiTypes.STR,
description=_('Workspace ID'),
description=_("Workspace ID"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
Expand All @@ -151,7 +157,8 @@ def get_parameters():
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH, # type: ignore
required=True,
)]
),
]

@staticmethod
def get_request():
Expand All @@ -164,9 +171,7 @@ def get_response():

class RemoveMemberRequest(serializers.Serializer):
group_relation_ids = serializers.ListField(
child=serializers.CharField(required=True),
required=True,
label=_('User group relation IDs')
child=serializers.CharField(required=True), required=True, label=_("User group relation IDs")
)


Expand All @@ -175,9 +180,9 @@ class RemoveMemberApi(APIMixin):
def get_parameters():
return [
OpenApiParameter(
name='workspace_id',
name="workspace_id",
type=OpenApiTypes.STR,
description=_('Workspace ID'),
description=_("Workspace ID"),
required=True,
location=OpenApiParameter.PATH, # type: ignore
),
Expand All @@ -187,7 +192,8 @@ def get_parameters():
type=OpenApiTypes.STR,
location=OpenApiParameter.PATH, # type: ignore
required=True,
)]
),
]

@staticmethod
def get_request():
Expand Down
12 changes: 12 additions & 0 deletions apps/users/serializers/user_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ class Query(serializers.Serializer):
group_id = serializers.CharField(required=True, label=_("Group ID"))
username = serializers.CharField(required=False, label=_("Username"), allow_null=True)
nick_name = serializers.CharField(required=False, label=_("Nick Name"), allow_null=True)
role = serializers.CharField(required=False, label=_("Role"), allow_null=True)
source = serializers.CharField(required=False, label=_("Source"), allow_null=True)

def is_valid(self, *, raise_exception=False):
Expand Down Expand Up @@ -272,13 +273,24 @@ def get_query_set(self):
group_id = self.data.get("group_id")
username = self.data.get("username")
nick_name = self.data.get("nick_name")
role = self.data.get("role")
source = self.data.get("source")
query_set = SystemUserGroupRelation.objects.filter(group_id=group_id).select_related("user")

if username is not None:
query_set = query_set.filter(user__username__contains=username)
if nick_name is not None:
query_set = query_set.filter(user__nick_name__contains=nick_name)
if role is not None:
user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
if user_role_relation_model is None:
return query_set.none()
role_user_ids = user_role_relation_model.objects.filter(
workspace_id=self.data.get("workspace_id"),
role__type=RoleConstants.USER.name,
role__role_name__icontains=role,
).values("user_id")
query_set = query_set.filter(user_id__in=role_user_ids)
if source is not None:
query_set = query_set.filter(user__source=source)
return query_set.order_by("-user__create_time")
53 changes: 53 additions & 0 deletions apps/users/tests/test_user_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,12 @@

from django.test import TestCase

from common.auth.constants.role_constants import RoleConstants
from common.database_model_manage.database_model_manage import DatabaseModelManage
from users.models import User
from users.models.user_group import SystemUserGroup
from users.models.user_group import SystemUserGroupRelation
from users.serializers.user_group import UserGroupListPageSerializer


class SystemUserGroupWorkspaceIsolationTests(TestCase):
Expand All @@ -17,3 +22,51 @@ def test_same_group_name_rejected_in_same_workspace(self):
SystemUserGroup.objects.create(id=uuid.uuid7(), name="test", workspace_id="default")
with self.assertRaises(Exception):
SystemUserGroup.objects.create(id=uuid.uuid7(), name="test", workspace_id="default")


class SystemUserGroupMemberRoleFilterTests(TestCase):
def test_filter_members_by_role(self):
DatabaseModelManage.init()
role_model = DatabaseModelManage.get_model("role_model")
user_role_relation_model = DatabaseModelManage.get_model("workspace_user_role_mapping")
if role_model is None or user_role_relation_model is None:
self.skipTest("Role search requires the enterprise role model")

group = SystemUserGroup.objects.create(name="test-group", workspace_id="workspace-1")
target_user = User.objects.create(
username="target-user",
nick_name="Target User",
password="encrypted-password",
role=RoleConstants.USER.name,
)
other_user = User.objects.create(
username="other-user",
nick_name="Other User",
password="encrypted-password",
role=RoleConstants.USER.name,
)
SystemUserGroupRelation.objects.create(user=target_user, group=group)
SystemUserGroupRelation.objects.create(user=other_user, group=group)

target_role = role_model.objects.create(role_name="知识库管理员", type=RoleConstants.USER.name)
other_role = role_model.objects.create(role_name="应用管理员", type=RoleConstants.USER.name)
user_role_relation_model.objects.create(
user=target_user,
role=target_role,
workspace_id=group.workspace_id,
)
user_role_relation_model.objects.create(
user=other_user,
role=other_role,
workspace_id=group.workspace_id,
)

result = UserGroupListPageSerializer.Query(
data={
"workspace_id": group.workspace_id,
"group_id": group.id,
"role": "知识库管理员",
}
).page(1, 20)

self.assertEqual(["target-user"], [member["username"] for member in result["records"]])
1 change: 1 addition & 0 deletions apps/users/views/system_user_group.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ def get(self, request: Request, workspace_id: str, user_group_id: str, current_p
data={
"username": request.query_params.get("username", None),
"nick_name": request.query_params.get("nick_name", None),
"role": request.query_params.get("role", None),
"source": request.query_params.get("source", None),
"group_id": user_group_id,
"workspace_id": workspace_id,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,21 +3,24 @@ import { computed, ref } from 'vue'
import UserGroupsApi from '@/api/admin/system/user-groups'
import { LOGIN_METHOD } from '@/api/enums'
import { LOGIN_METHOD_LABELS } from '@/constants'
import { useStore } from '@/stores'
import type { Dict, LoginMethod, OptionItem, SystemUserGroup, SystemUserGroupMember } from '@/api/types'

defineOptions({ name: 'UserGroupMembersDrawer' })

/* 成员查询与分页 */
const { auth } = useStore()
const drawerVisible = ref(false)
const currentGroup = ref<SystemUserGroup>()
const loadingMembers = ref(false)
const userGroupMembers = ref<SystemUserGroupMember[]>([])
const paginationConfig = ref({ currentPage: 1, pageSize: 20, total: 0 })
const memberSearchQuery = ref<Dict<unknown>>()
const memberSearchFields: OptionItem<string>[] = [
const memberSearchFields = computed<OptionItem<string>[]>(() => [
{ label: '姓名', value: 'nick_name' },
{ label: '用户名', value: 'username' },
]
...(auth.isEE || auth.isPE ? [{ label: '角色', value: 'role' }] : []),
])

/* 表头筛选 */
const selectedMemberSource = ref<string | null>(null)
Expand Down Expand Up @@ -115,7 +118,7 @@ defineExpose({ open })
{{ row.source === LOGIN_METHOD.LOCAL ? '系统用户' : LOGIN_METHOD_LABELS[row.source as LoginMethod] }}
</template>
</el-table-column>
<el-table-column label="角色">
<el-table-column v-if="auth.isEE || auth.isPE" label="角色">
<template #default="{ row }">
<MkTagGroup v-if="row.roles?.length" :tags="row.roles" />
<span v-else>-</span>
Expand Down
Loading