签入版本
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
"""
|
||||
种子数据脚本(幂等,可重复执行)
|
||||
=============================================================
|
||||
|
||||
在空库上初始化系统必需数据,供多数据库(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("种子数据初始化完成")
|
||||
Reference in New Issue
Block a user