Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: added query list of departments #2036

Merged
merged 14 commits into from
Jan 21, 2025
20 changes: 20 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/serializers/department.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand All @@ -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)
rolin999 marked this conversation as resolved.
Show resolved Hide resolved

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
rolin999 marked this conversation as resolved.
Show resolved Hide resolved


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)
5 changes: 5 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,11 @@
name="open_v3.tenant_department.retrieve",
),
path("users/", views.TenantUserListApi.as_view(), name="open_v3.tenant_user.list"),
path(
"departments/",
views.TenantDepartmentListApi.as_view(),
name="open_v3.tenant_department.list",
),
]
),
),
Expand Down
3 changes: 2 additions & 1 deletion src/bk-user/bkuser/apis/open_v3/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -32,4 +32,5 @@
"TenantUserLeaderListApi",
"TenantUserListApi",
"TenantDepartmentRetrieveApi",
"TenantDepartmentListApi",
]
89 changes: 89 additions & 0 deletions src/bk-user/bkuser/apis/open_v3/views/department.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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)
rolin999 marked this conversation as resolved.
Show resolved Hide resolved

return self.get_paginated_response(TenantDepartmentListOutputSLZ(dept_info, many=True).data)

def get_filter_queryset(self, data: Dict[str, Any]) -> QuerySet:
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
"""
根据查询参数过滤部门
"""
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]]:
rolin999 marked this conversation as resolved.
Show resolved Hide resolved
"""
根据过滤后的数据获取部门信息
"""

# 预加载部门对应的租户部门
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
]
61 changes: 61 additions & 0 deletions src/bk-user/support-files/apidocs/en/list_department.md
Original file line number Diff line number Diff line change
@@ -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"
}
}
```
60 changes: 60 additions & 0 deletions src/bk-user/support-files/apidocs/zh/list_department.md
Original file line number Diff line number Diff line change
@@ -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: 指定的父部门在当前租户中不存在"
}
}
```
25 changes: 25 additions & 0 deletions src/bk-user/support-files/resources.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -181,3 +181,28 @@ paths:
appVerifiedRequired: true
resourcePermissionRequired: true
descriptionEn: (Pagination) Query user's list

/api/v3/open/tenant/departments/:
nannan00 marked this conversation as resolved.
Show resolved Hide resolved
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
Loading
Loading