diff --git a/src/bk-user/bkuser/apis/open_v2/serializers/profilers.py b/src/bk-user/bkuser/apis/open_v2/serializers/profilers.py index 82141f18a..13b7c6f19 100644 --- a/src/bk-user/bkuser/apis/open_v2/serializers/profilers.py +++ b/src/bk-user/bkuser/apis/open_v2/serializers/profilers.py @@ -25,7 +25,7 @@ def validated_fields(self, fields: List[str]) -> List[str]: # [基本] code, qq # [登录&密码相关] password_valid_days, password_update_time, last_login_time, account_expiration_date, # [时间相关] create_time, update_time - # 总返回固定值字段:logo, position, type, role + # 总返回固定值字段:logo, type, role allowed_fields = { # 基础字段 "id", @@ -97,3 +97,7 @@ class DepartmentProfileListInputSLZ(serializers.Serializer): recursive = serializers.BooleanField(help_text="是否递归", required=False, default=False) include_disabled = serializers.BooleanField(help_text="是否包含软删除部门", required=False, default=False) no_page = serializers.BooleanField(help_text="全量返回", required=False, default=False) + + +class ProfileLanguageUpdateInputSLZ(serializers.Serializer): + language = serializers.ChoiceField(help_text="需设置的语言", choices=["zh-cn", "en"]) diff --git a/src/bk-user/bkuser/apis/open_v2/urls.py b/src/bk-user/bkuser/apis/open_v2/urls.py index 5e14c2078..c72bd2ac1 100644 --- a/src/bk-user/bkuser/apis/open_v2/urls.py +++ b/src/bk-user/bkuser/apis/open_v2/urls.py @@ -53,4 +53,10 @@ views.DepartmentProfileListApi.as_view(), name="open_v2.list_department_profiles", ), + # 更新用户语言 + path( + "profiles//languages/", + views.ProfileLanguageUpdateApi.as_view(), + name="open_v2.update_profile_language", + ), ] diff --git a/src/bk-user/bkuser/apis/open_v2/views/__init__.py b/src/bk-user/bkuser/apis/open_v2/views/__init__.py index f09882d46..18f1f39eb 100644 --- a/src/bk-user/bkuser/apis/open_v2/views/__init__.py +++ b/src/bk-user/bkuser/apis/open_v2/views/__init__.py @@ -12,7 +12,7 @@ from .categories import CategoriesListApi from .departments import DepartmentChildrenListApi, DepartmentListApi, DepartmentRetrieveApi, ProfileDepartmentListApi from .edges import DepartmentProfileRelationListApi, ProfileLeaderRelationListApi -from .profilers import DepartmentProfileListApi, ProfileListApi, ProfileRetrieveApi +from .profilers import DepartmentProfileListApi, ProfileLanguageUpdateApi, ProfileListApi, ProfileRetrieveApi __all__ = [ # 目录类 @@ -29,4 +29,5 @@ "ProfileListApi", "ProfileRetrieveApi", "DepartmentProfileListApi", + "ProfileLanguageUpdateApi", ] diff --git a/src/bk-user/bkuser/apis/open_v2/views/profilers.py b/src/bk-user/bkuser/apis/open_v2/views/profilers.py index b736385a2..e44cbd5f4 100644 --- a/src/bk-user/bkuser/apis/open_v2/views/profilers.py +++ b/src/bk-user/bkuser/apis/open_v2/views/profilers.py @@ -24,6 +24,7 @@ from bkuser.apis.open_v2.pagination import LegacyOpenApiPagination from bkuser.apis.open_v2.serializers.profilers import ( DepartmentProfileListInputSLZ, + ProfileLanguageUpdateInputSLZ, ProfileListInputSLZ, ProfileRetrieveInputSLZ, ) @@ -35,6 +36,7 @@ from bkuser.apps.tenant.models import DataSourceDepartment, TenantDepartment, TenantUser from bkuser.biz.tenant import TenantUserHandler from bkuser.common.error_codes import error_codes +from bkuser.common.views import ExcludePatchAPIViewMixin from bkuser.utils.tree import Tree @@ -85,6 +87,7 @@ def build_user_infos(self, tenant_users: QuerySet[TenantUser], fields: List[str] "language": tenant_user.language, "wx_userid": tenant_user.wx_userid, "domain": tenant_user.data_source.domain, + # TODO: 协同需要调整 "category_id": tenant_user.data_source_id, # TODO 1. 支持软删除后需要特殊处理 2. 支持状态时需要特殊处理 "status": "", @@ -280,7 +283,7 @@ def _filter_queryset(self, params: Dict[str, Any]) -> QuerySet[TenantUser]: @staticmethod def _convert_lookup_field(lookup_field: str) -> str: if lookup_field == "id": - return f"data_source_user__{lookup_field}" + return "data_source_user__id" if lookup_field == "username": return "id" if lookup_field == "display_name": @@ -323,7 +326,7 @@ def _convert_optional_inherited_lookup_field(lookup_field: str, value: str, is_e if lookup_field == "telephone": lookup_field = "phone" - # 精确查询 + # 精确查询 if is_exact: return Q( # 继承 @@ -361,7 +364,7 @@ def get(self, request, *args, **kwargs): # TODO 目前 ID 指的是数据源用户 ID,未来支持协同之后,需要重新考虑 filters = {"data_source_user__id": lookup_value} - # TODO (su) 支持软删除后,需要根据 include_disabled 参数判断是返回被删除的部门还是 Raise 404 + # TODO (su) 支持软删除后,需要根据 include_disabled 参数判断是返回被删除的用户还是 Raise 404 tenant_user = TenantUser.objects.select_related("data_source_user").filter(**filters).first() if not tenant_user: raise Http404(f"user {params['lookup_field']}:{kwargs['lookup_value']} not found") @@ -408,7 +411,7 @@ def _get_departments(self, tenant_user: TenantUser) -> List[Dict[str, Any]]: # 查询对应的租户部门 departments = TenantDepartment.objects.filter( - tenant=tenant_user.tenant, data_source_department_id__in=department_ids + tenant_id=tenant_user.tenant_id, data_source_department_id__in=department_ids ).select_related("data_source_department") # 部门的 full_name @@ -458,6 +461,7 @@ def _build_user_info(self, tenant_user: TenantUser, fields: List[str]) -> Dict[s "wx_userid": tenant_user.wx_userid, "wx_openid": tenant_user.wx_openid, "domain": tenant_user.data_source.domain, + # TODO: 协同需要调整 "category_id": tenant_user.data_source_id, # TODO 1. 支持软删除后需要特殊处理 2. 支持状态时需要特殊处理 "status": "", @@ -527,12 +531,14 @@ def _filter_queryset(tenant_dept: TenantDepartment, recursive: bool) -> QuerySet dept_ids = [tenant_dept.data_source_department_id] if recursive: # 根据部门关系,查询部门子孙(包括自身) - rel = DataSourceDepartmentRelation.objects.filter(department=tenant_dept.data_source_department).first() + rel = DataSourceDepartmentRelation.objects.filter( + department_id=tenant_dept.data_source_department_id + ).first() if rel: dept_ids = rel.get_descendants(include_self=True).values_list("department_id", flat=True) # 查询部门下的用户 ID 列表 - user_ids = DataSourceDepartmentUserRelation.objects.filter(department__in=dept_ids).values_list( + user_ids = DataSourceDepartmentUserRelation.objects.filter(department_id__in=dept_ids).values_list( "user_id", flat=True ) @@ -540,3 +546,21 @@ def _filter_queryset(tenant_dept: TenantDepartment, recursive: bool) -> QuerySet return TenantUser.objects.filter( tenant_id=tenant_dept.tenant_id, data_source_user_id__in=user_ids ).select_related("data_source_user") + + +class ProfileLanguageUpdateApi(ExcludePatchAPIViewMixin, LegacyOpenApiCommonMixin, generics.UpdateAPIView): + """更新用户国际化语言""" + + def put(self, request, *args, **kwargs): + slz = ProfileLanguageUpdateInputSLZ(data=request.data) + slz.is_valid(raise_exception=True) + + # TODO (su) 支持软删除后,需要根据 include_disabled 参数判断是返回被删除的用户还是 Raise 404 + tenant_user = TenantUser.objects.filter(id=kwargs["username"]).first() + if not tenant_user: + raise Http404(f"user username:{kwargs['username']} not found") + + tenant_user.language = slz.validated_data["language"] + tenant_user.save(update_fields=["language"]) + + return Response() diff --git a/src/bk-user/bkuser/urls.py b/src/bk-user/bkuser/urls.py index a375e219c..84d50f4a9 100644 --- a/src/bk-user/bkuser/urls.py +++ b/src/bk-user/bkuser/urls.py @@ -24,7 +24,9 @@ # 提供给登录服务使用的内部 API path("api/v1/login/", include("bkuser.apis.login.urls")), # 兼容旧版本(v2)用户管理 OpenAPI - path("api/v2/open/", include("bkuser.apis.open_v2.urls")), + # Q: 这里使用 api/v2 而非 api/v2/open, + # A: 为了保证 ESB 调用的兼容,只需修改 ESB 配置 bk_user host,不需要依赖 ESB 的版本发布 + path("api/v2/", include("bkuser.apis.open_v2.urls")), # 用于监控相关的,比如ping/healthz/sentry/metrics/otel等等 path("", include("bkuser.monitoring.urls")), ]