Skip to content

Commit

Permalink
refactor: cr调整,单元测试补充
Browse files Browse the repository at this point in the history
cr调整,回收站接口单元测试补充
  • Loading branch information
neronkl committed May 15, 2023
1 parent 8a9b118 commit 93856d2
Show file tree
Hide file tree
Showing 23 changed files with 534 additions and 229 deletions.
13 changes: 12 additions & 1 deletion src/api/bkuser_core/api/web/category/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
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.
"""
import logging
from typing import List

from django.utils.translation import ugettext_lazy as _
Expand All @@ -19,11 +20,14 @@
from bkuser_core.bkiam.serializers import AuthInfoSLZ
from bkuser_core.categories.constants import CategoryStatus, CreateAbleCategoryType
from bkuser_core.categories.models import ProfileCategory
from bkuser_core.categories.utils import change_periodic_sync_task_status
from bkuser_core.departments.models import Department
from bkuser_core.profiles.models import Profile
from bkuser_core.profiles.validators import validate_domain
from bkuser_core.user_settings.models import Setting

logger = logging.getLogger(__name__)


class ExtraInfoSerializer(serializers.Serializer):
auth_infos = serializers.ListField(read_only=True, child=AuthInfoSLZ())
Expand Down Expand Up @@ -92,7 +96,6 @@ class CategoryCreateInputSLZ(serializers.Serializer):
def validate(self, data):
if ProfileCategory.objects.filter(domain=data["domain"]).exists():
raise ValidationError(_("登陆域为 {} 的用户目录已存在").format(data["domain"]))

return data

def create(self, validated_data):
Expand Down Expand Up @@ -122,6 +125,14 @@ def update(self, instance, validated_data):
if activated is not None:
instance.status = CategoryStatus.NORMAL.value if activated else CategoryStatus.INACTIVE.value
should_updated_fields.append("status")
# 变更同步任务使能状态
logger.info(
"going to change periodic task status<%s> for Category-<%s>, the category type is %s",
activated,
instance.id,
instance.type,
)
change_periodic_sync_task_status(instance.id, activated)

display_name = validated_data.get("display_name")
if display_name:
Expand Down
2 changes: 1 addition & 1 deletion src/api/bkuser_core/api/web/category/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,7 @@ def delete(self, request, *args, **kwargs):

if instance.default:
raise error_codes.CANNOT_DELETE_DEFAULT_CATEGORY

# 不可删除非停用目录
if instance.status != CategoryStatus.INACTIVE.value:
raise error_codes.CANNOT_DELETE_ACTIVE_CATEGORY

Expand Down
9 changes: 6 additions & 3 deletions src/api/bkuser_core/api/web/recycle_bin/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,10 @@


class CategoryCheckMessageEnum(AutoNameEnum):
REPEATED_CONNECTION_URL = auto()
REPEATED_DISPLAY_NAME = auto()
DUPLICATE_DISPLAY_NAME = auto()
DUPLICATE_DOMAIN = auto()

_choices_labels = ((REPEATED_CONNECTION_URL, "相同ldap连接域"), (REPEATED_DISPLAY_NAME, "相同目录吗"))
_choices_labels = (
(DUPLICATE_DISPLAY_NAME, "目录名称重复"),
(DUPLICATE_DOMAIN, "目录登录域重复"),
)
8 changes: 3 additions & 5 deletions src/api/bkuser_core/api/web/recycle_bin/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -98,20 +98,18 @@ class Meta:


class BatchCategoryRevertInputSlZ(serializers.Serializer):
deleted_category_ids = serializers.ListField(required=True, help_text="被软删除的目录id列表")
category_ids = serializers.ListField(required=True, help_text="被软删除的目录id列表")


class BatchCategoryHardDeleteInputSlZ(serializers.Serializer):
category_ids = serializers.CharField(required=True, help_text="进行硬删除的对象id列表")
category_ids = serializers.ListField(required=True, help_text="进行硬删除的对象id列表")


class CategoryRevertCheckResultOutputSlZ(serializers.Serializer):
category_id = serializers.IntegerField(help_text="目录id")
category_display_name = serializers.CharField(help_text="目录名称")
check_status = serializers.BooleanField(help_text="检查结果")
check_result = serializers.BooleanField(help_text="检查结果")
error_message = serializers.CharField(help_text="检查信息")


class CategoryRevertResultOutputSlZ(serializers.Serializer):
successful_count = serializers.IntegerField(help_text="目录还原成功个数")
failed_count = serializers.IntegerField(help_text="目录还原失败个数")
6 changes: 5 additions & 1 deletion src/api/bkuser_core/api/web/recycle_bin/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@
views.RecycleBinBatchCategoryRevertCheckApi.as_view(),
name="recycle_bin.category.revert.check",
),
path("categories/revert/", views.RecycleBinBatchCategoryRevertApi.as_view(), name="recycle_bin.category.revert"),
path(
"categories/revert/",
views.RecycleBinBatchCategoryRevertApi.as_view(),
name="recycle_bin.category.revert",
),
path(
"categories/hard_delete/",
views.RecycleBinCategoryBatchHardDeleteApi.as_view(),
Expand Down
41 changes: 41 additions & 0 deletions src/api/bkuser_core/api/web/recycle_bin/utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# -*- coding: utf-8 -*-
"""
TencentBlueKing is pleased to support the open source community by making 蓝鲸智云-用户管理(Bk-User) available.
Copyright (C) 2017-2021 THL A29 Limited, a Tencent company. All rights reserved.
Licensed under the MIT License (the "License"); you may not use this file except in compliance with the License.
You may obtain a copy of the License at http://opensource.org/licenses/MIT
Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
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 bkuser_core.api.web.recycle_bin.constants import CategoryCheckMessageEnum
from bkuser_core.categories.models import ProfileCategory


def check_conflict_before_revert_category(deleted_category: "ProfileCategory") -> dict:

# 当前使能的目录(停用/启用中的目录)
enabled_categories = ProfileCategory.objects.filter(enabled=True)

# 开始检查
# 检查结果初始化
check_detail: dict = {
"category_id": deleted_category.id,
"check_status": True,
"error_message": "",
}

# 重名检测
if deleted_category.display_name in enabled_categories.values_list("display_name", flat=True):
check_detail["check_status"] = False
check_detail["error_message"] = CategoryCheckMessageEnum.DUPLICATE_DISPLAY_NAME.value
return check_detail

# 重登录域检测
elif deleted_category.domain in enabled_categories.values_list("domain", flat=True):
check_detail["check_status"] = False
check_detail["error_message"] = CategoryCheckMessageEnum.DUPLICATE_DOMAIN.value
return check_detail

return check_detail
193 changes: 48 additions & 145 deletions src/api/bkuser_core/api/web/recycle_bin/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
from rest_framework import generics
from rest_framework.response import Response

from bkuser_core.api.web.recycle_bin.constants import CategoryCheckMessageEnum
from bkuser_core.api.web.recycle_bin.serializers import (
BatchCategoryHardDeleteInputSlZ,
BatchCategoryRevertInputSlZ,
Expand All @@ -26,17 +25,19 @@
RecycleBinProfileOutputSlZ,
RecycleBinSearchInputSlZ,
)
from bkuser_core.api.web.recycle_bin.utils import check_conflict_before_revert_category
from bkuser_core.api.web.utils import get_category_display_name_map
from bkuser_core.api.web.viewset import CustomPagination
from bkuser_core.categories.constants import CategoryStatus
from bkuser_core.categories.models import ProfileCategory
from bkuser_core.categories.signals import post_category_hard_delete, post_category_revert
from bkuser_core.departments.models import Department, DepartmentThroughModel
from bkuser_core.common.error_codes import error_codes
from bkuser_core.departments.models import Department
from bkuser_core.profiles.constants import ProfileStatus
from bkuser_core.profiles.models import LeaderThroughModel, Profile
from bkuser_core.profiles.models import Profile
from bkuser_core.recycle_bin.constants import RecycleBinObjectType
from bkuser_core.recycle_bin.models import RecycleBin
from bkuser_core.user_settings.models import Setting
from bkuser_core.recycle_bin.signals import post_category_hard_delete, post_category_revert
from bkuser_core.recycle_bin.tasks import hard_delete_category_related_resource

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -193,59 +194,14 @@ def post(self, request, *args, **kwargs):
data = input_slz.validated_data

# 待还原,需进行检查的软删除目录
deleted_category_ids = data["deleted_category_ids"]
deleted_categories = ProfileCategory.objects.filter(
id__in=deleted_category_ids, enabled=False, status=CategoryStatus.DELETED.value
)
if not deleted_categories:
return Response()

# 当前使能的目录(停用/启用中的目录)
enabled_categories = ProfileCategory.objects.filter(enabled=True)
category_display_name_list = enabled_categories.values_list("display_name", flat=True)

# 获取当前使能的mad/ldap 的连接域
enabled_connection_url_list = Setting.objects.filter(
category_id__in=enabled_categories.values_list("id", flat=True), meta__key="connection_url"
).values_list("value", flat=True)

# 提前暴露 待还原 mad/ldap目录的connection_url
deleted_categories_connection_url = Setting.objects.filter(
category_id__in=deleted_category_ids, meta__key="connection_url"
).values("category_id", "value")
deleted_category_urls_map: dict = {}
for item in deleted_categories_connection_url:
deleted_category_urls_map[item["category_id"]] = item["value"]

# 开始检查
check_result_list: list = []
for del_category in deleted_categories:
# 检查结果初始化
check_detail: dict = {
"category_id": del_category.id,
"category_display_name": del_category.display_name,
"check_status": True,
"error_message": "",
}

# 重名检测
if del_category.display_name in category_display_name_list:
check_detail["check_status"] = False
check_detail["error_message"] = CategoryCheckMessageEnum.REPEATED_DISPLAY_NAME.value
check_result_list.append(check_detail)
continue

# 连接域检查, connection_url=None 不是local就是未配置完全的ldap/mad
connection_url = deleted_category_urls_map.get(del_category.id, None)
if connection_url and connection_url in enabled_connection_url_list:
check_detail["check_status"] = False
check_detail["error_message"] = CategoryCheckMessageEnum.REPEATED_CONNECTION_URL.value
check_result_list.append(check_detail)
continue

check_result_list.append(check_detail)
deleted_category_ids = data["category_ids"]
deleted_categories = ProfileCategory.objects.filter(id__in=deleted_category_ids)
check_detail_list: list = []
for deleted_category in deleted_categories:
check_detail = check_conflict_before_revert_category(deleted_category)
check_detail_list.append(check_detail)

return Response(data=self.serializer_class(instance=check_result_list, many=True).data)
return Response(data=self.serializer_class(instance=check_detail_list, many=True).data)


class RecycleBinBatchCategoryRevertApi(generics.CreateAPIView):
Expand All @@ -257,67 +213,37 @@ def post(self, request, *args, **kwargs):
input_slz.is_valid(raise_exception=True)
data = input_slz.validated_data

# 还原软删除目录状态
reverting_category_ids = data["deleted_category_ids"]
reverting_categories = ProfileCategory.objects.filter(
id__in=reverting_category_ids, enabled=False, status=ProfileStatus.DELETED.value
)

# 当前使能的目录(停用/启用中的目录)
enabled_categories = ProfileCategory.objects.filter(enabled=True)
enabled_category_display_names = enabled_categories.values_list("display_name", flat=True)

# 获取当前使能的mad/ldap 的连接域
enabled_categories_connection_urls = Setting.objects.filter(
category_id__in=enabled_categories.values_list("id", flat=True), meta__key="connection_url"
).values_list("value", flat=True)

# 提前暴露待还原 mad/ldap目录的connection_url
reverting_categories_connection_settings = Setting.objects.filter(
category_id__in=reverting_category_ids, meta__key="connection_url"
).values("category_id", "value")
reverting_category_urls: dict = {}
for item in reverting_categories_connection_settings:
reverting_category_urls[item["category_id"]] = item["value"]
# 二次检查
check_result_list: list = []
reverting_category_ids = data["category_ids"]
deleted_categories = ProfileCategory.objects.filter(id__in=reverting_category_ids)
for deleted_category in deleted_categories:
check_detail = check_conflict_before_revert_category(deleted_category)
check_result_list.append(check_detail)

# 还原结果初始化
revert_results: dict = {
"successful_count": 0,
"failed_count": 0,
}
# 成功还原的目录id
reverted_category_ids: list = []

for category in reverting_categories:
# 二次检查
# 避免还原过程中,同名目录出现
if category.display_name in enabled_category_display_names:
logger.error(
"Category<%s-%s> get a repeated display_name<%s>",
category.id,
category.display_name,
category.display_name,
)
revert_results["failed_count"] += 1
continue

# 获取不到则可能是未配置,或者为本地目录
connection_url = reverting_category_urls.get(category.id, None)
if connection_url and connection_url in enabled_categories_connection_urls:
logger.error(
"Category<%s-%s> get a repeated connection url<%s>",
category.id,
category.display_name,
category.display_name,
with transaction.atomic():
for result in check_result_list:
if not result["check_status"]:
logger.error(
"category %s check conflict failed: %s", result["category_id"], result["error_message"]
)
raise error_codes.REVERT_CATEGORIES_FAILED.f(
category_id=result["category_id"],
error_message=result["error_message"],
)

category = ProfileCategory.objects.get(id=result["category_id"])
category.revert()
# 恢复原本的定时任务,增加审计日志
post_category_revert.send_robust(
sender=self, instance=category, operator=request.operator, extra_values={"request": request}
)
revert_results["failed_count"] += 1
continue

category.revert()
# 善后: 恢复原本的定时任务,增加审计日志
post_category_revert.send_robust(
sender=self, instance=category, operator=request.operator, extra_values={"request": request}
)
revert_results["successful_count"] += 1
reverted_category_ids.append(category.id)

Expand All @@ -331,45 +257,22 @@ class RecycleBinCategoryBatchHardDeleteApi(generics.DestroyAPIView):
queryset = RecycleBin.objects.filter(object_type=RecycleBinObjectType.CATEGORY.value)

def destroy(self, request, *args, **kwargs):
slz = BatchCategoryHardDeleteInputSlZ(data=request.query_params)
slz = BatchCategoryHardDeleteInputSlZ(data=request.data)
slz.is_valid(raise_exception=True)
data = slz.validated_data

# 删除关联资源
categories_ids = data["category_ids"].split(",")
with transaction.atomic():
categories = ProfileCategory.objects.filter(id__in=categories_ids)

# 上下级关系删除
logger.info("Categories %s: clear leaders and profiles' relationship.", categories_ids)
relate_profiles = Profile.objects.filter(category_id__in=categories_ids)
LeaderThroughModel.objects.filter(from_profile_id__in=relate_profiles).delete()

# 人员-部门关系删除
logger.info("Categories %s: clear departments and profiles' relationship", categories_ids)
relate_departments = Department.objects.filter(category_id__in=categories_ids)
DepartmentThroughModel.objects.filter(
department_id__in=relate_departments.values_list("id", flat=True)
).delete()

# 清理资源: 人员,部门,目录设置
logger.info("Categories: clear departments, profiles and settings ", categories_ids)
Setting.objects.filter(category_id__in=categories_ids).delete()
relate_departments.delete()
relate_profiles.delete()

# 删除回收站记录
self.queryset.filter(object_id__in=categories_ids).delete()

# 善后,删除定时任务,增加审计日志
[
post_category_hard_delete.send_robust(
sender=self, instance=instance, operator=request.operator, extra_values={"request": request}
)
for instance in categories
]
categories_ids = data["category_ids"]
categories = ProfileCategory.objects.filter(
id__in=categories_ids, enabled=False, status=CategoryStatus.DELETED.value
)

# 删除目录
categories.delete()
for item in categories:
# 增加审计日志, 删除同步功能
post_category_hard_delete.send_robust(
sender=self, instance=item, operator=request.operator, extra_values={"request": request}
)
# 删除人员,部门,关系,目录设置
hard_delete_category_related_resource.delay(category_id=item.id)

return Response()
Loading

0 comments on commit 93856d2

Please sign in to comment.