Skip to content

Commit

Permalink
feat: support use display_name for web page updater or operator (#1468)
Browse files Browse the repository at this point in the history
  • Loading branch information
narasux authored Dec 11, 2023
1 parent 7e3f005 commit a45e50b
Show file tree
Hide file tree
Showing 8 changed files with 82 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/bk-login/bklogin/authentication/api_views.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ def get(self, request, *args, **kwargs):
"tenant_id": user.tenant_id,
"full_name": user.full_name,
"source_username": user.username,
"display_name": user.display_name,
"language": user.language,
"time_zone": user.time_zone,
}
Expand Down
1 change: 1 addition & 0 deletions src/bk-login/bklogin/component/bk_user/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class TenantUserInfo(BaseModel):


class TenantUserDetailInfo(TenantUserInfo):
display_name: str
tenant_id: str
language: str
time_zone: str
6 changes: 6 additions & 0 deletions src/bk-user/bkuser/apis/login/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
from bkuser.apps.data_source.constants import DATA_SOURCE_USERNAME_REGEX
from bkuser.apps.idp.constants import IdpStatus
from bkuser.apps.idp.models import Idp
from bkuser.apps.tenant.models import TenantUser
from bkuser.biz.tenant import TenantUserHandler


class LocalUserCredentialAuthenticateInputSLZ(serializers.Serializer):
Expand Down Expand Up @@ -131,10 +133,14 @@ class TenantUserRetrieveOutputSLZ(serializers.Serializer):
id = serializers.CharField(help_text="用户 ID")
username = serializers.ReadOnlyField(help_text="用户名", source="data_source_user.username")
full_name = serializers.ReadOnlyField(help_text="用户姓名", source="data_source_user.full_name")
display_name = serializers.SerializerMethodField(help_text="用户姓名")
language = serializers.CharField(help_text="语言")
time_zone = serializers.CharField(help_text="时区")

tenant_id = serializers.CharField(help_text="用户所在租户 ID")

def get_display_name(self, obj: TenantUser) -> str:
return TenantUserHandler.generate_tenant_user_display_name(obj)

class Meta:
ref_name = "login.TenantUserRetrieveOutputSLZ"
10 changes: 8 additions & 2 deletions src/bk-user/bkuser/apis/web/data_source/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class DataSourceSearchOutputSLZ(serializers.Serializer):
plugin_name = serializers.SerializerMethodField(help_text="数据源插件名称")
cooperation_tenants = serializers.SerializerMethodField(help_text="协作公司")
status = serializers.CharField(help_text="数据源状态")
updater = serializers.CharField(help_text="更新者")
updater = serializers.SerializerMethodField(help_text="更新者")
updated_at = serializers.CharField(help_text="更新时间", source="updated_at_display")

def get_plugin_name(self, obj: DataSource) -> str:
Expand All @@ -64,6 +64,9 @@ def get_cooperation_tenants(self, obj: DataSource) -> List[str]:
# TODO 目前未支持数据源跨租户协作,因此该数据均为空
return []

def get_updater(self, obj: DataSource) -> str:
return self.context["user_display_name_map"].get(obj.updater) or obj.updater


class DataSourceFieldMappingSLZ(serializers.Serializer):
"""单个数据源字段映射"""
Expand Down Expand Up @@ -360,14 +363,17 @@ class DataSourceSyncRecordListOutputSLZ(serializers.Serializer):
status = serializers.ChoiceField(help_text="数据源同步状态", choices=SyncTaskStatus.get_choices())
has_warning = serializers.BooleanField(help_text="是否有警告")
trigger = serializers.ChoiceField(help_text="同步触发方式", choices=SyncTaskTrigger.get_choices())
operator = serializers.CharField(help_text="操作人")
operator = serializers.SerializerMethodField(help_text="操作人")
start_at = serializers.SerializerMethodField(help_text="开始时间")
duration = serializers.DurationField(help_text="持续时间")
extras = serializers.JSONField(help_text="额外信息")

def get_data_source_name(self, obj: DataSourceSyncTask) -> str:
return self.context["data_source_name_map"].get(obj.data_source_id)

def get_operator(self, obj: DataSourceSyncTask) -> str:
return self.context["user_display_name_map"].get(obj.operator) or obj.operator

def get_start_at(self, obj: DataSourceSyncTask) -> str:
return obj.start_at_display

Expand Down
18 changes: 13 additions & 5 deletions src/bk-user/bkuser/apis/web/data_source/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
from bkuser.apps.sync.managers import DataSourceSyncManager
from bkuser.apps.sync.models import DataSourceSyncTask
from bkuser.biz.exporters import DataSourceUserExporter
from bkuser.biz.tenant import TenantUserHandler
from bkuser.common.error_codes import error_codes
from bkuser.common.passwd import PasswordGenerator
from bkuser.common.response import convert_workbook_to_response
Expand Down Expand Up @@ -103,7 +104,13 @@ class DataSourceListCreateApi(CurrentUserTenantMixin, generics.ListCreateAPIView
serializer_class = DataSourceSearchOutputSLZ

def get_serializer_context(self):
return {"data_source_plugin_map": dict(DataSourcePlugin.objects.values_list("id", "name"))}
tenant_user_ids = DataSource.objects.filter(
owner_tenant_id=self.get_current_tenant_id(),
).values_list("updater", flat=True)
return {
"data_source_plugin_map": dict(DataSourcePlugin.objects.values_list("id", "name")),
"user_display_name_map": TenantUserHandler.get_tenant_user_display_name_map_by_ids(tenant_user_ids),
}

def get_queryset(self):
slz = DataSourceSearchInputSLZ(data=self.request.query_params)
Expand Down Expand Up @@ -447,11 +454,12 @@ def get_queryset(self):
return queryset

def get_serializer_context(self):
context = super().get_serializer_context()
context["data_source_name_map"] = {
ds.id: ds.name for ds in DataSource.objects.filter(owner_tenant_id=self.get_current_tenant_id())
data_sources = DataSource.objects.filter(owner_tenant_id=self.get_current_tenant_id())
tenant_user_ids = data_sources.values_list("updater", flat=True)
return {
"data_source_name_map": {ds.id: ds.name for ds in data_sources},
"user_display_name_map": TenantUserHandler.get_tenant_user_display_name_map_by_ids(tenant_user_ids),
}
return context

@swagger_auto_schema(
tags=["data_source"],
Expand Down
7 changes: 5 additions & 2 deletions src/bk-user/bkuser/apis/web/idp/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class IdpSearchOutputSLZ(serializers.Serializer):
id = serializers.CharField(help_text="认证源唯一标识")
name = serializers.CharField(help_text="认证源名称")
status = serializers.ChoiceField(help_text="认证源状态", choices=IdpStatus.get_choices())
updater = serializers.CharField(help_text="更新者")
updater = serializers.SerializerMethodField(help_text="更新者")
updated_at = serializers.CharField(help_text="更新时间", source="updated_at_display")
plugin = IdpPluginOutputSLZ(help_text="认证源插件")
matched_data_sources = serializers.SerializerMethodField(help_text="匹配的数据源列表")
Expand All @@ -67,6 +67,9 @@ def get_matched_data_sources(self, obj: Idp) -> List[str]:
if r.data_source_id in data_source_name_map
]

def get_updater(self, obj: Idp) -> str:
return self.context["user_display_name_map"].get(obj.updater) or obj.updater


def _validate_duplicate_idp_name(name: str, tenant_id: str, idp_id: str = "") -> str:
"""校验IDP 是否重名"""
Expand All @@ -89,7 +92,7 @@ def _validate_source_field(value):
if not re.fullmatch(SOURCE_FIELD_REGEX, value):
raise ValidationError(
_(
"{} 不符合认证源字段的命名规范: 由3-32位字母、数字、下划线(_)、连接符(-)字符组成,以字母开头并以字母或数字结尾" # noqa: E501
"{} 不符合认证源字段的命名规范: 由3-32位字母、数字、下划线(_)、连接符(-)字符组成,以字母开头并以字母或数字结尾", # noqa: E501
).format(value),
)

Expand Down
10 changes: 9 additions & 1 deletion src/bk-user/bkuser/apis/web/idp/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from bkuser.apps.idp.models import Idp, IdpPlugin
from bkuser.apps.permission.constants import PermAction
from bkuser.apps.permission.permissions import perm_class
from bkuser.biz.tenant import TenantUserHandler
from bkuser.common.error_codes import error_codes

from .schema import get_idp_plugin_cfg_json_schema, get_idp_plugin_cfg_openapi_schema_map
Expand Down Expand Up @@ -85,7 +86,14 @@ def get_serializer_context(self):
data_source_name_map = dict(
DataSource.objects.filter(owner_tenant_id=self.get_current_tenant_id()).values_list("id", "name")
)
return {"data_source_name_map": data_source_name_map}
tenant_user_ids = Idp.objects.filter(
owner_tenant_id=self.get_current_tenant_id(),
).values_list("updater", flat=True)

return {
"data_source_name_map": data_source_name_map,
"user_display_name_map": TenantUserHandler.get_tenant_user_display_name_map_by_ids(tenant_user_ids),
}

def get_queryset(self):
slz = IdpSearchInputSLZ(data=self.request.query_params)
Expand Down
39 changes: 39 additions & 0 deletions src/bk-user/bkuser/biz/tenant.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@
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 collections import defaultdict
from typing import Any, Dict, List, Optional

from django.conf import settings
from django.contrib.auth import get_user_model
from django.db import transaction
from django.utils import timezone
from django.utils.translation import gettext_lazy as _
Expand All @@ -35,6 +37,8 @@
)
from bkuser.plugins.local.models import PasswordInitialConfig

logger = logging.getLogger(__name__)


class DataSourceUserInfo(BaseModel):
"""数据源用户信息"""
Expand Down Expand Up @@ -263,6 +267,41 @@ def update_tenant_user_email(tenant_user: TenantUser, email_info: TenantUserEmai
tenant_user.custom_email = email_info.custom_email
tenant_user.save()

@staticmethod
def generate_tenant_user_display_name(user: TenantUser) -> str:
# TODO (su) 支持读取表达式并渲染
return f"{user.data_source_user.username} ({user.data_source_user.full_name})"

@staticmethod
def get_tenant_user_display_name_map_by_ids(tenant_user_ids: List[str]) -> Dict[str, str]:
"""
根据指定的租户用户 ID 列表,获取对应的展示用名称列表
:return: {user_id: user_display_name}
"""
# 1. 尝试从 TenantUser 表根据表达式渲染出展示用名称
display_name_map = {
user.id: TenantUserHandler.generate_tenant_user_display_name(user)
for user in TenantUser.objects.select_related("data_source_user").filter(id__in=tenant_user_ids)
}
# 2. 针对可能出现的 TenantUser 中被删除的 user_id,尝试从 User 表获取展示用名称(登录过就有记录)
if not_exists_user_ids := set(tenant_user_ids) - set(display_name_map.keys()):
logger.warning(
"tenant user ids: %s not exists in TenantUser model, try find display name in User Model",
not_exists_user_ids,
)
UserModel = get_user_model() # noqa: N806
for user in UserModel.objects.filter(username__in=not_exists_user_ids):
# FIXME (nan) get_property 有 N+1 的风险,需要处理
display_name_map[user.username] = user.get_property("display_name") or user.username

# 3. 前两种方式都失效,那就给啥 user_id 就返回啥,避免调用的地方还需要处理
if not_exists_user_ids := set(tenant_user_ids) - set(display_name_map.keys()):
for user_id in not_exists_user_ids:
display_name_map[user_id] = user_id

return display_name_map


class TenantHandler:
@staticmethod
Expand Down

0 comments on commit a45e50b

Please sign in to comment.