Files
2026-09-12 18:57:19 +08:00

222 lines
9.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
种子数据脚本(幂等,可重复执行)
=============================================================
在空库上初始化系统必需数据,供多数据库(mysql/pgsql/mssql/sqlite/oracle)就绪:
- admin 超管用户(id=1,密码见 DEFAULT_PASSWORD,默认 123456)
- 超级管理员角色 + 关联
- 系统管理目录 + 常用管理菜单 + 查询权限节点
- 基础字典(性别)、站点配置、系统参数
用法:由 scripts/setup_db.py 在 app 上下文内调用 seed_all()。
"""
import logging
import os
import sys
# 将项目根加入 sys.path(脚本以 `python scripts/xxx.py` 运行时 cwd 是 scripts/)
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
if ROOT not in sys.path:
sys.path.insert(0, ROOT)
from src.core.database import db # noqa: E402
from src.core.password import encrypt_password, generate_salt # noqa: E402
from src.common.constants.common import DEFAULT_PASSWORD # noqa: E402
from src.modules.system.user.repository import UserRepository # noqa: E402
from src.modules.system.role.repository import RoleRepository # noqa: E402
from src.modules.association.user_role.repository import UserRoleRepository # noqa: E402
from src.modules.system.menu.repository import MenuRepository # noqa: E402
from src.modules.dictionary.dict.repository import DictRepository # noqa: E402
from src.modules.dictionary.dict_item.repository import DictItemRepository # noqa: E402
from src.modules.configuration.config.repository import ConfigRepository # noqa: E402
from src.modules.configuration.config_item.repository import ConfigItemRepository # noqa: E402
from src.modules.param.repository import ParamRepository # noqa: E402
logger = logging.getLogger(__name__)
def _get_or_create(repo, filters, defaults):
"""幂等:按等值条件查,不存在则创建"""
obj = repo.first_by(filters)
if obj is None:
obj = repo.create(**defaults)
db.session.commit()
logger.info(f"已创建 {repo.__name__}: {filters}")
return obj
def seed_admin_user():
"""创建 admin 超管用户(id=1,超管绕过 user_id==1)"""
if UserRepository.first_by({"username": "admin"}):
logger.info("admin 用户已存在,跳过")
return
salt = generate_salt()
admin = UserRepository.create(
username="admin",
realname="超级管理员",
gender=1,
avatar="",
mobile="",
email="",
dept_id=0,
level_id=0,
position_id=0,
province_code="",
city_code="",
district_code="",
street_code="",
address="",
status=1,
sort=1,
note="系统内置超管,请登录后修改密码",
password=encrypt_password(DEFAULT_PASSWORD, salt),
salt=salt,
create_user="system",
)
db.session.commit()
logger.info(f"已创建 admin 用户(id={admin.id},初始密码 {DEFAULT_PASSWORD})")
def seed_superadmin_role():
"""创建超级管理员角色并关联 admin 用户"""
role = _get_or_create(
RoleRepository,
{"code": "admin"},
{"name": "超级管理员", "code": "admin", "status": 1, "sort": 1, "create_user": "system"},
)
admin = UserRepository.first_by({"username": "admin"})
if admin and not UserRoleRepository.first_by({"user_id": admin.id, "role_id": role.id}):
UserRoleRepository.create(user_id=admin.id, role_id=role.id, create_user="system")
db.session.commit()
logger.info("已关联 admin → 超级管理员")
def seed_basic_menus():
"""创建系统管理目录 + 常用管理菜单 + 查询权限节点"""
if MenuRepository.first_by({"name": "系统管理", "parent_id": 0}):
logger.info("系统管理菜单已存在,跳过")
return
sys_dir = MenuRepository.create(
name="系统管理", parent_id=0, path="/system", component="",
permission="", type=0, status=1, target=0, sort=1, icon="setting",
hide=0, create_user="system",
)
db.session.commit()
child_menus = [
("用户管理", "/user", "system/user/index", "sys:user:page", 1),
("角色管理", "/role", "system/role/index", "sys:role:page", 2),
("菜单管理", "/menu", "system/menu/index", "sys:menu:page", 3),
("部门管理", "/dept", "system/dept/index", "sys:dept:page", 4),
("岗位管理", "/position", "system/position/index", "sys:position:page", 5),
("职级管理", "/level", "system/level/index", "sys:level:page", 6),
]
for name, path, component, permission, sort in child_menus:
menu = MenuRepository.create(
name=name, parent_id=sys_dir.id, path=path, component=component,
permission=permission, type=0, status=1, target=0, sort=sort,
hide=0, create_user="system",
)
db.session.commit()
# 查询权限节点(type=1,前端按钮/接口鉴权用)
MenuRepository.create(
name=f"查询{name}", parent_id=menu.id, path=f"{path}/page", component="",
permission=permission, type=1, status=1, target=0, sort=1,
create_user="system",
)
db.session.commit()
logger.info("已创建系统管理目录与常用菜单")
def seed_import_export_permissions():
"""为存量菜单幂等补齐「导入/导出」按钮权限节点
背景说明:
导入/导出 按钮在前端通过 v-perm 指令按权限码鉴权(如 sys:user:import),
权限码必须存在于菜单表中(type=1 按钮节点),否则即使后端接口已放行、
后端装饰器也已校验,前端按钮仍不会渲染。本函数为老库中已存在的菜单
补建对应按钮节点,与新建库流程(seed_basic_menus)保持一致。
设计要点:
1. 幂等:以「父菜单名 + type=0」定位父菜单,以「按钮名 + parent_id」判重,
已存在的节点直接跳过,脚本可安全重复执行。
2. 容错:父菜单缺失(如老库中未创建「职级管理」)时仅记录日志并跳过,
不中断其余按钮节点创建。
3. 按钮路径约定为 <父菜单路径>/import、<父菜单路径>/export,
与后端 router 的路由保持一致,便于前端跳转与鉴权对齐。
"""
# 待补建的按钮节点配置:父菜单名 / 按钮名 / 路径 / 权限码 / 排序
btn_specs = [
("用户管理", "导入用户", "/user/import", "sys:user:import", 2),
("用户管理", "导出用户", "/user/export", "sys:user:export", 3),
("职级管理", "导入职级", "/level/import", "sys:level:import", 2),
("职级管理", "导出职级", "/level/export", "sys:level:export", 3),
]
for menu_name, btn_name, btn_path, permission, sort in btn_specs:
parent = MenuRepository.first_by({"name": menu_name, "type": 0})
if not parent:
logger.warning(f"菜单「{menu_name}」不存在,跳过权限节点 {permission}")
continue
if MenuRepository.first_by({"name": btn_name, "parent_id": parent.id}):
logger.info(f"权限节点 {permission} 已存在,跳过")
continue
MenuRepository.create(
name=btn_name, parent_id=parent.id, path=btn_path, component="",
permission=permission, type=1, status=1, target=0, sort=sort,
create_user="system",
)
db.session.commit()
logger.info(f"已创建权限节点 {permission}({btn_name})")
def seed_basic_dict():
"""创建性别字典"""
if DictRepository.first_by({"code": "gender"}):
logger.info("性别字典已存在,跳过")
return
dict_obj = DictRepository.create(name="性别", code="gender", sort=1, create_user="system")
db.session.commit()
for name, value, sort in [("男", "1", 1), ("女", "2", 2), ("保密", "3", 3)]:
DictItemRepository.create(
dict_id=dict_obj.id, name=name, value=value, sort=sort, create_user="system"
)
db.session.commit()
def seed_basic_config():
"""创建站点配置"""
if ConfigRepository.first_by({"code": "site_config"}):
logger.info("站点配置已存在,跳过")
return
config = ConfigRepository.create(name="站点配置", code="site_config", sort=1, create_user="system")
db.session.commit()
ConfigItemRepository.create(
config_id=config.id, name="网站名称",
code="site_name", value="DjangoAdmin", type="text", sort=1, create_user="system",
)
db.session.commit()
def seed_basic_param():
"""创建系统参数"""
_get_or_create(
ParamRepository,
{"code": "system_param"},
{"name": "系统参数", "code": "system_param", "value": "", "type": 0, "status": 1, "sort": 1, "create_user": "system"},
)
def seed_all():
"""执行全部种子(幂等)"""
seed_admin_user()
seed_superadmin_role()
seed_basic_menus()
# 为存量库补建导入/导出按钮权限节点,必须在菜单创建之后执行
seed_import_export_permissions()
seed_basic_dict()
seed_basic_config()
seed_basic_param()
logger.info("种子数据初始化完成")