Skip to content

Commit

Permalink
feat(management v2 open api): support list_subject_template_of_group …
Browse files Browse the repository at this point in the history
…and query_group_member_detail (#2741)
  • Loading branch information
nannan00 authored Jul 1, 2024
1 parent b538cf7 commit 46bb048
Show file tree
Hide file tree
Showing 9 changed files with 281 additions and 8 deletions.
2 changes: 1 addition & 1 deletion saas/.editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ charset = utf-8

# Docstrings and comments use max_line_length = 79
[*.py]
max_line_length = 88
max_line_length = 119

# Use 2 spaces for the HTML files
[*.html]
Expand Down
4 changes: 4 additions & 0 deletions saas/backend/api/management/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ class ManagementAPIEnum(BaseAPIEnum):
V2_GROUP_MEMBER_ADD = auto()
V2_GROUP_MEMBER_DELETE = auto()
V2_GROUP_MEMBER_EXPIRED_AT_UPDATE = auto()
V2_GROUP_SUBJECT_TEMPLATE_LIST = auto()
# 用户组权限
V2_GROUP_POLICY_GRANT = auto()
V2_GROUP_POLICY_REVOKE = auto()
Expand All @@ -66,6 +67,7 @@ class ManagementAPIEnum(BaseAPIEnum):
# 用户组归属
V2_USER_GROUPS_BELONG_CHECK = auto()
V2_DEPARTMENT_GROUPS_BELONG_CHECK = auto()
V2_MEMBER_GROUPS_DETAIL_LIST = auto()
# 分级管理员
V2_GRADE_MANAGER_CREATE = auto()
V2_GRADE_MANAGER_UPDATE = auto()
Expand Down Expand Up @@ -123,6 +125,7 @@ class ManagementAPIEnum(BaseAPIEnum):
(V2_GROUP_MEMBER_ADD, "[V2]添加用户组成员"),
(V2_GROUP_MEMBER_DELETE, "[V2]删除用户组成员"),
(V2_GROUP_MEMBER_EXPIRED_AT_UPDATE, "[V2]用户组成员续期"),
(V2_GROUP_SUBJECT_TEMPLATE_LIST, "[V2]获取用户组人员模板列表"),
# 用户组权限
(V2_GROUP_POLICY_GRANT, "[V2]授权用户组"),
(V2_GROUP_POLICY_REVOKE, "[V2]回收用户组权限"),
Expand All @@ -134,6 +137,7 @@ class ManagementAPIEnum(BaseAPIEnum):
# 用户组归属
(V2_USER_GROUPS_BELONG_CHECK, "[V2]判断用户与用户组归属"),
(V2_DEPARTMENT_GROUPS_BELONG_CHECK, "[V2]判断部门与用户组归属"),
(V2_MEMBER_GROUPS_DETAIL_LIST, "[V2]用户组成员在组内的详情"),
# 分级管理员
(V2_GRADE_MANAGER_DETAIL, "[V2]分级管理员详情"),
(V2_GRADE_MANAGER_CREATE, "[V2]新建分级管理员"),
Expand Down
57 changes: 57 additions & 0 deletions saas/backend/api/management/v2/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from backend.biz.subject_template import SubjectTemplateBiz
from backend.service.constants import GroupMemberType
from backend.service.models import Subject
from backend.util.serializer import StringArrayField


class ManagementSourceSystemSLZ(serializers.Serializer):
Expand Down Expand Up @@ -189,6 +190,15 @@ def validate(self, data):
return data


class ManagementGroupSubjectTemplateSLZ(serializers.Serializer):
id = serializers.CharField(label="人员模板 ID", source="template_id")
name = serializers.SerializerMethodField(label="人员模板名称")
expired_at = serializers.IntegerField(label="过期时间戳(单位秒)")

def get_name(self, obj):
return self.context["subject_template_name_map"].get(obj.template_id) or obj.template_id


class ManagementGradeManagerBasicSLZ(serializers.Serializer):
id = serializers.IntegerField(label="分级管理员ID")

Expand Down Expand Up @@ -252,6 +262,53 @@ class ManagementSubjectGroupBelongSLZ(serializers.Serializer):
group_ids = serializers.CharField(label="用户组ID,多个以英文逗号分隔", max_length=255, required=True)


class ManagementMemberGroupDetailInputInPathSLZ(serializers.Serializer):
group_member_type = serializers.ChoiceField(help_text="用户组成员类型", choices=GroupMemberType.get_choices())
member_id = serializers.CharField(help_text="用户组成员 ID", max_length=128)

def validate(self, attrs):
# 组织 ID 和 人员模板 ID 都必须是整数
group_member_type = attrs["group_member_type"]
member_id = attrs["member_id"]
if group_member_type in (GroupMemberType.DEPARTMENT.value, GroupMemberType.TEMPLATE.value):
try:
int(member_id)
except ValueError:
raise serializers.ValidationError(
{
"member_id": [f"当 group_member_type 为 {group_member_type} 时,member_id({member_id}) 必须为整数"],
}
)

return attrs


class ManagementMemberGroupDetailInputSLZ(serializers.Serializer):
group_ids = StringArrayField(help_text="用户组 ID,多个以英文逗号分隔", min_items=1, max_items=20)

def validate_group_ids(self, value):
try:
[int(i) for i in value]
except ValueError:
raise serializers.ValidationError({"group_ids": ["用户组 ID 必须为整数"]})

return value


class ManagementMemberGroupDetailOutputSLZ(serializers.Serializer):
id = serializers.IntegerField(help_text="用户组 ID")
name = serializers.CharField(help_text="用户组名称")
description = serializers.CharField(help_text="用户组描述")
created_at = serializers.SerializerMethodField(help_text="加入用户组时间戳")
expired_at = serializers.SerializerMethodField(help_text="过期时间戳")

def get_created_at(self, obj):
return self.context["subject_group_map"][obj.id]["created_at"]

def get_expired_at(self, obj):
return self.context["subject_group_map"][obj.id]["expired_at"]


class ManagementGradeManagerApplicationResultSLZ(serializers.Serializer):
id = serializers.CharField(label="申请单据ID")
sn = serializers.CharField(label="ITSM审批单SN")
Expand Down
22 changes: 17 additions & 5 deletions saas/backend/api/management/v2/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,37 +26,43 @@
name="open.management.v2.grade_manager",
),
# -------------- 用户组本身 --------------
# 系统管理员下创建用户组
# 系统管理员下创建用户组(支持同步创建人员模板)
path(
"grade_managers/-/groups/",
views.ManagementSystemManagerGroupViewSet.as_view({"post": "create"}),
name="open.management.v2.system_manager_group",
),
# 分级管理员下创建用户组
# 分级管理员下创建用户组(支持同步创建用户组对应的人员模板)
path(
"grade_managers/<int:id>/groups/",
views.ManagementGradeManagerGroupViewSet.as_view({"post": "create", "get": "list"}),
name="open.management.v2.grade_manager_group",
),
# 用户组基本信息更新 & 删除
# 用户组基本信息更新 & 删除(自动同步删除用户组人员模板)
path(
"groups/<int:id>/",
views.ManagementGroupViewSet.as_view({"put": "update", "delete": "destroy"}),
name="open.management.v2.group",
),
# -------------- 用户组成员 --------------
# 用户组成员
# 用户组成员(增加和删除支持人员模板,查询列表不支持人员模板)
path(
"groups/<int:id>/members/",
views.ManagementGroupMemberViewSet.as_view({"get": "list", "post": "create", "delete": "destroy"}),
name="open.management.v2.group_member",
),
# 用户组成员有效期
# 用户组成员有效期(不支持人员模板)
path(
"groups/<int:id>/members/-/expired_at/",
views.ManagementGroupMemberExpiredAtViewSet.as_view({"put": "update"}),
name="open.management.v2.group_member.expired_at",
),
# 用户组下类型为人员模板的成员
path(
"groups/<int:id>/subject_templates/",
views.ManagementGroupSubjectTemplateViewSet.as_view({"get": "list"}),
name="open.management.v2.group_subject_template",
),
# -------------- 用户组权限 --------------
# 用户组自定义权限
path(
Expand Down Expand Up @@ -112,6 +118,12 @@
views.ManagementDepartmentGroupBelongViewSet.as_view({"get": "check"}),
name="open.management.v2.department_group_belong",
),
# 批量查询某个成员在一批用户组里的详情(支持人员模板)
path(
"group_member_types/<str:group_member_type>/members/<str:member_id>/groups/-/details/",
views.ManagementMemberGroupDetailViewSet.as_view({"get": "list"}),
name="open.management.v2.member_group_detail",
),
# -------------- Approval --------------
# 审批回调
path(
Expand Down
9 changes: 8 additions & 1 deletion saas/backend/api/management/v2/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,15 @@
ManagementGroupMemberViewSet,
ManagementGroupPolicyActionViewSet,
ManagementGroupPolicyViewSet,
ManagementGroupSubjectTemplateViewSet,
ManagementGroupViewSet,
ManagementSystemManagerGroupViewSet,
)
from .subject import ManagementDepartmentGroupBelongViewSet, ManagementUserGroupBelongViewSet
from .subject import (
ManagementDepartmentGroupBelongViewSet,
ManagementMemberGroupDetailViewSet,
ManagementUserGroupBelongViewSet,
)
from .subject_template import ManagementGradeManagerSubjectTemplateViewSet
from .subset_manager import ManagementSubsetManagerCreateListViewSet, ManagementSubsetManagerViewSet

Expand All @@ -37,6 +42,7 @@
"ManagementGroupViewSet",
"ManagementGroupMemberViewSet",
"ManagementGroupMemberExpiredAtViewSet",
"ManagementGroupSubjectTemplateViewSet",
"ManagementGroupPolicyViewSet",
"ManagementGroupApplicationViewSet",
"ManagementGroupRenewApplicationViewSet",
Expand All @@ -52,4 +58,5 @@
"ManagementGradeManagerViewSet",
"ManagementSubsetManagerViewSet",
"ManagementGradeManagerSubjectTemplateViewSet",
"ManagementMemberGroupDetailViewSet",
]
46 changes: 46 additions & 0 deletions saas/backend/api/management/v2/views/group.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
ManagementGroupPolicyDeleteSLZ,
ManagementGroupRevokeSLZ,
ManagementGroupSLZ,
ManagementGroupSubjectTemplateSLZ,
ManagementQueryGroupSLZ,
)
from backend.apps.group.audit import (
Expand All @@ -49,6 +50,7 @@
from backend.apps.policy.models import Policy
from backend.apps.policy.serializers import PolicySLZ
from backend.apps.role.models import Role
from backend.apps.subject_template.models import SubjectTemplate, SubjectTemplateGroup
from backend.audit.audit import add_audit, audit_context_setter, view_audit_decorator
from backend.audit.constants import AuditSourceType
from backend.biz.group import (
Expand Down Expand Up @@ -481,6 +483,50 @@ def update(self, request, *args, **kwargs):
return Response({})


class ManagementGroupSubjectTemplateViewSet(GenericViewSet):
"""用户组类型为人员模板的成员"""

authentication_classes = [ESBAuthentication]
permission_classes = [ManagementAPIPermission]

management_api_permission = {
"list": (
VerifyApiParamLocationEnum.GROUP_IN_PATH.value,
ManagementAPIEnum.V2_GROUP_SUBJECT_TEMPLATE_LIST.value,
)
}

lookup_field = "id"
queryset = Group.objects.all()
pagination_class = CompatiblePagination

@swagger_auto_schema(
operation_description="用户组人员模板成员列表",
responses={status.HTTP_200_OK: ManagementGroupMemberSLZ(label="用户组人员模板成员信息", many=True)},
tags=["management.role.group.subject_template"],
)
def list(self, request, *args, **kwargs):
group = self.get_object()

# 查询用户组拥有的权限模板
queryset = SubjectTemplateGroup.objects.filter(group_id=group.id)

# 查询模板名称
subject_template_ids = list(queryset.values_list("template_id", flat=True))
subject_template_name_map = dict(
SubjectTemplate.objects.filter(id__in=subject_template_ids).values_list("id", "name")
)

# 分页
page = self.paginate_queryset(queryset)
assert page is not None

serializer = ManagementGroupSubjectTemplateSLZ(
page, many=True, context={"subject_template_name_map": subject_template_name_map}
)
return self.get_paginated_response(serializer.data)


class ManagementGroupPolicyViewSet(GenericViewSet):
"""用户组权限 - 自定义权限"""

Expand Down
90 changes: 89 additions & 1 deletion saas/backend/api/management/v2/views/subject.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
specific language governing permissions and limitations under the License.
"""
from typing import Dict, List

from drf_yasg.utils import swagger_auto_schema
from rest_framework import serializers, status
from rest_framework.response import Response
Expand All @@ -16,10 +18,20 @@
from backend.api.authentication import ESBAuthentication
from backend.api.management.constants import ManagementAPIEnum, VerifyApiParamLocationEnum
from backend.api.management.v2.permissions import ManagementAPIPermission
from backend.api.management.v2.serializers import ManagementSubjectGroupBelongSLZ
from backend.api.management.v2.serializers import (
ManagementMemberGroupDetailInputInPathSLZ,
ManagementMemberGroupDetailInputSLZ,
ManagementMemberGroupDetailOutputSLZ,
ManagementSubjectGroupBelongSLZ,
)
from backend.apps.group.models import Group
from backend.apps.subject_template.models import SubjectTemplateGroup
from backend.biz.group import GroupBiz
from backend.common.error_codes import error_codes
from backend.component.iam import list_all_subject_groups
from backend.service.constants import GroupMemberType
from backend.service.models import Subject
from backend.util.time import utc_string_to_timestamp


class ManagementUserGroupBelongViewSet(GenericViewSet):
Expand Down Expand Up @@ -101,3 +113,79 @@ def check(self, request, *args, **kwargs):
)

return Response(group_belongs)


class ManagementMemberGroupDetailViewSet(GenericViewSet):
"""用户组成员在用户组内的详情"""

authentication_classes = [ESBAuthentication]
permission_classes = [ManagementAPIPermission]
management_api_permission = {
"list": (
VerifyApiParamLocationEnum.GROUP_IDS_IN_QUERY.value,
ManagementAPIEnum.V2_MEMBER_GROUPS_DETAIL_LIST.value,
),
}

pagination_class = None

@staticmethod
def _get_subject_group_dict(subject: Subject, group_ids: List[int]) -> Dict[int, Dict[str, int]]:
"""
从后台查询加入的用户组详情
"""
# Note: 可能有性能问题,部分用户加入的用户组可能过多,后续可考虑后台支持 group_ids 过滤查询
# 这里参考了 [web api] /api/v1/roles/group_members/<subject_type>/<subject_id>/groups/
groups = list_all_subject_groups(subject.type, subject.id)
return {
int(one["id"]): {
"expired_at": one["expired_at"],
"created_at": utc_string_to_timestamp(one["created_at"]),
}
for one in groups
if int(one["id"]) in group_ids
}

@swagger_auto_schema(
operation_description="用户组成员在用户组内的详情",
responses={status.HTTP_200_OK: ManagementMemberGroupDetailOutputSLZ(label="加入用户组的详细信息", many=True)},
tags=["management.role.group_member_detail"],
)
def list(self, request, *args, **kwargs):
# URL 路径参数 获取用户组成员类型和成员 ID
path_slz = ManagementMemberGroupDetailInputInPathSLZ(data=kwargs)
path_slz.is_valid(raise_exception=True)
path_data = path_slz.validated_data
group_member_type, member_id = path_data["group_member_type"], path_data["member_id"]

# 请求参数
slz = ManagementMemberGroupDetailInputSLZ(data=request.query_params)
slz.is_valid(raise_exception=True)
data = slz.validated_data
group_ids = list(map(int, data["group_ids"]))

# 查询成员在组里的详情:过期时间、加入的时间
subject_group_map = {}
if group_member_type in (GroupMemberType.DEPARTMENT.value, GroupMemberType.USER.value):
# 用户或组织
subject = Subject(type=group_member_type, id=member_id)
subject_group_map = self._get_subject_group_dict(subject, group_ids)
elif group_member_type == GroupMemberType.TEMPLATE.value:
# 人员模板
template_groups = SubjectTemplateGroup.objects.filter(template_id=int(member_id), group_id__in=group_ids)
subject_group_map = {
i.group_id: {
"expired_at": i.expired_at,
"created_at": int(i.created_time.timestamp()),
}
for i in template_groups
}

# 只返回存在关系的用户组
groups = Group.objects.filter(id__in=list(subject_group_map.keys()))

return Response(
ManagementMemberGroupDetailOutputSLZ(
groups, many=True, context={"subject_group_map": subject_group_map}
).data
)
Loading

0 comments on commit 46bb048

Please sign in to comment.