diff --git a/src/bk-user/bkuser/apps/data_source/initializers.py b/src/bk-user/bkuser/apps/data_source/initializers.py index 998b03bba..336144c7c 100644 --- a/src/bk-user/bkuser/apps/data_source/initializers.py +++ b/src/bk-user/bkuser/apps/data_source/initializers.py @@ -9,6 +9,7 @@ specific language governing permissions and limitations under the License. """ import datetime +from typing import List from django.utils import timezone @@ -44,16 +45,35 @@ class LocalDataSourceIdentityInfoInitializer: def __init__(self, data_source: DataSource): self.data_source = data_source + if not data_source.is_local: + return + self.plugin_cfg = LocalDataSourcePluginConfig(**data_source.plugin_config) self.password_provider = PasswordProvider(self.plugin_cfg) - def initialize(self) -> None: - if self._can_skip_initialize(): + def sync(self) -> None: + """检查指定数据源的所有用户,对没有账密信息的,做初始化,适用于批量同步(导入)的情况""" + if self._can_skip(): + return + + exists_info_user_ids = LocalDataSourceIdentityInfo.objects.filter( + data_source=self.data_source, + ).values_list("user_id", flat=True) + # NOTE:已经存在的账密信息,不会按照最新规则重新生成!不然用户密码就失效了! + waiting_init_users = DataSourceUser.objects.filter( + data_source=self.data_source, + ).exclude(id__in=exists_info_user_ids) + + self._init_users_identity_info(waiting_init_users) + + def initialize(self, user: DataSourceUser) -> None: + """初始化用户身份信息,适用于单个用户创建的情况""" + if self._can_skip(): return - self._init_users_identity_info() + self._init_users_identity_info([user]) - def _can_skip_initialize(self): + def _can_skip(self): """预先判断能否直接跳过""" # 不是本地数据源的,不需要初始化 @@ -66,14 +86,8 @@ def _can_skip_initialize(self): return False - def _init_users_identity_info(self): - exists_infos = LocalDataSourceIdentityInfo.objects.filter(data_source=self.data_source) - exists_info_user_ids = exists_infos.objects.values_list("user_id", flat=True) - # NOTE:已经存在的账密信息,不会按照最新规则重新生成! - waiting_init_users = DataSourceUser.objects.filter( - data_source=self.data_source, - ).exclude(id__in=exists_info_user_ids) - + def _init_users_identity_info(self, users: List[DataSourceUser]): + """初始化用户身份信息""" time_now = timezone.now() expired_at = self._get_password_expired_at(time_now) @@ -88,7 +102,7 @@ def _init_users_identity_info(self): created_at=time_now, updated_at=time_now, ) - for user in waiting_init_users + for user in users ] LocalDataSourceIdentityInfo.objects.bulk_create(waiting_create_infos, batch_size=self.BATCH_SIZE) diff --git a/src/bk-user/bkuser/apps/data_source/migrations/0004_alter_localdatasourceidentityinfo_password.py b/src/bk-user/bkuser/apps/data_source/migrations/0004_alter_localdatasourceidentityinfo_password.py index 5c3490999..6a1fecbb0 100644 --- a/src/bk-user/bkuser/apps/data_source/migrations/0004_alter_localdatasourceidentityinfo_password.py +++ b/src/bk-user/bkuser/apps/data_source/migrations/0004_alter_localdatasourceidentityinfo_password.py @@ -1,14 +1,4 @@ -# -*- 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. -""" -# Generated by Django 3.2.20 on 2023-09-12 03:18 +# Generated by Django 3.2.20 on 2023-09-19 09:45 import blue_krill.models.fields from django.db import migrations @@ -24,6 +14,6 @@ class Migration(migrations.Migration): migrations.AlterField( model_name='localdatasourceidentityinfo', name='password', - field=blue_krill.models.fields.EncryptField(blank=True, default='', max_length=255, null=True), + field=blue_krill.models.fields.EncryptField(blank=True, default='', max_length=255, null=True, verbose_name='用户密码'), ), ] diff --git a/src/bk-user/bkuser/apps/data_source/models.py b/src/bk-user/bkuser/apps/data_source/models.py index 581e67299..b842c53a3 100644 --- a/src/bk-user/bkuser/apps/data_source/models.py +++ b/src/bk-user/bkuser/apps/data_source/models.py @@ -92,11 +92,11 @@ class LocalDataSourceIdentityInfo(TimestampedModel): """ user = models.OneToOneField(DataSourceUser, on_delete=models.CASCADE) - password = EncryptField("用户密码", null=True, blank=True, default="", max_length=255) + password = EncryptField(verbose_name="用户密码", null=True, blank=True, default="", max_length=255) password_updated_at = models.DateTimeField("密码最后更新时间", null=True, blank=True) password_expired_at = models.DateTimeField("密码过期时间", null=True, blank=True) - # data_source_id/username为冗余字段,便于认证时快速匹配 + # data_source / username 为冗余字段,便于认证时快速匹配 data_source = models.ForeignKey(DataSource, on_delete=models.DO_NOTHING, db_constraint=False) username = models.CharField("用户名", max_length=128) diff --git a/src/bk-user/bkuser/apps/sync/handlers.py b/src/bk-user/bkuser/apps/sync/handlers.py index 0c9fbf420..013473e57 100644 --- a/src/bk-user/bkuser/apps/sync/handlers.py +++ b/src/bk-user/bkuser/apps/sync/handlers.py @@ -18,9 +18,9 @@ @receiver(post_sync_data_source) -def initialize_local_data_source_identity_info(sender, data_source: DataSource, **kwargs): +def sync_local_data_source_identity_infos(sender, data_source: DataSource, **kwargs): """在完成数据源同步后,需要对本地数据源的用户账密信息做初始化""" - LocalDataSourceIdentityInfoInitializer(data_source).initialize() + LocalDataSourceIdentityInfoInitializer(data_source).sync() @receiver(post_sync_data_source) diff --git a/src/bk-user/bkuser/biz/data_source_organization.py b/src/bk-user/bkuser/biz/data_source_organization.py index 9cfed69b7..b32c776f1 100644 --- a/src/bk-user/bkuser/biz/data_source_organization.py +++ b/src/bk-user/bkuser/biz/data_source_organization.py @@ -14,6 +14,7 @@ from django.db import transaction from pydantic import BaseModel +from bkuser.apps.data_source.initializers import LocalDataSourceIdentityInfoInitializer from bkuser.apps.data_source.models import ( DataSource, DataSourceDepartment, @@ -82,6 +83,9 @@ def create_user( data_source=data_source, code=gen_code(base_user_info.username), **base_user_info.model_dump() ) + # 为本地数据源用户初始化账密信息 + LocalDataSourceIdentityInfoInitializer(data_source).initialize(user) + # 批量创建数据源用户-部门关系 department_user_relation_objs = [ DataSourceDepartmentUserRelation(department_id=dept_id, user_id=user.id) diff --git a/src/bk-user/bkuser/biz/tenant.py b/src/bk-user/bkuser/biz/tenant.py index 1df2d2281..9c1ea2cca 100644 --- a/src/bk-user/bkuser/biz/tenant.py +++ b/src/bk-user/bkuser/biz/tenant.py @@ -16,6 +16,7 @@ from django.utils.translation import gettext_lazy as _ from pydantic import BaseModel +from bkuser.apps.data_source.initializers import LocalDataSourceIdentityInfoInitializer from bkuser.apps.data_source.models import DataSourceDepartmentRelation, DataSourceUser from bkuser.apps.tenant.models import Tenant, TenantDepartment, TenantManager, TenantUser from bkuser.biz.data_source import ( @@ -290,6 +291,9 @@ def create_with_managers( if tenant_manager_objs: TenantManager.objects.bulk_create(tenant_manager_objs) + # 批量为租户管理员创建账密信息 + LocalDataSourceIdentityInfoInitializer(data_source).sync() + return tenant_info.id @staticmethod diff --git a/src/bk-user/tests/apps/data_source/__init__.py b/src/bk-user/tests/apps/data_source/__init__.py new file mode 100644 index 000000000..1060b7bf4 --- /dev/null +++ b/src/bk-user/tests/apps/data_source/__init__.py @@ -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. +""" diff --git a/src/bk-user/tests/apps/data_source/test_initializers.py b/src/bk-user/tests/apps/data_source/test_initializers.py new file mode 100644 index 000000000..c1aacf645 --- /dev/null +++ b/src/bk-user/tests/apps/data_source/test_initializers.py @@ -0,0 +1,46 @@ +# -*- 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 pytest +from bkuser.apps.data_source.initializers import LocalDataSourceIdentityInfoInitializer +from bkuser.apps.data_source.models import DataSourceUser, LocalDataSourceIdentityInfo + +pytestmark = pytest.mark.django_db + + +class TestLocalDataSourceIdentityInfoInitializer: + """测试本地数据源账密初始化""" + + def test_sync(self, full_local_data_source): + """批量同步的情况""" + LocalDataSourceIdentityInfoInitializer(full_local_data_source).sync() + assert ( + LocalDataSourceIdentityInfo.objects.filter(data_source=full_local_data_source).count() + == DataSourceUser.objects.filter(data_source=full_local_data_source).count() + ) + + def test_initialize(self, full_local_data_source): + """单个初始化的情况""" + user = DataSourceUser.objects.filter(data_source=full_local_data_source).first() + LocalDataSourceIdentityInfoInitializer(full_local_data_source).initialize(user) + assert LocalDataSourceIdentityInfo.objects.filter(data_source=full_local_data_source).count() == 1 + + def test_skip_not_local_data_source(self, full_general_data_source): + """不是本地数据源的,同步不会生效""" + LocalDataSourceIdentityInfoInitializer(full_general_data_source).sync() + assert not LocalDataSourceIdentityInfo.objects.filter(data_source=full_general_data_source).exists() + + def test_skip_not_account_password_login_data_source(self, full_local_data_source): + """没有启用账密登录的,同步不会生效""" + full_local_data_source.plugin_config["enable_account_password_login"] = False + full_local_data_source.save() + + LocalDataSourceIdentityInfoInitializer(full_local_data_source).sync() + assert not LocalDataSourceIdentityInfo.objects.filter(data_source=full_local_data_source).exists() diff --git a/src/bk-user/tests/assets/fake_users.xlsx b/src/bk-user/tests/assets/fake_users.xlsx new file mode 100644 index 000000000..f9718cc5f Binary files /dev/null and b/src/bk-user/tests/assets/fake_users.xlsx differ diff --git a/src/bk-user/tests/conftest.py b/src/bk-user/tests/conftest.py index dc814c0ef..e41754062 100644 --- a/src/bk-user/tests/conftest.py +++ b/src/bk-user/tests/conftest.py @@ -13,8 +13,12 @@ from bkuser.auth.models import User from tests.fixtures.data_source import ( # noqa: F401 + bare_general_data_source, bare_local_data_source, + full_general_data_source, full_local_data_source, + general_ds_plugin, + general_ds_plugin_config, local_ds_plugin, local_ds_plugin_config, ) diff --git a/src/bk-user/tests/fixtures/data_source.py b/src/bk-user/tests/fixtures/data_source.py index 492800ecf..5faf8d7ce 100644 --- a/src/bk-user/tests/fixtures/data_source.py +++ b/src/bk-user/tests/fixtures/data_source.py @@ -11,17 +11,10 @@ from typing import Any, Dict import pytest -from bkuser.apps.data_source.models import ( - DataSource, - DataSourceDepartment, - DataSourceDepartmentRelation, - DataSourceDepartmentUserRelation, - DataSourcePlugin, - DataSourceUser, - DataSourceUserLeaderRelation, -) +from bkuser.apps.data_source.models import DataSource, DataSourcePlugin from bkuser.plugins.constants import DataSourcePluginEnum from bkuser.plugins.local.constants import NotificationMethod, NotificationScene, PasswordGenerateMethod +from tests.test_utils.data_source import init_data_source_users_depts_and_relations from tests.test_utils.helpers import generate_random_string from tests.test_utils.tenant import DEFAULT_TENANT @@ -145,168 +138,40 @@ def bare_local_data_source(local_ds_plugin_config, local_ds_plugin) -> DataSourc @pytest.fixture() -def full_local_data_source(local_ds_plugin_config, local_ds_plugin) -> DataSource: +def full_local_data_source(bare_local_data_source) -> DataSource: """携带用户,部门信息的本地数据源""" + init_data_source_users_depts_and_relations(bare_local_data_source) + return bare_local_data_source - # 数据源 - ds = DataSource.objects.create( - name=generate_random_string(), - owner_tenant_id=DEFAULT_TENANT, - plugin=local_ds_plugin, - plugin_config=local_ds_plugin_config, - ) - # 数据源用户 - zhangsan = DataSourceUser.objects.create( - code="Employee-3", - username="zhangsan", - full_name="张三", - email="zhangsan@m.com", - phone="13512345671", - data_source=ds, - ) - lisi = DataSourceUser.objects.create( - code="Employee-4", - username="lisi", - full_name="李四", - email="lisi@m.com", - phone="13512345672", - data_source=ds, - ) - wangwu = DataSourceUser.objects.create( - code="Employee-5", - username="wangwu", - full_name="王五", - email="wangwu@m.com", - phone="13512345673", - data_source=ds, - ) - zhaoliu = DataSourceUser.objects.create( - code="Employee-6", - username="zhaoliu", - full_name="赵六", - email="zhaoliu@m.com", - phone="13512345674", - data_source=ds, - ) - liuqi = DataSourceUser.objects.create( - code="Employee-7", - username="liuqi", - full_name="柳七", - email="liuqi@m.com", - phone="13512345675", - data_source=ds, - ) - maiba = DataSourceUser.objects.create( - code="Employee-8", - username="maiba", - full_name="麦八", - email="maiba@m.com", - phone="13512345676", - data_source=ds, - ) - yangjiu = DataSourceUser.objects.create( - code="Employee-9", - username="yangjiu", - full_name="杨九", - email="yangjiu@m.com", - phone="13512345677", - data_source=ds, - ) - lushi = DataSourceUser.objects.create( - code="Employee-10", - username="lushi", - full_name="鲁十", - email="lushi@m.com", - phone="13512345678", - data_source=ds, - ) - linshiyi = DataSourceUser.objects.create( - code="Employee-11", - username="linshiyi", - full_name="林十一", - email="linshiyi@m.com", - phone="13512345679", - data_source=ds, - ) - baishier = DataSourceUser.objects.create( - code="Employee-12", - username="baishier", - full_name="白十二", - email="baishier@m.com", - phone="13512345670", - data_source=ds, - ) - # 不属于任何组织,没有上下级的自由人 - DataSourceUser.objects.create( - code="Employee-666", - username="freedom", - full_name="自由人", - email="freedom@m.com", - phone="1351234567X", - data_source=ds, - ) +@pytest.fixture() +def general_ds_plugin_config() -> Dict[str, Any]: + # TODO (su) 预设通用 HTTP 数据源的插件配置 + return {"TODO": "TODO"} - # 数据源部门 - company = DataSourceDepartment.objects.create(data_source=ds, code="company", name="公司") - dept_a = DataSourceDepartment.objects.create(data_source=ds, code="dept_a", name="部门A") - dept_b = DataSourceDepartment.objects.create(data_source=ds, code="dept_b", name="部门B") - center_aa = DataSourceDepartment.objects.create(data_source=ds, code="center_aa", name="中心AA") - center_ab = DataSourceDepartment.objects.create(data_source=ds, code="center_ab", name="中心AB") - center_ba = DataSourceDepartment.objects.create(data_source=ds, code="center_ba", name="中心BA") - group_aaa = DataSourceDepartment.objects.create(data_source=ds, code="group_aaa", name="小组AAA") - group_aba = DataSourceDepartment.objects.create(data_source=ds, code="group_aba", name="小组ABA") - group_baa = DataSourceDepartment.objects.create(data_source=ds, code="group_baa", name="小组BAA") - # 数据源部门关系 - company_node = DataSourceDepartmentRelation.objects.create(department=company, parent=None, data_source=ds) - dept_a_node = DataSourceDepartmentRelation.objects.create(department=dept_a, parent=company_node, data_source=ds) - dept_b_node = DataSourceDepartmentRelation.objects.create(department=dept_b, parent=company_node, data_source=ds) - center_aa_node = DataSourceDepartmentRelation.objects.create( - department=center_aa, parent=dept_a_node, data_source=ds - ) - center_ab_node = DataSourceDepartmentRelation.objects.create( - department=center_ab, parent=dept_a_node, data_source=ds - ) - center_ba_node = DataSourceDepartmentRelation.objects.create( - department=center_ba, parent=dept_b_node, data_source=ds +@pytest.fixture() +def general_ds_plugin() -> DataSourcePlugin: + plugin, _ = DataSourcePlugin.objects.get_or_create( + id=DataSourcePluginEnum.GENERAL, + defaults={"name": "通用 HTTP 数据源"}, ) - DataSourceDepartmentRelation.objects.create(department=group_aaa, parent=center_aa_node, data_source=ds) - DataSourceDepartmentRelation.objects.create(department=group_aba, parent=center_ab_node, data_source=ds) - DataSourceDepartmentRelation.objects.create(department=group_baa, parent=center_ba_node, data_source=ds) + return plugin - # 数据源部门用户关联 - dept_user_relations = [ - DataSourceDepartmentUserRelation(department=company, user=zhangsan), - DataSourceDepartmentUserRelation(department=dept_a, user=lisi), - DataSourceDepartmentUserRelation(department=dept_a, user=wangwu), - DataSourceDepartmentUserRelation(department=center_aa, user=lisi), - DataSourceDepartmentUserRelation(department=center_aa, user=zhaoliu), - DataSourceDepartmentUserRelation(department=group_aaa, user=liuqi), - DataSourceDepartmentUserRelation(department=center_ab, user=maiba), - DataSourceDepartmentUserRelation(department=center_ab, user=yangjiu), - DataSourceDepartmentUserRelation(department=group_aba, user=lushi), - DataSourceDepartmentUserRelation(department=group_aba, user=linshiyi), - DataSourceDepartmentUserRelation(department=dept_b, user=wangwu), - DataSourceDepartmentUserRelation(department=center_ba, user=lushi), - DataSourceDepartmentUserRelation(department=group_baa, user=baishier), - ] - DataSourceDepartmentUserRelation.objects.bulk_create(dept_user_relations) - # 数据源用户 Leader 关联 - user_leader_relations = [ - DataSourceUserLeaderRelation(user=lisi, leader=zhangsan), - DataSourceUserLeaderRelation(user=wangwu, leader=zhangsan), - DataSourceUserLeaderRelation(user=zhaoliu, leader=lisi), - DataSourceUserLeaderRelation(user=liuqi, leader=zhaoliu), - DataSourceUserLeaderRelation(user=maiba, leader=wangwu), - DataSourceUserLeaderRelation(user=maiba, leader=lisi), - DataSourceUserLeaderRelation(user=yangjiu, leader=wangwu), - DataSourceUserLeaderRelation(user=lushi, leader=maiba), - DataSourceUserLeaderRelation(user=linshiyi, leader=lushi), - DataSourceUserLeaderRelation(user=lushi, leader=wangwu), - DataSourceUserLeaderRelation(user=baishier, leader=lushi), - ] - DataSourceUserLeaderRelation.objects.bulk_create(user_leader_relations) +@pytest.fixture() +def bare_general_data_source(general_ds_plugin_config, general_ds_plugin) -> DataSource: + """裸通用 HTTP 数据源(没有用户,部门等数据)""" + return DataSource.objects.create( + name=generate_random_string(), + owner_tenant_id=DEFAULT_TENANT, + plugin=general_ds_plugin, + plugin_config=general_ds_plugin_config, + ) + - return ds +@pytest.fixture() +def full_general_data_source(bare_general_data_source) -> DataSource: + """携带用户,部门信息的通用 HTTP 数据源""" + init_data_source_users_depts_and_relations(bare_general_data_source) + return bare_general_data_source diff --git a/src/bk-user/tests/plugins/local/test_utils.py b/src/bk-user/tests/plugins/local/test_utils.py index 10fe2261e..6361d549a 100644 --- a/src/bk-user/tests/plugins/local/test_utils.py +++ b/src/bk-user/tests/plugins/local/test_utils.py @@ -27,5 +27,5 @@ ) def test_gen_code(raw, excepted): # 重要:如果该单元测试挂了,说明修改了本地数据源用户 & 部门的 Code 的生成规则 - # 改行为会导致新同步的数据,无法与 DB 中的数据匹配上,将会触发数据重建!!! + # 该行为会导致新同步的数据,无法与 DB 中的数据匹配上,将会触发数据重建!!! assert gen_code(raw) == excepted diff --git a/src/bk-user/tests/test_utils/data_source.py b/src/bk-user/tests/test_utils/data_source.py new file mode 100644 index 000000000..708971169 --- /dev/null +++ b/src/bk-user/tests/test_utils/data_source.py @@ -0,0 +1,175 @@ +# -*- 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.apps.data_source.models import ( + DataSource, + DataSourceDepartment, + DataSourceDepartmentRelation, + DataSourceDepartmentUserRelation, + DataSourceUser, + DataSourceUserLeaderRelation, +) + + +def init_data_source_users_depts_and_relations(ds: DataSource) -> None: + """为数据源初始化用户,部门,用户部门关系,用户 leader 关系,部门关系等""" + + # 数据源用户 + zhangsan = DataSourceUser.objects.create( + code="Employee-3", + username="zhangsan", + full_name="张三", + email="zhangsan@m.com", + phone="13512345671", + data_source=ds, + ) + lisi = DataSourceUser.objects.create( + code="Employee-4", + username="lisi", + full_name="李四", + email="lisi@m.com", + phone="13512345672", + data_source=ds, + ) + wangwu = DataSourceUser.objects.create( + code="Employee-5", + username="wangwu", + full_name="王五", + email="wangwu@m.com", + phone="13512345673", + data_source=ds, + ) + zhaoliu = DataSourceUser.objects.create( + code="Employee-6", + username="zhaoliu", + full_name="赵六", + email="zhaoliu@m.com", + phone="13512345674", + data_source=ds, + ) + liuqi = DataSourceUser.objects.create( + code="Employee-7", + username="liuqi", + full_name="柳七", + email="liuqi@m.com", + phone="13512345675", + data_source=ds, + ) + maiba = DataSourceUser.objects.create( + code="Employee-8", + username="maiba", + full_name="麦八", + email="maiba@m.com", + phone="13512345676", + data_source=ds, + ) + yangjiu = DataSourceUser.objects.create( + code="Employee-9", + username="yangjiu", + full_name="杨九", + email="yangjiu@m.com", + phone="13512345677", + data_source=ds, + ) + lushi = DataSourceUser.objects.create( + code="Employee-10", + username="lushi", + full_name="鲁十", + email="lushi@m.com", + phone="13512345678", + data_source=ds, + ) + linshiyi = DataSourceUser.objects.create( + code="Employee-11", + username="linshiyi", + full_name="林十一", + email="linshiyi@m.com", + phone="13512345679", + data_source=ds, + ) + baishier = DataSourceUser.objects.create( + code="Employee-12", + username="baishier", + full_name="白十二", + email="baishier@m.com", + phone="13512345670", + data_source=ds, + ) + # 不属于任何组织,没有上下级的自由人 + DataSourceUser.objects.create( + code="Employee-666", + username="freedom", + full_name="自由人", + email="freedom@m.com", + phone="1351234567X", + data_source=ds, + ) + + # 数据源部门 + company = DataSourceDepartment.objects.create(data_source=ds, code="company", name="公司") + dept_a = DataSourceDepartment.objects.create(data_source=ds, code="dept_a", name="部门A") + dept_b = DataSourceDepartment.objects.create(data_source=ds, code="dept_b", name="部门B") + center_aa = DataSourceDepartment.objects.create(data_source=ds, code="center_aa", name="中心AA") + center_ab = DataSourceDepartment.objects.create(data_source=ds, code="center_ab", name="中心AB") + center_ba = DataSourceDepartment.objects.create(data_source=ds, code="center_ba", name="中心BA") + group_aaa = DataSourceDepartment.objects.create(data_source=ds, code="group_aaa", name="小组AAA") + group_aba = DataSourceDepartment.objects.create(data_source=ds, code="group_aba", name="小组ABA") + group_baa = DataSourceDepartment.objects.create(data_source=ds, code="group_baa", name="小组BAA") + + # 数据源部门关系 + company_node = DataSourceDepartmentRelation.objects.create(department=company, parent=None, data_source=ds) + dept_a_node = DataSourceDepartmentRelation.objects.create(department=dept_a, parent=company_node, data_source=ds) + dept_b_node = DataSourceDepartmentRelation.objects.create(department=dept_b, parent=company_node, data_source=ds) + center_aa_node = DataSourceDepartmentRelation.objects.create( + department=center_aa, parent=dept_a_node, data_source=ds + ) + center_ab_node = DataSourceDepartmentRelation.objects.create( + department=center_ab, parent=dept_a_node, data_source=ds + ) + center_ba_node = DataSourceDepartmentRelation.objects.create( + department=center_ba, parent=dept_b_node, data_source=ds + ) + DataSourceDepartmentRelation.objects.create(department=group_aaa, parent=center_aa_node, data_source=ds) + DataSourceDepartmentRelation.objects.create(department=group_aba, parent=center_ab_node, data_source=ds) + DataSourceDepartmentRelation.objects.create(department=group_baa, parent=center_ba_node, data_source=ds) + + # 数据源部门用户关联 + dept_user_relations = [ + DataSourceDepartmentUserRelation(department=company, user=zhangsan), + DataSourceDepartmentUserRelation(department=dept_a, user=lisi), + DataSourceDepartmentUserRelation(department=dept_a, user=wangwu), + DataSourceDepartmentUserRelation(department=center_aa, user=lisi), + DataSourceDepartmentUserRelation(department=center_aa, user=zhaoliu), + DataSourceDepartmentUserRelation(department=group_aaa, user=liuqi), + DataSourceDepartmentUserRelation(department=center_ab, user=maiba), + DataSourceDepartmentUserRelation(department=center_ab, user=yangjiu), + DataSourceDepartmentUserRelation(department=group_aba, user=lushi), + DataSourceDepartmentUserRelation(department=group_aba, user=linshiyi), + DataSourceDepartmentUserRelation(department=dept_b, user=wangwu), + DataSourceDepartmentUserRelation(department=center_ba, user=lushi), + DataSourceDepartmentUserRelation(department=group_baa, user=baishier), + ] + DataSourceDepartmentUserRelation.objects.bulk_create(dept_user_relations) + + # 数据源用户 Leader 关联 + user_leader_relations = [ + DataSourceUserLeaderRelation(user=lisi, leader=zhangsan), + DataSourceUserLeaderRelation(user=wangwu, leader=zhangsan), + DataSourceUserLeaderRelation(user=zhaoliu, leader=lisi), + DataSourceUserLeaderRelation(user=liuqi, leader=zhaoliu), + DataSourceUserLeaderRelation(user=maiba, leader=wangwu), + DataSourceUserLeaderRelation(user=maiba, leader=lisi), + DataSourceUserLeaderRelation(user=yangjiu, leader=wangwu), + DataSourceUserLeaderRelation(user=lushi, leader=maiba), + DataSourceUserLeaderRelation(user=linshiyi, leader=lushi), + DataSourceUserLeaderRelation(user=lushi, leader=wangwu), + DataSourceUserLeaderRelation(user=baishier, leader=lushi), + ] + DataSourceUserLeaderRelation.objects.bulk_create(user_leader_relations)