Skip to content

Commit

Permalink
feat: create data_source user #1154
Browse files Browse the repository at this point in the history
  • Loading branch information
Canway-shiisa committed Aug 16, 2023
1 parent b39162d commit 053cbbc
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 0 deletions.
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

0 comments on commit 053cbbc

Please sign in to comment.