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: create data_source user #1154 #1164

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions src/bk-user/bkuser/apis/web/data_source/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# -*- 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.
"""
35 changes: 35 additions & 0 deletions src/bk-user/bkuser/apis/web/data_source/serializers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# -*- 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 rest_framework import serializers

from bkuser.apps.data_source.models import DataSourceUser


class UserCreateInputSLZ(serializers.ModelSerializer):
department_ids = serializers.ListField(help_text="部门", child=serializers.IntegerField(), default=[])
leader_ids = serializers.ListField(help_text="上级", child=serializers.IntegerField(), default=[])

class Meta:
model = DataSourceUser
fields = [
"username",
"full_name",
"email",
"phone_country_code",
"phone",
"logo",
"department_ids",
"leader_ids",
]


class UserCreateOutputSLZ(serializers.Serializer):
id = serializers.CharField(help_text="数据源用户ID")
17 changes: 17 additions & 0 deletions src/bk-user/bkuser/apis/web/data_source/urls.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# -*- 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 django.urls import path

from bkuser.apis.web.data_source import views

urlpatterns = [
path("<int:id>/users/", views.DataSourceUserListCreateApi.as_view(), name="data_source.list_create"),
]
80 changes: 80 additions & 0 deletions src/bk-user/bkuser/apis/web/data_source/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# -*- 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 drf_yasg.utils import swagger_auto_schema
from rest_framework import generics, status
from rest_framework.response import Response

from bkuser.apis.web.data_source.serializers import UserCreateInputSLZ, UserCreateOutputSLZ
from bkuser.apps.data_source.models import DataSource, DataSourceUser
from bkuser.biz.data_source_organization import (
DataSourceOrganizationHandler,
DataSourceUserBaseInfo,
DataSourceUserRelationInfo,
)
from bkuser.common.error_codes import error_codes


class DataSourceUserListCreateApi(generics.ListCreateAPIView):
pagination_class = None

@swagger_auto_schema(
operation_description="新建数据源用户",
query_serializer=UserCreateInputSLZ(),
responses={status.HTTP_201_CREATED: UserCreateOutputSLZ(many=True)},
tags=["data_source"],
)
def post(self, request, *args, **kwargs):
slz = UserCreateInputSLZ(data=request.data)
slz.is_valid(raise_exception=True)
data = slz.validated_data
data_source_id = kwargs["id"]

# 校验数据源是否存在
try:
data_source = DataSource.objects.get(id=data_source_id)
except Exception:
raise error_codes.DATA_SOURCE_NOT_EXIST

# 不允许对非本地数据源进行用户新增操作
else:
if data_source.plugin.id != "local":
raise error_codes.CANNOT_CREATE_USER

# 校验是否已存在该用户
try:
DataSourceUser.objects.get(
username=data["username"],
data_source=data_source,
)
except Exception:
pass

else:
raise error_codes.DATA_SOURCE_USER_ALREADY_EXISTED

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

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

user_id = DataSourceOrganizationHandler.create_user(
data_source=data_source, base_user_info=base_user_info, relation_info=relation_info
)
return Response({"id": user_id})
2 changes: 2 additions & 0 deletions src/bk-user/bkuser/apis/web/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,6 @@

urlpatterns = [
path("tenants/", include("bkuser.apis.web.tenant.urls")),
path("data_source/", include("bkuser.apis.web.data_source.urls")),

]
9 changes: 9 additions & 0 deletions src/bk-user/bkuser/apps/data_source/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from django.db import models
from mptt.models import MPTTModel, TreeForeignKey

from bkuser.apps.data_source.validators import validate_phone, validate_username
from bkuser.common.models import TimestampedModel


Expand Down Expand Up @@ -68,6 +69,14 @@ class Meta:
("full_name", "data_source"),
]

def custom_validate(self):
validate_username(self.username)
validate_phone(self.phone_country_code, self.phone)

def save(self, force_insert=False, force_update=False, using=None, update_fields=None):
self.custom_validate()
super().save(force_insert, force_update, using, update_fields)


class LocalDataSourceIdentityInfo(TimestampedModel):
"""
Expand Down
50 changes: 50 additions & 0 deletions src/bk-user/bkuser/apps/data_source/validators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# -*- 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.
"""
import logging
import re

import phonenumbers
from django.utils.translation import gettext_lazy as _
from phonenumbers import region_code_for_country_code
from rest_framework.exceptions import ValidationError

USERNAME_REGEX = r"^(\d|[a-zA-Z])([a-zA-Z0-9._-]){0,31}"
logger = logging.getLogger(__name__)
CHINESE_REGION = "CN"
CHINESE_PHONE_LENGTH = 11


def validate_username(value):
if not re.fullmatch(re.compile(USERNAME_REGEX), value):
raise ValidationError(_("{} 不符合 username 命名规范").format(value))


def validate_phone(phone_country_code: str, phone: str):
try:
# 根据国家码获取对应地区码
region = region_code_for_country_code(int(phone_country_code))

except phonenumbers.NumberParseException:
logger.debug("failed to parse phone_country_code: %s, ", phone_country_code)

else:
# phonenumbers库在验证号码的时:过短会解析为有效号码,超过250的字节才算超长
# =》所以这里需要显式做中国号码的长度校验
if region == CHINESE_REGION and len(phone) != CHINESE_PHONE_LENGTH:
raise ValidationError(_("{} 不符合 长度要求").format(phone))

try:
# 按照指定地区码解析手机号
phonenumbers.parse(phone, region)

except Exception: # pylint: disable=broad-except
logger.debug("failed to parse phone number: %s", phone)
raise ValidationError
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Generated by Django 3.2.20 on 2023-08-10 12:17

from django.db import migrations, models
import django.db.models.deletion
import django.db.models.manager
import mptt.fields


class Migration(migrations.Migration):

initial = True

dependencies = [
]

operations = [
migrations.CreateModel(
name='DataSourceUser',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('data_source_id', models.IntegerField(verbose_name='数据源 ID')),
('username', models.CharField(max_length=128, verbose_name='用户名')),
('full_name', models.CharField(max_length=128, verbose_name='姓名')),
('email', models.EmailField(blank=True, default='', max_length=254, null=True, verbose_name='邮箱')),
('phone', models.CharField(max_length=32, verbose_name='手机号')),
('phone_country_code', models.CharField(blank=True, default='86', max_length=16, null=True, verbose_name='手机国际区号')),
('logo', models.TextField(blank=True, default='', max_length=256, null=True, verbose_name='Logo')),
('extras', models.JSONField(default=dict, verbose_name='自定义字段')),
('leader', models.ManyToManyField(blank=True, related_name='subordinate_staff', to='data_source_organization.DataSourceUser')),
],
options={
'ordering': ['id'],
'unique_together': {('username', 'data_source_id')},
},
),
migrations.CreateModel(
name='LocalDataSourceIdentityInfo',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('password', models.CharField(blank=True, default='', max_length=255, null=True, verbose_name='用户密码')),
('password_updated_at', models.DateTimeField(blank=True, null=True, verbose_name='密码最后更新时间')),
('password_expired_at', models.DateTimeField(blank=True, null=True, verbose_name='密码过期时间')),
('data_source_id', models.IntegerField(verbose_name='数据源 ID')),
('username', models.CharField(max_length=128, verbose_name='用户名')),
('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='data_source_organization.datasourceuser')),
],
options={
'unique_together': {('username', 'data_source_id')},
},
),
migrations.CreateModel(
name='DataSourceDepartment',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('data_source_id', models.IntegerField(verbose_name='数据源 ID')),
('code', models.CharField(blank=True, max_length=128, null=True, verbose_name='部门标识')),
('name', models.CharField(max_length=255, verbose_name='部门名称')),
('extras', models.JSONField(default=dict, verbose_name='自定义字段')),
('order', models.IntegerField(default=1, verbose_name='顺序')),
('lft', models.PositiveIntegerField(editable=False)),
('rght', models.PositiveIntegerField(editable=False)),
('tree_id', models.PositiveIntegerField(db_index=True, editable=False)),
('level', models.PositiveIntegerField(editable=False)),
('parent', mptt.fields.TreeForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='data_source_organization.datasourcedepartment')),
('users', models.ManyToManyField(blank=True, related_name='departments', to='data_source_organization.DataSourceUser', verbose_name='成员')),
],
options={
'verbose_name': '部门表',
'verbose_name_plural': '部门表',
'ordering': ['id'],
'index_together': {('tree_id', 'lft', 'rght'), ('parent_id', 'tree_id', 'lft')},
},
managers=[
('tree_objects', django.db.models.manager.Manager()),
],
),
]
Loading
Loading