Skip to content

Commit

Permalink
minor: merge tencent/ft_tenant into data_source_base_api
Browse files Browse the repository at this point in the history
  • Loading branch information
narasux committed Sep 1, 2023
2 parents a496cd0 + 42bb5a4 commit 77b84e9
Show file tree
Hide file tree
Showing 36 changed files with 431 additions and 93 deletions.
24 changes: 24 additions & 0 deletions .github/workflows/eslint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: ESLint

on:
push:
branches: [ master, develop, pre_*, ft_* ]
pull_request:
branches: [ master, develop, pre_*, ft_* ]

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
- name: Install modules
run: |
cd src/pages
npm i
- name: Run ESLint
run: |
cd src/pages
npm run lint
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -215,3 +215,5 @@ pre_commit_hooks

# local settings
cliff.toml
.codecc
.idea
72 changes: 72 additions & 0 deletions src/bk-user/bkuser/apis/web/data_source/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -242,3 +242,75 @@ class DepartmentSearchInputSLZ(serializers.Serializer):
class DepartmentSearchOutputSLZ(serializers.Serializer):
id = serializers.CharField(help_text="部门ID")
name = serializers.CharField(help_text="部门名称")


class UserDepartmentOutputSLZ(serializers.Serializer):
id = serializers.IntegerField(help_text="部门ID")
name = serializers.CharField(help_text="部门名称")


class UserLeaderOutputSLZ(serializers.Serializer):
id = serializers.IntegerField(help_text="上级ID")
username = serializers.CharField(help_text="上级用户名")


class UserRetrieveOutputSLZ(serializers.Serializer):
username = serializers.CharField(help_text="用户名")
full_name = serializers.CharField(help_text="全名")
email = serializers.CharField(help_text="邮箱")
phone_country_code = serializers.CharField(help_text="手机区号")
phone = serializers.CharField(help_text="手机号")
logo = serializers.SerializerMethodField(help_text="用户Logo")

departments = serializers.SerializerMethodField(help_text="部门信息")
leaders = serializers.SerializerMethodField(help_text="上级信息")

def get_logo(self, obj: DataSourceUser) -> str:
return obj.logo or settings.DEFAULT_DATA_SOURCE_USER_LOGO

@swagger_serializer_method(serializer_or_field=UserDepartmentOutputSLZ(many=True))
def get_departments(self, obj: DataSourceUser) -> List[Dict]:
user_departments_map = self.context["user_departments_map"]
departments = user_departments_map.get(obj.id, [])
return [{"id": dept.id, "name": dept.name} for dept in departments]

@swagger_serializer_method(serializer_or_field=UserLeaderOutputSLZ(many=True))
def get_leaders(self, obj: DataSourceUser) -> List[Dict]:
user_leaders_map = self.context["user_leaders_map"]
leaders = user_leaders_map.get(obj.id, [])
return [{"id": leader.id, "username": leader.username} for leader in leaders]


class UserUpdateInputSLZ(serializers.Serializer):
full_name = serializers.CharField(help_text="姓名")
email = serializers.CharField(help_text="邮箱")
phone_country_code = serializers.CharField(help_text="手机国际区号")
phone = serializers.CharField(help_text="手机号")
logo = serializers.CharField(help_text="用户 Logo", allow_blank=True)

department_ids = serializers.ListField(help_text="部门ID列表", child=serializers.IntegerField())
leader_ids = serializers.ListField(help_text="上级ID列表", child=serializers.IntegerField())

def validate(self, data):
validate_phone_with_country_code(phone=data["phone"], country_code=data["phone_country_code"])
return data

def validate_department_ids(self, department_ids):
diff_department_ids = set(department_ids) - set(
DataSourceDepartment.objects.filter(
id__in=department_ids, data_source=self.context["data_source"]
).values_list("id", flat=True)
)
if diff_department_ids:
raise serializers.ValidationError(_("传递了错误的部门信息: {}").format(diff_department_ids))
return department_ids

def validate_leader_ids(self, leader_ids):
diff_leader_ids = set(leader_ids) - set(
DataSourceUser.objects.filter(id__in=leader_ids, data_source=self.context["data_source"]).values_list(
"id", flat=True
)
)
if diff_leader_ids:
raise serializers.ValidationError(_("传递了错误的上级信息: {}").format(diff_leader_ids))
return leader_ids
1 change: 1 addition & 0 deletions src/bk-user/bkuser/apis/web/data_source/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,5 @@
path("<int:id>/leaders/", views.DataSourceLeadersListApi.as_view(), name="data_source_leaders.list"),
# 数据源部门
path("<int:id>/departments/", views.DataSourceDepartmentsListApi.as_view(), name="data_source_departments.list"),
path("user/<int:id>/", views.DataSourceUserRetrieveUpdateApi.as_view(), name="data_source_user.retrieve_update"),
]
58 changes: 57 additions & 1 deletion src/bk-user/bkuser/apis/web/data_source/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
from bkuser.biz.data_source_organization import (
DataSourceOrganizationHandler,
DataSourceUserBaseInfo,
DataSourceUserEditableBaseInfo,
DataSourceUserRelationInfo,
)
from bkuser.common.error_codes import error_codes
Expand Down Expand Up @@ -194,7 +195,7 @@ def post(self, request, *args, **kwargs):

# 不允许对非本地数据源进行用户新增操作
if not data_source.is_local:
raise error_codes.CANNOT_CREATE_USER
raise error_codes.CANNOT_CREATE_DATA_SOURCE_USER
# 校验是否已存在该用户
if DataSourceUser.objects.filter(username=data["username"], data_source=data_source).exists():
raise error_codes.DATA_SOURCE_USER_ALREADY_EXISTED
Expand Down Expand Up @@ -289,3 +290,58 @@ def post(self, request, *args, **kwargs):
"""导入本地数据源用户数据(Excel 格式)"""
# TODO 实现代码逻辑
return Response()


class DataSourceUserRetrieveUpdateApi(ExcludePatchAPIViewMixin, generics.RetrieveUpdateAPIView):
queryset = DataSourceUser.objects.all()
lookup_url_kwarg = "id"
serializer_class = slzs.UserRetrieveOutputSLZ

def get_serializer_context(self):
user_departments_map = DataSourceOrganizationHandler.get_user_departments_map_by_user_id(
user_ids=[self.kwargs["id"]]
)
user_leaders_map = DataSourceOrganizationHandler.get_user_leaders_map_by_user_id([self.kwargs["id"]])
return {"user_departments_map": user_departments_map, "user_leaders_map": user_leaders_map}

@swagger_auto_schema(
operation_description="数据源用户详情",
responses={status.HTTP_200_OK: slzs.UserRetrieveOutputSLZ()},
tags=["data_source"],
)
def get(self, request, *args, **kwargs):
return self.retrieve(request, *args, **kwargs)

@swagger_auto_schema(
operation_description="更新数据源用户",
request_body=slzs.UserUpdateInputSLZ(),
responses={status.HTTP_200_OK: ""},
tags=["data_source"],
)
def put(self, request, *args, **kwargs):
user = self.get_object()
if not user.data_source.is_local:
raise error_codes.CANNOT_UPDATE_DATA_SOURCE_USER

slz = slzs.UserUpdateInputSLZ(data=request.data, context={"data_source": user.data_source})
slz.is_valid(raise_exception=True)
data = slz.validated_data

# 用户数据整合
base_user_info = DataSourceUserEditableBaseInfo(
full_name=data["full_name"],
email=data["email"],
phone_country_code=data["phone_country_code"],
phone=data["phone"],
logo=data["logo"],
)

relation_info = DataSourceUserRelationInfo(
department_ids=data["department_ids"], leader_ids=data["leader_ids"]
)

DataSourceOrganizationHandler.update_user(
user=user, base_user_info=base_user_info, relation_info=relation_info
)

return Response()
180 changes: 179 additions & 1 deletion src/bk-user/bkuser/biz/data_source_organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
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 List
from collections import defaultdict
from typing import Dict, List

from django.db import transaction
from pydantic import BaseModel

from bkuser.apps.data_source.models import (
DataSource,
DataSourceDepartment,
DataSourceDepartmentUserRelation,
DataSourceUser,
DataSourceUserLeaderRelation,
Expand All @@ -33,6 +35,30 @@ class DataSourceUserBaseInfo(BaseModel):
phone_country_code: str


class DataSourceUserEditableBaseInfo(BaseModel):
"""数据源用户可编辑的基础信息"""

full_name: str
email: str
phone: str
phone_country_code: str
logo: str


class DataSourceUserDepartmentInfo(BaseModel):
"""数据源用户部门信息"""

id: int
name: str


class DataSourceUserLeaderInfo(BaseModel):
"""数据源用户上级信息"""

id: int
username: str


class DataSourceUserRelationInfo(BaseModel):
"""数据源用户关系信息"""

Expand Down Expand Up @@ -82,3 +108,155 @@ def create_user(
)

return user.id

@staticmethod
def update_user_department_relations(user: DataSourceUser, department_ids: List):
"""
更新用户-部门关系
"""
# 查询旧用户部门信息
old_department_ids = DataSourceDepartmentUserRelation.objects.filter(user=user).values_list(
"department_id", flat=True
)

# 需要新增的用户部门信息
should_created_department_ids = set(department_ids) - set(old_department_ids)
# 需要删除的用户部门信息
should_deleted_department_ids = set(old_department_ids) - set(department_ids)

# DB新增
if should_created_department_ids:
should_created_relations = [
DataSourceDepartmentUserRelation(department_id=department_id, user=user)
for department_id in should_created_department_ids
]
DataSourceDepartmentUserRelation.objects.bulk_create(should_created_relations)

# DB删除
if should_deleted_department_ids:
DataSourceDepartmentUserRelation.objects.filter(
user=user, department_id__in=should_deleted_department_ids
).delete()

@staticmethod
def update_user_leader_relations(user: DataSourceUser, leader_ids: List):
"""更新用户-上级关系"""
# 查询旧用户上级信息
old_leader_ids = DataSourceUserLeaderRelation.objects.filter(user=user).values_list("leader_id", flat=True)

# 需要新增的用户部门信息
should_created_leader_ids = set(leader_ids) - set(old_leader_ids)
# 需要删除的用户部门信息
should_deleted_leader_ids = set(old_leader_ids) - set(leader_ids)

# DB新增
if should_created_leader_ids:
should_created_relations = [
DataSourceUserLeaderRelation(leader_id=leader_id, user=user) for leader_id in should_created_leader_ids
]
DataSourceUserLeaderRelation.objects.bulk_create(should_created_relations)

# DB删除
if should_deleted_leader_ids:
DataSourceUserLeaderRelation.objects.filter(user=user, leader_id__in=should_deleted_leader_ids).delete()

@staticmethod
def update_user(
user: DataSourceUser, base_user_info: DataSourceUserEditableBaseInfo, relation_info: DataSourceUserRelationInfo
):
"""更新数据源用户"""

with transaction.atomic():
# 更新用户基础信息
user.full_name = base_user_info.full_name
user.email = base_user_info.email
user.phone = base_user_info.phone
user.phone_country_code = base_user_info.phone_country_code
user.logo = base_user_info.logo

user.save()

# 更新用户-部门关系
DataSourceOrganizationHandler.update_user_department_relations(
user=user, department_ids=relation_info.department_ids
)

# 更新用户-上级关系
DataSourceOrganizationHandler.update_user_leader_relations(user=user, leader_ids=relation_info.leader_ids)

@staticmethod
def list_department_info_by_id(department_ids: List[int]) -> List[DataSourceUserDepartmentInfo]:
"""
根据部门ID获取部门信息
"""
return [
DataSourceUserDepartmentInfo(id=dept.id, name=dept.name)
for dept in DataSourceDepartment.objects.filter(id__in=department_ids)
]

@staticmethod
def get_user_department_ids_map(user_ids: List[int]) -> Dict[int, List[int]]:
"""
获取 用户-所属部门ID关系 映射
"""
department_user_relations = DataSourceDepartmentUserRelation.objects.filter(user_id__in=user_ids)
user_department_ids_map = defaultdict(list)
for r in department_user_relations:
user_department_ids_map[r.user_id].append(r.department_id)

return user_department_ids_map

@staticmethod
def get_user_departments_map_by_user_id(user_ids: List[int]) -> Dict[int, List[DataSourceUserDepartmentInfo]]:
"""
获取 用户-所有归属部门信息
"""
user_department_ids_map = DataSourceOrganizationHandler.get_user_department_ids_map(user_ids=user_ids)

data: Dict = {}
for user_id in user_ids:
department_ids = user_department_ids_map.get(user_id)
if not department_ids:
continue
data[user_id] = DataSourceOrganizationHandler.list_department_info_by_id(department_ids=department_ids)

return data

@staticmethod
def get_user_leader_ids_map(user_ids: List[int]) -> Dict[int, List[int]]:
"""
获取用户-所有上级ID关系映射
"""
user_leader_relations = DataSourceUserLeaderRelation.objects.filter(user_id__in=user_ids)

user_leader_ids_map = defaultdict(list)
for r in user_leader_relations:
user_leader_ids_map[r.user_id].append(r.leader_id)

return user_leader_ids_map

@staticmethod
def list_leader_info_by_id(leaders_ids: List[int]) -> List[DataSourceUserLeaderInfo]:
"""
根据上级ID获取上级信息
"""
return [
DataSourceUserLeaderInfo(id=leader.id, username=leader.username)
for leader in DataSourceUser.objects.filter(id__in=leaders_ids)
]

@staticmethod
def get_user_leaders_map_by_user_id(user_ids: List[int]):
"""
获取用户-所有上级信息数据
"""
user_leader_ids_map = DataSourceOrganizationHandler.get_user_leader_ids_map(user_ids=user_ids)

data: Dict = {}
for user_id in user_ids:
leaders_ids = user_leader_ids_map.get(user_id)
if not leaders_ids:
continue
data[user_id] = DataSourceOrganizationHandler.list_leader_info_by_id(leaders_ids=leaders_ids)

return data
Loading

0 comments on commit 77b84e9

Please sign in to comment.