From 4ca2398b7429ba54e70f7ecaa1ba8d30a9e2b83c Mon Sep 17 00:00:00 2001 From: rolinchen Date: Fri, 17 Jan 2025 10:24:12 +0800 Subject: [PATCH 01/13] feat: added query department list --- .../apis/open_v3/serializers/department.py | 20 +++++ src/bk-user/bkuser/apis/open_v3/urls.py | 5 ++ .../bkuser/apis/open_v3/views/__init__.py | 3 +- .../bkuser/apis/open_v3/views/department.py | 89 +++++++++++++++++++ .../apidocs/en/list_department.md | 61 +++++++++++++ .../apidocs/zh/list_department.md | 60 +++++++++++++ src/bk-user/support-files/resources.yaml | 25 ++++++ .../tests/apis/open_v3/test_department.py | 45 ++++++++++ 8 files changed, 307 insertions(+), 1 deletion(-) create mode 100644 src/bk-user/support-files/apidocs/en/list_department.md create mode 100644 src/bk-user/support-files/apidocs/zh/list_department.md diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/department.py b/src/bk-user/bkuser/apis/open_v3/serializers/department.py index 34d501642..1089a50f8 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/department.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/department.py @@ -15,7 +15,11 @@ # We undertake not to change the open source license (MIT license) applicable # to the current version of the project delivered to anyone in the future. +from django.utils.translation import gettext_lazy as _ from rest_framework import serializers +from rest_framework.exceptions import ValidationError + +from bkuser.apps.tenant.models import TenantDepartment class AncestorSLZ(serializers.Serializer): @@ -34,3 +38,19 @@ class TenantDepartmentRetrieveOutputSLZ(serializers.Serializer): id = serializers.IntegerField(help_text="部门 ID") name = serializers.CharField(help_text="部门名称") ancestors = serializers.ListField(help_text="祖先部门列表", required=False, child=AncestorSLZ(), allow_empty=True) + + +class TenantDepartmentListInputSLZ(serializers.Serializer): + parent_id = serializers.IntegerField(help_text="父部门 ID", required=False) + + def validate_parent_id(self, parent_id: int) -> int: + if not TenantDepartment.objects.filter(id=parent_id, tenant_id=self.context["tenant_id"]).exists(): + raise ValidationError(_("指定的父部门在当前租户中不存在")) + + return parent_id + + +class TenantDepartmentListOutputSLZ(serializers.Serializer): + id = serializers.IntegerField(help_text="部门 ID") + name = serializers.CharField(help_text="部门名称") + parent_id = serializers.IntegerField(help_text="父部门 ID", allow_null=True) diff --git a/src/bk-user/bkuser/apis/open_v3/urls.py b/src/bk-user/bkuser/apis/open_v3/urls.py index 96d00cbbd..c26089330 100644 --- a/src/bk-user/bkuser/apis/open_v3/urls.py +++ b/src/bk-user/bkuser/apis/open_v3/urls.py @@ -50,6 +50,11 @@ views.TenantDepartmentRetrieveApi.as_view(), name="open_v3.tenant_department.retrieve", ), + path( + "departments/", + views.TenantDepartmentListApi.as_view(), + name="open_v3.tenant_department.list", + ), ] ), ), diff --git a/src/bk-user/bkuser/apis/open_v3/views/__init__.py b/src/bk-user/bkuser/apis/open_v3/views/__init__.py index 08ad36011..d4caee729 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/__init__.py +++ b/src/bk-user/bkuser/apis/open_v3/views/__init__.py @@ -14,7 +14,7 @@ # # We undertake not to change the open source license (MIT license) applicable # to the current version of the project delivered to anyone in the future. -from .department import TenantDepartmentRetrieveApi +from .department import TenantDepartmentListApi, TenantDepartmentRetrieveApi from .tenant import TenantListApi from .user import ( TenantUserDepartmentListApi, @@ -30,4 +30,5 @@ "TenantUserDepartmentListApi", "TenantUserLeaderListApi", "TenantDepartmentRetrieveApi", + "TenantDepartmentListApi", ] diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index b98ead919..ae93e8616 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -14,16 +14,21 @@ # # We undertake not to change the open source license (MIT license) applicable # to the current version of the project delivered to anyone in the future. +from typing import Any, Dict, List +from django.db.models import QuerySet from drf_yasg.utils import swagger_auto_schema from rest_framework import generics, status from rest_framework.response import Response from bkuser.apis.open_v3.mixins import OpenApiCommonMixin from bkuser.apis.open_v3.serializers.department import ( + TenantDepartmentListInputSLZ, + TenantDepartmentListOutputSLZ, TenantDepartmentRetrieveInputSLZ, TenantDepartmentRetrieveOutputSLZ, ) +from bkuser.apps.data_source.models import DataSourceDepartment from bkuser.apps.tenant.models import TenantDepartment from bkuser.biz.organization import DataSourceDepartmentHandler @@ -66,3 +71,87 @@ def get(self, request, *args, **kwargs): info["ancestors"] = [{"id": d.id, "name": d.data_source_department.name} for d in tenant_depts] return Response(TenantDepartmentRetrieveOutputSLZ(info).data) + + +class TenantDepartmentListApi(OpenApiCommonMixin, generics.ListAPIView): + """ + 获取部门列表 + """ + + serializer_class = TenantDepartmentListOutputSLZ + + @swagger_auto_schema( + tags=["open_v3.department"], + operation_id="list_department", + operation_description="查询部门列表", + query_serializer=TenantDepartmentListInputSLZ(), + responses={status.HTTP_200_OK: TenantDepartmentListOutputSLZ(many=True)}, + ) + def get(self, request, *args, **kwargs): + slz = TenantDepartmentListInputSLZ(data=self.request.query_params, context={"tenant_id": self.tenant_id}) + slz.is_valid(raise_exception=True) + data = slz.validated_data + + # 获取过滤后的查询集并进行分页 + tenant_depts = self.paginate_queryset(self.get_filter_queryset(data)) + + # 处理部门信息 + dept_info = self._get_depts_info(tenant_depts) + + return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) + + def get_filter_queryset(self, data: Dict[str, Any]) -> QuerySet: + """ + 根据查询参数过滤部门 + """ + tenant_depts = TenantDepartment.objects.select_related("data_source_department__department_relation").filter( + tenant=self.tenant_id + ) + + parent_id = data.get("parent_id") + # 若没有指定查询字段,则直接返回 + if not parent_id: + return tenant_depts + + # 若根据父部门 ID 进行精确查询,则需要先获取到对应的数据源部门 ID,然后通过部门关系表定位子部门 + ds_parent_id = ( + TenantDepartment.objects.filter(id=parent_id, tenant_id=self.tenant_id) + .values_list("data_source_department_id", flat=True) + .first() + ) + + return tenant_depts.filter(data_source_department__department_relation__parent=ds_parent_id) + + @staticmethod + def _get_depts_info(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: + """ + 根据过滤后的数据获取部门信息 + """ + + # 预加载部门对应的租户部门 + tenant_dept_id_map = { + (data_source_dept_id, tenant_id): dept_id + for (data_source_dept_id, tenant_id, dept_id) in TenantDepartment.objects.values_list( + "data_source_department_id", "tenant_id", "id" + ) + } + + # 获取数据源部门 ID + data_source_dept_ids = {dept.data_source_department_id for dept in tenant_depts} + + # 预加载部门对应的名称 + dept_name_map = dict( + DataSourceDepartment.objects.filter(id__in=data_source_dept_ids).values_list("id", "name") + ) + + # 组装数据 + return [ + { + "id": dept.id, + "name": dept_name_map[dept.data_source_department_id], + "parent_id": tenant_dept_id_map.get( + (dept.data_source_department.department_relation.parent_id, dept.tenant_id) + ), + } + for dept in tenant_depts + ] diff --git a/src/bk-user/support-files/apidocs/en/list_department.md b/src/bk-user/support-files/apidocs/en/list_department.md new file mode 100644 index 000000000..c54d3697b --- /dev/null +++ b/src/bk-user/support-files/apidocs/en/list_department.md @@ -0,0 +1,61 @@ +### Description + +(Pagination) Query list of departments + +### Parameters + +| Name | Type | Required | Description | +|-----------|------|----------|-------------------------------------------------------------------------------------------------| +| page | int | No | Page number, default is 1 | +| page_size | int | No | The number of pages per page, default is 10 | +| parent_id | int | No | Parent department ID, precise query parameter, if not provided, all departments will be queried | + +### Request Example + +``` +// URL Query Parameters +page=1&page_size=5&parent_id=1 +``` + +### Response Example for Status Code 200 + +```json5 +{ + "data": { + "count": 2, + "results": [ + { + "id": 2, + "name": "部门A", + "parent_id": 1, + }, + { + "id": 3, + "name": "部门B", + "parent_id": 1, + } + ], + } +} +``` + +### Response Parameters Description + +| Name | Type | Description | +|-----------|--------|-------------------------------------| +| id | int | Unique identifier of the department | +| name | string | The name of the department | +| parent_id | int | The parent department ID | + + +# Response Example for Non-200 Status Code + +```json5 +// status_code = 400 +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "Parameter validation failed: parent_id: The specified parent department does not exist in the current tenant" + } +} +``` diff --git a/src/bk-user/support-files/apidocs/zh/list_department.md b/src/bk-user/support-files/apidocs/zh/list_department.md new file mode 100644 index 000000000..8dc34c200 --- /dev/null +++ b/src/bk-user/support-files/apidocs/zh/list_department.md @@ -0,0 +1,60 @@ +### 描述 + +(分页)查询部门列表 + +### 输入参数 + +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|------|----|---------------------------| +| page | int | 否 | 页码,从 1 开始 | +| page_size | int | 否 | 每页数量,默认 10 | +| parent_id | int | 否 | 父部门 ID,精确查询参数,若不传入则查询所有部门 | + +### 请求示例 + +``` +// URL Query 参数 +page=1&page_size=5&parent_id=1 +``` + +### 状态码 200 的响应示例 + +```json5 +{ + "data": { + "count": 2, + "results": [ + { + "id": 2, + "name": "部门A", + "parent_id": 1, + }, + { + "id": 3, + "name": "部门B", + "parent_id": 1, + } + ], + } +} +``` + +### 响应参数说明 + +| 参数名称 | 参数类型 | 描述 | +|-----------|--------|--------| +| id | int | 部门唯一标识 | +| name | string | 部门名称 | +| parent_id | int | 父部门 ID | + +### 状态码非 200 的响应示例 + +```json5 +// status_code = 400 +{ + "error": { + "code": "INVALID_ARGUMENT", + "message": "参数校验不通过: parent_id: 指定的父部门在当前租户中不存在" + } +} +``` diff --git a/src/bk-user/support-files/resources.yaml b/src/bk-user/support-files/resources.yaml index 5707517a5..a4d1ac492 100644 --- a/src/bk-user/support-files/resources.yaml +++ b/src/bk-user/support-files/resources.yaml @@ -156,3 +156,28 @@ paths: appVerifiedRequired: true resourcePermissionRequired: true descriptionEn: Query information of the department + + /api/v3/open/tenant/departments/: + get: + operationId: list_department + description: 查询部门列表 + tags: [] + responses: + default: + description: '' + x-bk-apigateway-resource: + isPublic: true + allowApplyPermission: false + matchSubpath: false + backend: + name: default + method: get + path: /api/v3/open/tenant/departments/ + matchSubpath: false + timeout: 0 + pluginConfigs: [] + authConfig: + userVerifiedRequired: false + appVerifiedRequired: true + resourcePermissionRequired: true + descriptionEn: Query list of the departments diff --git a/src/bk-user/tests/apis/open_v3/test_department.py b/src/bk-user/tests/apis/open_v3/test_department.py index b305d499d..f02e8daf3 100644 --- a/src/bk-user/tests/apis/open_v3/test_department.py +++ b/src/bk-user/tests/apis/open_v3/test_department.py @@ -61,3 +61,48 @@ def test_with_ancestors(self, api_client): def test_with_not_found(self, api_client): resp = api_client.get(reverse("open_v3.tenant_department.retrieve", kwargs={"id": 9999})) assert resp.status_code == status.HTTP_404_NOT_FOUND + + +@pytest.mark.usefixtures("_init_tenant_users_depts") +class TestTenantDepartmentListApi: + def test_standard(self, api_client): + resp = api_client.get(reverse("open_v3.tenant_department.list")) + + assert resp.status_code == status.HTTP_200_OK + assert resp.data["count"] == 9 + assert len(resp.data["results"]) == 9 + assert [x["id"] for x in resp.data["results"]] == [37, 38, 39, 40, 41, 42, 43, 44, 45] + assert [x["name"] for x in resp.data["results"]] == [ + "公司", + "部门A", + "部门B", + "中心AA", + "中心AB", + "中心BA", + "小组AAA", + "小组ABA", + "小组BAA", + ] + assert [x["parent_id"] for x in resp.data["results"]] == [None, 37, 37, 38, 38, 39, 40, 41, 42] + + def test_with_pagination(self, api_client): + resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"page": 2, "page_size": 2}) + + assert resp.status_code == status.HTTP_200_OK + assert resp.data["count"] == 9 + assert len(resp.data["results"]) == 2 + assert [x["name"] for x in resp.data["results"]] == ["部门B", "中心AA"] + + def test_with_parent(self, api_client): + resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": 56}) + + assert resp.status_code == status.HTTP_200_OK + assert resp.data["count"] == 2 + assert len(resp.data["results"]) == 2 + assert [x["name"] for x in resp.data["results"]] == ["中心AA", "中心AB"] + assert [x["parent_id"] for x in resp.data["results"]] == [56, 56] + + def test_with_invalid_parent(self, api_client): + resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": 9999}) + + assert resp.status_code == status.HTTP_400_BAD_REQUEST From 08a9147f6bc310755ef333113b313cc3ddcc89c2 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Fri, 17 Jan 2025 11:14:15 +0800 Subject: [PATCH 02/13] resolve: solve some problems --- .../tests/apis/open_v3/test_department.py | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/src/bk-user/tests/apis/open_v3/test_department.py b/src/bk-user/tests/apis/open_v3/test_department.py index f02e8daf3..30d5f7c39 100644 --- a/src/bk-user/tests/apis/open_v3/test_department.py +++ b/src/bk-user/tests/apis/open_v3/test_department.py @@ -66,12 +66,32 @@ def test_with_not_found(self, api_client): @pytest.mark.usefixtures("_init_tenant_users_depts") class TestTenantDepartmentListApi: def test_standard(self, api_client): + company = TenantDepartment.objects.get(data_source_department__name="公司") + dept_a = TenantDepartment.objects.get(data_source_department__name="部门A") + dept_b = TenantDepartment.objects.get(data_source_department__name="部门B") + center_aa = TenantDepartment.objects.get(data_source_department__name="中心AA") + center_ab = TenantDepartment.objects.get(data_source_department__name="中心AB") + center_ba = TenantDepartment.objects.get(data_source_department__name="中心BA") + group_aaa = TenantDepartment.objects.get(data_source_department__name="小组AAA") + group_aba = TenantDepartment.objects.get(data_source_department__name="小组ABA") + group_baa = TenantDepartment.objects.get(data_source_department__name="小组BAA") + resp = api_client.get(reverse("open_v3.tenant_department.list")) assert resp.status_code == status.HTTP_200_OK assert resp.data["count"] == 9 assert len(resp.data["results"]) == 9 - assert [x["id"] for x in resp.data["results"]] == [37, 38, 39, 40, 41, 42, 43, 44, 45] + assert [x["id"] for x in resp.data["results"]] == [ + company.id, + dept_a.id, + dept_b.id, + center_aa.id, + center_ab.id, + center_ba.id, + group_aaa.id, + group_aba.id, + group_baa.id, + ] assert [x["name"] for x in resp.data["results"]] == [ "公司", "部门A", @@ -83,7 +103,17 @@ def test_standard(self, api_client): "小组ABA", "小组BAA", ] - assert [x["parent_id"] for x in resp.data["results"]] == [None, 37, 37, 38, 38, 39, 40, 41, 42] + assert [x["parent_id"] for x in resp.data["results"]] == [ + None, + company.id, + company.id, + dept_a.id, + dept_a.id, + dept_b.id, + center_aa.id, + center_ab.id, + center_ba.id, + ] def test_with_pagination(self, api_client): resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"page": 2, "page_size": 2}) @@ -94,13 +124,14 @@ def test_with_pagination(self, api_client): assert [x["name"] for x in resp.data["results"]] == ["部门B", "中心AA"] def test_with_parent(self, api_client): - resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": 56}) + dept_a = TenantDepartment.objects.get(data_source_department__name="部门A") + resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": dept_a.id}) assert resp.status_code == status.HTTP_200_OK assert resp.data["count"] == 2 assert len(resp.data["results"]) == 2 assert [x["name"] for x in resp.data["results"]] == ["中心AA", "中心AB"] - assert [x["parent_id"] for x in resp.data["results"]] == [56, 56] + assert [x["parent_id"] for x in resp.data["results"]] == [dept_a.id, dept_a.id] def test_with_invalid_parent(self, api_client): resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": 9999}) From c1c8a9855dad3af4e27f9eb4240655f2be3a648c Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 15:19:22 +0800 Subject: [PATCH 03/13] resolve: solve some problems --- .../bkuser/apis/open_v3/views/department.py | 41 +++++++------------ 1 file changed, 15 insertions(+), 26 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index ae93e8616..0bfd99699 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -16,7 +16,6 @@ # to the current version of the project delivered to anyone in the future. from typing import Any, Dict, List -from django.db.models import QuerySet from drf_yasg.utils import swagger_auto_schema from rest_framework import generics, status from rest_framework.response import Response @@ -92,40 +91,30 @@ def get(self, request, *args, **kwargs): slz.is_valid(raise_exception=True) data = slz.validated_data - # 获取过滤后的查询集并进行分页 - tenant_depts = self.paginate_queryset(self.get_filter_queryset(data)) - - # 处理部门信息 - dept_info = self._get_depts_info(tenant_depts) - - return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) - - def get_filter_queryset(self, data: Dict[str, Any]) -> QuerySet: - """ - 根据查询参数过滤部门 - """ tenant_depts = TenantDepartment.objects.select_related("data_source_department__department_relation").filter( tenant=self.tenant_id ) - parent_id = data.get("parent_id") - # 若没有指定查询字段,则直接返回 - if not parent_id: - return tenant_depts + if parent_id := data.get("parent_id"): + # 若根据父部门 ID 进行精确查询,则需要先获取到对应的数据源部门 ID,然后通过部门关系表定位子部门 + ds_parent_id = ( + TenantDepartment.objects.filter(id=parent_id, tenant_id=self.tenant_id) + .values_list("data_source_department_id", flat=True) + .first() + ) + queryset = tenant_depts.filter(data_source_department__department_relation__parent=ds_parent_id) + else: + queryset = tenant_depts - # 若根据父部门 ID 进行精确查询,则需要先获取到对应的数据源部门 ID,然后通过部门关系表定位子部门 - ds_parent_id = ( - TenantDepartment.objects.filter(id=parent_id, tenant_id=self.tenant_id) - .values_list("data_source_department_id", flat=True) - .first() - ) + # 处理部门信息 + dept_info = self._list_dept_infos_with_parent(self.paginate_queryset(queryset)) - return tenant_depts.filter(data_source_department__department_relation__parent=ds_parent_id) + return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) @staticmethod - def _get_depts_info(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: + def _list_dept_infos_with_parent(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: """ - 根据过滤后的数据获取部门信息 + 获取部门信息(包含 parent_id) """ # 预加载部门对应的租户部门 From 0b42ba019b45a44fe1fed82cc6db4efd72b18501 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 15:34:32 +0800 Subject: [PATCH 04/13] resolve: solve some problems --- .../bkuser/apis/open_v3/views/department.py | 38 +------------------ src/bk-user/bkuser/biz/organization.py | 38 ++++++++++++++++++- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index 0bfd99699..be787fcb8 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -14,7 +14,6 @@ # # We undertake not to change the open source license (MIT license) applicable # to the current version of the project delivered to anyone in the future. -from typing import Any, Dict, List from drf_yasg.utils import swagger_auto_schema from rest_framework import generics, status @@ -27,7 +26,6 @@ TenantDepartmentRetrieveInputSLZ, TenantDepartmentRetrieveOutputSLZ, ) -from bkuser.apps.data_source.models import DataSourceDepartment from bkuser.apps.tenant.models import TenantDepartment from bkuser.biz.organization import DataSourceDepartmentHandler @@ -107,40 +105,6 @@ def get(self, request, *args, **kwargs): queryset = tenant_depts # 处理部门信息 - dept_info = self._list_dept_infos_with_parent(self.paginate_queryset(queryset)) + dept_info = DataSourceDepartmentHandler.list_dept_infos_with_parent(self.paginate_queryset(queryset)) return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) - - @staticmethod - def _list_dept_infos_with_parent(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: - """ - 获取部门信息(包含 parent_id) - """ - - # 预加载部门对应的租户部门 - tenant_dept_id_map = { - (data_source_dept_id, tenant_id): dept_id - for (data_source_dept_id, tenant_id, dept_id) in TenantDepartment.objects.values_list( - "data_source_department_id", "tenant_id", "id" - ) - } - - # 获取数据源部门 ID - data_source_dept_ids = {dept.data_source_department_id for dept in tenant_depts} - - # 预加载部门对应的名称 - dept_name_map = dict( - DataSourceDepartment.objects.filter(id__in=data_source_dept_ids).values_list("id", "name") - ) - - # 组装数据 - return [ - { - "id": dept.id, - "name": dept_name_map[dept.data_source_department_id], - "parent_id": tenant_dept_id_map.get( - (dept.data_source_department.department_relation.parent_id, dept.tenant_id) - ), - } - for dept in tenant_depts - ] diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index 6f38be510..89e839d40 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -16,17 +16,19 @@ # to the current version of the project delivered to anyone in the future. import datetime -from typing import List +from typing import Any, Dict, List from django.db import transaction from django.utils import timezone from bkuser.apps.data_source.models import ( + DataSourceDepartment, DataSourceDepartmentRelation, DataSourceUser, DataSourceUserDeprecatedPasswordRecord, LocalDataSourceIdentityInfo, ) +from bkuser.apps.tenant.models import TenantDepartment from bkuser.common.constants import PERMANENT_TIME from bkuser.common.hashers import make_password @@ -116,3 +118,37 @@ def get_dept_ancestors(dept_id: int) -> List[int]: return [] # 返回的祖先部门默认以降序排列,从根祖先部门 -> 父部门 return list(relation.get_ancestors().values_list("department_id", flat=True)) + + @staticmethod + def list_dept_infos_with_parent(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: + """ + 获取部门信息(包含 parent_id) + """ + + # 预加载部门对应的租户部门 + tenant_dept_id_map = { + (data_source_dept_id, tenant_id): dept_id + for (data_source_dept_id, tenant_id, dept_id) in TenantDepartment.objects.values_list( + "data_source_department_id", "tenant_id", "id" + ) + } + + # 获取数据源部门 ID + data_source_dept_ids = {dept.data_source_department_id for dept in tenant_depts} + + # 预加载部门对应的名称 + dept_name_map = dict( + DataSourceDepartment.objects.filter(id__in=data_source_dept_ids).values_list("id", "name") + ) + + # 组装数据 + return [ + { + "id": dept.id, + "name": dept_name_map[dept.data_source_department_id], + "parent_id": tenant_dept_id_map.get( + (dept.data_source_department.department_relation.parent_id, dept.tenant_id) + ), + } + for dept in tenant_depts + ] From 4a503003e68a8e6c05cb6fad951ff7e1d44e6506 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 16:13:19 +0800 Subject: [PATCH 05/13] resolve: solve some problems --- .../apis/open_v3/serializers/department.py | 14 -------- .../bkuser/apis/open_v3/views/department.py | 21 +----------- .../apidocs/en/list_department.md | 34 ++++++------------- .../apidocs/zh/list_department.md | 33 ++++++------------ .../tests/apis/open_v3/test_department.py | 23 ------------- 5 files changed, 21 insertions(+), 104 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/department.py b/src/bk-user/bkuser/apis/open_v3/serializers/department.py index 1089a50f8..79be2212a 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/department.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/department.py @@ -15,11 +15,7 @@ # We undertake not to change the open source license (MIT license) applicable # to the current version of the project delivered to anyone in the future. -from django.utils.translation import gettext_lazy as _ from rest_framework import serializers -from rest_framework.exceptions import ValidationError - -from bkuser.apps.tenant.models import TenantDepartment class AncestorSLZ(serializers.Serializer): @@ -40,16 +36,6 @@ class TenantDepartmentRetrieveOutputSLZ(serializers.Serializer): ancestors = serializers.ListField(help_text="祖先部门列表", required=False, child=AncestorSLZ(), allow_empty=True) -class TenantDepartmentListInputSLZ(serializers.Serializer): - parent_id = serializers.IntegerField(help_text="父部门 ID", required=False) - - def validate_parent_id(self, parent_id: int) -> int: - if not TenantDepartment.objects.filter(id=parent_id, tenant_id=self.context["tenant_id"]).exists(): - raise ValidationError(_("指定的父部门在当前租户中不存在")) - - return parent_id - - class TenantDepartmentListOutputSLZ(serializers.Serializer): id = serializers.IntegerField(help_text="部门 ID") name = serializers.CharField(help_text="部门名称") diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index be787fcb8..860c27bf2 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -21,7 +21,6 @@ from bkuser.apis.open_v3.mixins import OpenApiCommonMixin from bkuser.apis.open_v3.serializers.department import ( - TenantDepartmentListInputSLZ, TenantDepartmentListOutputSLZ, TenantDepartmentRetrieveInputSLZ, TenantDepartmentRetrieveOutputSLZ, @@ -81,30 +80,12 @@ class TenantDepartmentListApi(OpenApiCommonMixin, generics.ListAPIView): tags=["open_v3.department"], operation_id="list_department", operation_description="查询部门列表", - query_serializer=TenantDepartmentListInputSLZ(), responses={status.HTTP_200_OK: TenantDepartmentListOutputSLZ(many=True)}, ) def get(self, request, *args, **kwargs): - slz = TenantDepartmentListInputSLZ(data=self.request.query_params, context={"tenant_id": self.tenant_id}) - slz.is_valid(raise_exception=True) - data = slz.validated_data - tenant_depts = TenantDepartment.objects.select_related("data_source_department__department_relation").filter( tenant=self.tenant_id ) - - if parent_id := data.get("parent_id"): - # 若根据父部门 ID 进行精确查询,则需要先获取到对应的数据源部门 ID,然后通过部门关系表定位子部门 - ds_parent_id = ( - TenantDepartment.objects.filter(id=parent_id, tenant_id=self.tenant_id) - .values_list("data_source_department_id", flat=True) - .first() - ) - queryset = tenant_depts.filter(data_source_department__department_relation__parent=ds_parent_id) - else: - queryset = tenant_depts - # 处理部门信息 - dept_info = DataSourceDepartmentHandler.list_dept_infos_with_parent(self.paginate_queryset(queryset)) - + dept_info = DataSourceDepartmentHandler.list_dept_infos_with_parent(self.paginate_queryset(tenant_depts)) return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) diff --git a/src/bk-user/support-files/apidocs/en/list_department.md b/src/bk-user/support-files/apidocs/en/list_department.md index c54d3697b..21ade1cbc 100644 --- a/src/bk-user/support-files/apidocs/en/list_department.md +++ b/src/bk-user/support-files/apidocs/en/list_department.md @@ -4,17 +4,16 @@ ### Parameters -| Name | Type | Required | Description | -|-----------|------|----------|-------------------------------------------------------------------------------------------------| -| page | int | No | Page number, default is 1 | -| page_size | int | No | The number of pages per page, default is 10 | -| parent_id | int | No | Parent department ID, precise query parameter, if not provided, all departments will be queried | +| Name | Type | Required | Description | +|-----------|------|----------|---------------------------------------------| +| page | int | No | Page number, default is 1 | +| page_size | int | No | The number of pages per page, default is 10 | ### Request Example ``` // URL Query Parameters -page=1&page_size=5&parent_id=1 +page=2&page_size=2 ``` ### Response Example for Status Code 200 @@ -24,15 +23,15 @@ page=1&page_size=5&parent_id=1 "data": { "count": 2, "results": [ - { - "id": 2, - "name": "部门A", - "parent_id": 1, - }, { "id": 3, "name": "部门B", "parent_id": 1, + }, + { + "id": 4, + "name": "中心AA", + "parent_id": 2, } ], } @@ -46,16 +45,3 @@ page=1&page_size=5&parent_id=1 | id | int | Unique identifier of the department | | name | string | The name of the department | | parent_id | int | The parent department ID | - - -# Response Example for Non-200 Status Code - -```json5 -// status_code = 400 -{ - "error": { - "code": "INVALID_ARGUMENT", - "message": "Parameter validation failed: parent_id: The specified parent department does not exist in the current tenant" - } -} -``` diff --git a/src/bk-user/support-files/apidocs/zh/list_department.md b/src/bk-user/support-files/apidocs/zh/list_department.md index 8dc34c200..57719bcf2 100644 --- a/src/bk-user/support-files/apidocs/zh/list_department.md +++ b/src/bk-user/support-files/apidocs/zh/list_department.md @@ -4,17 +4,16 @@ ### 输入参数 -| 参数名称 | 参数类型 | 必选 | 描述 | -|-----------|------|----|---------------------------| -| page | int | 否 | 页码,从 1 开始 | -| page_size | int | 否 | 每页数量,默认 10 | -| parent_id | int | 否 | 父部门 ID,精确查询参数,若不传入则查询所有部门 | +| 参数名称 | 参数类型 | 必选 | 描述 | +|-----------|------|----|-------------| +| page | int | 否 | 页码,从 1 开始 | +| page_size | int | 否 | 每页数量,默认为 10 | ### 请求示例 ``` // URL Query 参数 -page=1&page_size=5&parent_id=1 +page=2&page_size=2 ``` ### 状态码 200 的响应示例 @@ -24,15 +23,15 @@ page=1&page_size=5&parent_id=1 "data": { "count": 2, "results": [ - { - "id": 2, - "name": "部门A", - "parent_id": 1, - }, { "id": 3, "name": "部门B", "parent_id": 1, + }, + { + "id": 4, + "name": "中心AA", + "parent_id": 2, } ], } @@ -46,15 +45,3 @@ page=1&page_size=5&parent_id=1 | id | int | 部门唯一标识 | | name | string | 部门名称 | | parent_id | int | 父部门 ID | - -### 状态码非 200 的响应示例 - -```json5 -// status_code = 400 -{ - "error": { - "code": "INVALID_ARGUMENT", - "message": "参数校验不通过: parent_id: 指定的父部门在当前租户中不存在" - } -} -``` diff --git a/src/bk-user/tests/apis/open_v3/test_department.py b/src/bk-user/tests/apis/open_v3/test_department.py index 30d5f7c39..357e14ed6 100644 --- a/src/bk-user/tests/apis/open_v3/test_department.py +++ b/src/bk-user/tests/apis/open_v3/test_department.py @@ -114,26 +114,3 @@ def test_standard(self, api_client): center_ab.id, center_ba.id, ] - - def test_with_pagination(self, api_client): - resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"page": 2, "page_size": 2}) - - assert resp.status_code == status.HTTP_200_OK - assert resp.data["count"] == 9 - assert len(resp.data["results"]) == 2 - assert [x["name"] for x in resp.data["results"]] == ["部门B", "中心AA"] - - def test_with_parent(self, api_client): - dept_a = TenantDepartment.objects.get(data_source_department__name="部门A") - resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": dept_a.id}) - - assert resp.status_code == status.HTTP_200_OK - assert resp.data["count"] == 2 - assert len(resp.data["results"]) == 2 - assert [x["name"] for x in resp.data["results"]] == ["中心AA", "中心AB"] - assert [x["parent_id"] for x in resp.data["results"]] == [dept_a.id, dept_a.id] - - def test_with_invalid_parent(self, api_client): - resp = api_client.get(reverse("open_v3.tenant_department.list"), data={"parent_id": 9999}) - - assert resp.status_code == status.HTTP_400_BAD_REQUEST From 6de4d1dd8a1ad89aab0e457ac214b473bc1dc7a8 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 17:18:48 +0800 Subject: [PATCH 06/13] resolve: solve some problems --- src/bk-user/bkuser/apis/open_v3/views/department.py | 2 +- src/bk-user/bkuser/biz/organization.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index 860c27bf2..1c1417683 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -87,5 +87,5 @@ def get(self, request, *args, **kwargs): tenant=self.tenant_id ) # 处理部门信息 - dept_info = DataSourceDepartmentHandler.list_dept_infos_with_parent(self.paginate_queryset(tenant_depts)) + dept_info = DataSourceDepartmentHandler.list_dept_infos(self.paginate_queryset(tenant_depts)) return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index 89e839d40..3a2cc10f4 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -120,7 +120,7 @@ def get_dept_ancestors(dept_id: int) -> List[int]: return list(relation.get_ancestors().values_list("department_id", flat=True)) @staticmethod - def list_dept_infos_with_parent(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: + def list_dept_infos(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: """ 获取部门信息(包含 parent_id) """ From 9606e786bd20278aad64dfa4dd70f2bb59e63af1 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 19:06:45 +0800 Subject: [PATCH 07/13] resolve: solve some problems --- .../apis/open_v3/serializers/department.py | 7 ++-- .../bkuser/apis/open_v3/views/department.py | 19 ++++++----- src/bk-user/bkuser/biz/organization.py | 32 ++++++------------- 3 files changed, 26 insertions(+), 32 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/department.py b/src/bk-user/bkuser/apis/open_v3/serializers/department.py index 79be2212a..e1ec7b499 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/department.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/department.py @@ -38,5 +38,8 @@ class TenantDepartmentRetrieveOutputSLZ(serializers.Serializer): class TenantDepartmentListOutputSLZ(serializers.Serializer): id = serializers.IntegerField(help_text="部门 ID") - name = serializers.CharField(help_text="部门名称") - parent_id = serializers.IntegerField(help_text="父部门 ID", allow_null=True) + name = serializers.CharField(help_text="部门名称", source="data_source_department.name") + parent_id = serializers.SerializerMethodField(help_text="父部门 ID", allow_null=True) + + def get_parent_id(self, obj): + return self.context["parent_id_map"].get(obj.id) diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index 1c1417683..53a612174 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -26,7 +26,7 @@ TenantDepartmentRetrieveOutputSLZ, ) from bkuser.apps.tenant.models import TenantDepartment -from bkuser.biz.organization import DataSourceDepartmentHandler +from bkuser.biz.organization import DataSourceDepartmentHandler, TenantDepartmentHandler class TenantDepartmentRetrieveApi(OpenApiCommonMixin, generics.RetrieveAPIView): @@ -74,8 +74,6 @@ class TenantDepartmentListApi(OpenApiCommonMixin, generics.ListAPIView): 获取部门列表 """ - serializer_class = TenantDepartmentListOutputSLZ - @swagger_auto_schema( tags=["open_v3.department"], operation_id="list_department", @@ -83,9 +81,14 @@ class TenantDepartmentListApi(OpenApiCommonMixin, generics.ListAPIView): responses={status.HTTP_200_OK: TenantDepartmentListOutputSLZ(many=True)}, ) def get(self, request, *args, **kwargs): - tenant_depts = TenantDepartment.objects.select_related("data_source_department__department_relation").filter( - tenant=self.tenant_id + depts = TenantDepartment.objects.select_related("data_source_department").filter(tenant=self.tenant_id) + + # 分页 + page = self.paginate_queryset(depts) + + # 查询 parent + parent_id_map = TenantDepartmentHandler.get_tenant_department_parent_id_map(page) + + return self.get_paginated_response( + TenantDepartmentListOutputSLZ(page, many=True, context={"parent_id_map": parent_id_map}).data ) - # 处理部门信息 - dept_info = DataSourceDepartmentHandler.list_dept_infos(self.paginate_queryset(tenant_depts)) - return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data) diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index 3a2cc10f4..a9ad33fff 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -16,13 +16,12 @@ # to the current version of the project delivered to anyone in the future. import datetime -from typing import Any, Dict, List +from typing import Dict, List from django.db import transaction from django.utils import timezone from bkuser.apps.data_source.models import ( - DataSourceDepartment, DataSourceDepartmentRelation, DataSourceUser, DataSourceUserDeprecatedPasswordRecord, @@ -119,10 +118,12 @@ def get_dept_ancestors(dept_id: int) -> List[int]: # 返回的祖先部门默认以降序排列,从根祖先部门 -> 父部门 return list(relation.get_ancestors().values_list("department_id", flat=True)) + +class TenantDepartmentHandler: @staticmethod - def list_dept_infos(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any]]: + def get_tenant_department_parent_id_map(tenant_depts: List[TenantDepartment]) -> Dict[int, int | None]: """ - 获取部门信息(包含 parent_id) + 批量获取指定租户部门的父部门映射 """ # 预加载部门对应的租户部门 @@ -133,22 +134,9 @@ def list_dept_infos(tenant_depts: List[TenantDepartment]) -> List[Dict[str, Any] ) } - # 获取数据源部门 ID - data_source_dept_ids = {dept.data_source_department_id for dept in tenant_depts} - - # 预加载部门对应的名称 - dept_name_map = dict( - DataSourceDepartment.objects.filter(id__in=data_source_dept_ids).values_list("id", "name") - ) - - # 组装数据 - return [ - { - "id": dept.id, - "name": dept_name_map[dept.data_source_department_id], - "parent_id": tenant_dept_id_map.get( - (dept.data_source_department.department_relation.parent_id, dept.tenant_id) - ), - } + return { + dept.id: tenant_dept_id_map.get( + (dept.data_source_department.department_relation.parent_id, dept.tenant_id) + ) for dept in tenant_depts - ] + } From 45eaaafa4e36030fff7a62230c05dee07f787248 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 19:10:01 +0800 Subject: [PATCH 08/13] resolve: solve some problems --- src/bk-user/bkuser/biz/organization.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index a9ad33fff..d1192cc92 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -121,7 +121,7 @@ def get_dept_ancestors(dept_id: int) -> List[int]: class TenantDepartmentHandler: @staticmethod - def get_tenant_department_parent_id_map(tenant_depts: List[TenantDepartment]) -> Dict[int, int | None]: + def get_tenant_department_parent_id_map(tenant_departments: List[TenantDepartment]) -> Dict[int, int | None]: """ 批量获取指定租户部门的父部门映射 """ @@ -138,5 +138,5 @@ def get_tenant_department_parent_id_map(tenant_depts: List[TenantDepartment]) -> dept.id: tenant_dept_id_map.get( (dept.data_source_department.department_relation.parent_id, dept.tenant_id) ) - for dept in tenant_depts + for dept in tenant_departments } From e48238b7ef0b94d40acc7b636d8ff8828e574d4e Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 19:51:39 +0800 Subject: [PATCH 09/13] resolve: solve some problems --- src/bk-user/bkuser/biz/organization.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index d1192cc92..6fd3e77d7 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -123,20 +123,14 @@ class TenantDepartmentHandler: @staticmethod def get_tenant_department_parent_id_map(tenant_departments: List[TenantDepartment]) -> Dict[int, int | None]: """ - 批量获取指定租户部门的父部门映射 + 获取部门的父部门 ID 映射 """ - # 预加载部门对应的租户部门 - tenant_dept_id_map = { - (data_source_dept_id, tenant_id): dept_id - for (data_source_dept_id, tenant_id, dept_id) in TenantDepartment.objects.values_list( - "data_source_department_id", "tenant_id", "id" - ) - } + dept_ids = [dept.data_source_department_id for dept in tenant_departments] - return { - dept.id: tenant_dept_id_map.get( - (dept.data_source_department.department_relation.parent_id, dept.tenant_id) - ) - for dept in tenant_departments - } + relations = DataSourceDepartmentRelation.objects.filter(department_id__in=dept_ids) + + # 部门 ID -> 父部门 ID 映射 + parent_id_map = {rel.department_id: rel.parent_id for rel in relations} + + return {dept.id: parent_id_map.get(dept.data_source_department_id) for dept in tenant_departments} From 4b72efe0a0ff4c5e0fd3a28c9165fe463e389493 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 20:17:28 +0800 Subject: [PATCH 10/13] resolve: solve some problems --- .../bkuser/apis/open_v3/views/department.py | 2 +- src/bk-user/bkuser/biz/organization.py | 23 +++++++++++++++---- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/views/department.py b/src/bk-user/bkuser/apis/open_v3/views/department.py index 53a612174..b8012daf6 100644 --- a/src/bk-user/bkuser/apis/open_v3/views/department.py +++ b/src/bk-user/bkuser/apis/open_v3/views/department.py @@ -87,7 +87,7 @@ def get(self, request, *args, **kwargs): page = self.paginate_queryset(depts) # 查询 parent - parent_id_map = TenantDepartmentHandler.get_tenant_department_parent_id_map(page) + parent_id_map = TenantDepartmentHandler.get_tenant_department_parent_id_map(self.tenant_id, page) return self.get_paginated_response( TenantDepartmentListOutputSLZ(page, many=True, context={"parent_id_map": parent_id_map}).data diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index 6fd3e77d7..065dadf0f 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -121,16 +121,31 @@ def get_dept_ancestors(dept_id: int) -> List[int]: class TenantDepartmentHandler: @staticmethod - def get_tenant_department_parent_id_map(tenant_departments: List[TenantDepartment]) -> Dict[int, int | None]: + def get_tenant_department_parent_id_map( + tenant_id: str, tenant_departments: List[TenantDepartment] + ) -> Dict[int, int | None]: """ 获取部门的父部门 ID 映射 """ + # 获取部门的数据源部门 ID 列表 dept_ids = [dept.data_source_department_id for dept in tenant_departments] + # 获取部门的数据源部门关系 relations = DataSourceDepartmentRelation.objects.filter(department_id__in=dept_ids) - # 部门 ID -> 父部门 ID 映射 - parent_id_map = {rel.department_id: rel.parent_id for rel in relations} + # 获取部门 ID 到父部门 ID 的映射 + dept_parent_id_map = {rel.department_id: rel.parent_id for rel in relations} - return {dept.id: parent_id_map.get(dept.data_source_department_id) for dept in tenant_departments} + # 获取父部门数据源 ID 到租户父部门 ID 的映射 + parent_ids = list(dept_parent_id_map.values()) + parent_id_map = dict( + TenantDepartment.objects.filter(tenant_id=tenant_id, data_source_department_id__in=parent_ids).values_list( + "data_source_department_id", "id" + ) + ) + + return { + dept.id: parent_id_map.get(dept_parent_id_map[dept.data_source_department_id]) + for dept in tenant_departments + } From d1115915b6343b8b5b6711460034e8a9e0f4ab81 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Mon, 20 Jan 2025 20:53:03 +0800 Subject: [PATCH 11/13] resolve: solve some problems --- .../bkuser/apis/open_v3/serializers/department.py | 2 +- src/bk-user/bkuser/biz/organization.py | 15 ++++++++------- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/department.py b/src/bk-user/bkuser/apis/open_v3/serializers/department.py index e1ec7b499..4c8a76efe 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/department.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/department.py @@ -41,5 +41,5 @@ class TenantDepartmentListOutputSLZ(serializers.Serializer): name = serializers.CharField(help_text="部门名称", source="data_source_department.name") parent_id = serializers.SerializerMethodField(help_text="父部门 ID", allow_null=True) - def get_parent_id(self, obj): + def get_parent_id(self, obj) -> int: return self.context["parent_id_map"].get(obj.id) diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index 065dadf0f..f9b006dae 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -123,7 +123,7 @@ class TenantDepartmentHandler: @staticmethod def get_tenant_department_parent_id_map( tenant_id: str, tenant_departments: List[TenantDepartment] - ) -> Dict[int, int | None]: + ) -> Dict[int, int]: """ 获取部门的父部门 ID 映射 """ @@ -132,11 +132,11 @@ def get_tenant_department_parent_id_map( dept_ids = [dept.data_source_department_id for dept in tenant_departments] # 获取部门的数据源部门关系 - relations = DataSourceDepartmentRelation.objects.filter(department_id__in=dept_ids) - - # 获取部门 ID 到父部门 ID 的映射 - dept_parent_id_map = {rel.department_id: rel.parent_id for rel in relations} - + dept_parent_id_map = dict( + DataSourceDepartmentRelation.objects.filter(department_id__in=dept_ids).values_list( + "department_id", "parent_id" + ) + ) # 获取父部门数据源 ID 到租户父部门 ID 的映射 parent_ids = list(dept_parent_id_map.values()) parent_id_map = dict( @@ -146,6 +146,7 @@ def get_tenant_department_parent_id_map( ) return { - dept.id: parent_id_map.get(dept_parent_id_map[dept.data_source_department_id]) + dept.id: parent_id_map[dept_parent_id_map[dept.data_source_department_id]] for dept in tenant_departments + if dept_parent_id_map[dept.data_source_department_id] in parent_id_map } From 807ca500f8d6e7d0de550928465842f41e3c7f00 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Tue, 21 Jan 2025 09:49:51 +0800 Subject: [PATCH 12/13] resolve: solve some problems --- .../bkuser/apis/open_v3/serializers/department.py | 4 +++- src/bk-user/bkuser/biz/organization.py | 10 +++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/department.py b/src/bk-user/bkuser/apis/open_v3/serializers/department.py index 4c8a76efe..6fb0d6bf5 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/department.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/department.py @@ -17,6 +17,8 @@ from rest_framework import serializers +from bkuser.apps.tenant.models import TenantDepartment + class AncestorSLZ(serializers.Serializer): id = serializers.IntegerField(help_text="祖先部门 ID") @@ -41,5 +43,5 @@ class TenantDepartmentListOutputSLZ(serializers.Serializer): name = serializers.CharField(help_text="部门名称", source="data_source_department.name") parent_id = serializers.SerializerMethodField(help_text="父部门 ID", allow_null=True) - def get_parent_id(self, obj) -> int: + def get_parent_id(self, obj: TenantDepartment) -> int: return self.context["parent_id_map"].get(obj.id) diff --git a/src/bk-user/bkuser/biz/organization.py b/src/bk-user/bkuser/biz/organization.py index f9b006dae..8e95daacc 100644 --- a/src/bk-user/bkuser/biz/organization.py +++ b/src/bk-user/bkuser/biz/organization.py @@ -132,21 +132,21 @@ def get_tenant_department_parent_id_map( dept_ids = [dept.data_source_department_id for dept in tenant_departments] # 获取部门的数据源部门关系 - dept_parent_id_map = dict( + parent_id_map = dict( DataSourceDepartmentRelation.objects.filter(department_id__in=dept_ids).values_list( "department_id", "parent_id" ) ) # 获取父部门数据源 ID 到租户父部门 ID 的映射 - parent_ids = list(dept_parent_id_map.values()) - parent_id_map = dict( + parent_ids = list(parent_id_map.values()) + tenant_dept_id_map = dict( TenantDepartment.objects.filter(tenant_id=tenant_id, data_source_department_id__in=parent_ids).values_list( "data_source_department_id", "id" ) ) return { - dept.id: parent_id_map[dept_parent_id_map[dept.data_source_department_id]] + dept.id: tenant_dept_id_map[parent_id_map[dept.data_source_department_id]] for dept in tenant_departments - if dept_parent_id_map[dept.data_source_department_id] in parent_id_map + if parent_id_map[dept.data_source_department_id] in tenant_dept_id_map } From 54b755d55424d0a77b5568c50d7f9b1091f5f0a2 Mon Sep 17 00:00:00 2001 From: rolinchen Date: Tue, 21 Jan 2025 10:13:12 +0800 Subject: [PATCH 13/13] resolve: solve some problems --- src/bk-user/bkuser/apis/open_v3/serializers/department.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bk-user/bkuser/apis/open_v3/serializers/department.py b/src/bk-user/bkuser/apis/open_v3/serializers/department.py index 6fb0d6bf5..c1164e20f 100644 --- a/src/bk-user/bkuser/apis/open_v3/serializers/department.py +++ b/src/bk-user/bkuser/apis/open_v3/serializers/department.py @@ -43,5 +43,5 @@ class TenantDepartmentListOutputSLZ(serializers.Serializer): name = serializers.CharField(help_text="部门名称", source="data_source_department.name") parent_id = serializers.SerializerMethodField(help_text="父部门 ID", allow_null=True) - def get_parent_id(self, obj: TenantDepartment) -> int: + def get_parent_id(self, obj: TenantDepartment) -> int | None: return self.context["parent_id_map"].get(obj.id)