feat: add governance bootstrap models

This commit is contained in:
hailin
2026-07-01 14:04:22 +08:00
parent ee9aad6a02
commit 7c4b66b832
9 changed files with 304 additions and 47 deletions

View File

@@ -0,0 +1,105 @@
from django.conf import settings
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from apps.accounts.menus import DEFAULT_MENUS
from apps.accounts.models import MenuItem, UserGroup
from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS
def seed_permissions() -> dict[str, Permission]:
content_type, _ = ContentType.objects.get_or_create(
app_label="system",
model="platformpermission",
)
permissions_by_codename: dict[str, Permission] = {}
for spec in PERMISSIONS:
permission, _ = Permission.objects.get_or_create(
content_type=content_type,
codename=spec.codename,
defaults={"name": spec.name},
)
if permission.name != spec.name:
permission.name = spec.name
permission.save(update_fields=["name"])
permissions_by_codename[spec.codename] = permission
return permissions_by_codename
def seed_roles(permissions_by_codename: dict[str, Permission]) -> None:
for group_name, codenames in DEFAULT_GROUPS.items():
group, _ = Group.objects.get_or_create(name=group_name)
group.permissions.set(permissions_by_codename[codename] for codename in codenames)
def seed_user_groups() -> None:
UserGroup.objects.update_or_create(
code="default",
defaults={
"name": "默认用户组",
"description": "系统初始化创建的默认业务用户组。",
"is_active": True,
},
)
def seed_menus() -> None:
menus_by_code: dict[str, MenuItem] = {}
for spec in DEFAULT_MENUS:
parent = menus_by_code.get(spec.parent_code) if spec.parent_code else None
menu, _ = MenuItem.objects.update_or_create(
code=spec.code,
defaults={
"title": spec.title,
"path": spec.path,
"icon": spec.icon,
"parent": parent,
"sort_order": spec.sort_order,
"permission_code": spec.permission_code,
"is_visible": spec.is_visible,
"is_enabled": spec.is_enabled,
},
)
menus_by_code[spec.code] = menu
def seed_admin_user() -> None:
username = getattr(settings, "DJANGO_SUPERUSER_USERNAME", None) or "admin"
password = getattr(settings, "DJANGO_SUPERUSER_PASSWORD", None) or "admin12345"
email = getattr(settings, "DJANGO_SUPERUSER_EMAIL", None) or "admin@example.com"
user_model = get_user_model()
user, created = user_model.objects.get_or_create(
username=username,
defaults={
"email": email,
"is_staff": True,
"is_superuser": True,
},
)
changed_fields: list[str] = []
if user.email != email:
user.email = email
changed_fields.append("email")
if not user.is_staff:
user.is_staff = True
changed_fields.append("is_staff")
if not user.is_superuser:
user.is_superuser = True
changed_fields.append("is_superuser")
if created or not user.check_password(password):
user.set_password(password)
changed_fields.append("password")
if changed_fields:
user.save(update_fields=changed_fields)
admin_role = Group.objects.get(name="系统管理员")
user.groups.add(admin_role)
def bootstrap_governance() -> None:
permissions_by_codename = seed_permissions()
seed_roles(permissions_by_codename)
seed_user_groups()
seed_menus()
seed_admin_user()

View File

@@ -0,0 +1,11 @@
from django.core.management.base import BaseCommand
from apps.accounts.bootstrap import bootstrap_governance
class Command(BaseCommand):
help = "Bootstrap enterprise governance defaults."
def handle(self, *args, **options):
bootstrap_governance()
self.stdout.write(self.style.SUCCESS("Bootstrapped enterprise governance."))

View File

@@ -1,33 +1,11 @@
from django.contrib.auth.models import Group, Permission
from django.contrib.contenttypes.models import ContentType
from django.core.management.base import BaseCommand
from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS
from apps.accounts.bootstrap import seed_permissions, seed_roles
class Command(BaseCommand):
help = "Seed MVP permissions and default user groups."
def handle(self, *args, **options):
content_type, _ = ContentType.objects.get_or_create(
app_label="system",
model="platformpermission",
)
permissions_by_codename: dict[str, Permission] = {}
for spec in PERMISSIONS:
permission, _ = Permission.objects.get_or_create(
content_type=content_type,
codename=spec.codename,
defaults={"name": spec.name},
)
if permission.name != spec.name:
permission.name = spec.name
permission.save(update_fields=["name"])
permissions_by_codename[spec.codename] = permission
for group_name, codenames in DEFAULT_GROUPS.items():
group, _ = Group.objects.get_or_create(name=group_name)
group.permissions.set(permissions_by_codename[codename] for codename in codenames)
seed_roles(seed_permissions())
self.stdout.write(self.style.SUCCESS("Seeded permissions and groups."))

View File

@@ -0,0 +1,35 @@
from dataclasses import dataclass
@dataclass(frozen=True)
class MenuSpec:
code: str
title: str
path: str = ""
icon: str = ""
parent_code: str = ""
sort_order: int = 0
permission_code: str = ""
is_visible: bool = True
is_enabled: bool = True
DEFAULT_MENUS: tuple[MenuSpec, ...] = (
MenuSpec("workbench", "工作台", icon="home", sort_order=10),
MenuSpec("workbench.overview", "运营总览", "#/", "home", "workbench", 10),
MenuSpec("business", "业务处理", icon="ticket", sort_order=20),
MenuSpec("business.tickets", "工单中心", "#/tickets", "ticket", "business", 10, "ticket.view"),
MenuSpec("business.ticket_create", "创建工单", "#/tickets/new", "briefcase", "business", 20, "ticket.create"),
MenuSpec("business.solutions", "解决方案库", "#/solutions", "library", "business", 30, "solution.view"),
MenuSpec("platform", "平台能力", icon="bot", sort_order=30),
MenuSpec("platform.knowledge", "知识库", "#/knowledge", "database", "platform", 10, "knowledge.view", is_enabled=False),
MenuSpec("platform.agents", "Agent 配置", "#/agents", "bot", "platform", 20, "agent.manage", is_enabled=False),
MenuSpec("platform.models", "模型与工具", "#/models", "settings", "platform", 30, "model.manage", is_enabled=False),
MenuSpec("system", "系统治理", icon="shield", sort_order=40),
MenuSpec("system.users", "用户管理", "#/system/users", "users", "system", 10, "system.user.view"),
MenuSpec("system.user_groups", "用户组管理", "#/system/user-groups", "user_groups", "system", 20, "system.user_group.view"),
MenuSpec("system.roles", "角色权限", "#/system/roles", "roles", "system", 30, "system.role.view"),
MenuSpec("system.menus", "菜单权限", "#/system/menus", "menu", "system", 40, "system.menu.view"),
MenuSpec("system.audit", "操作审计", "#/audit", "audit", "system", 50, "system.audit.view"),
MenuSpec("system.design", "设计系统", "#/design-system", "settings", "system", 60),
)

View File

@@ -0,0 +1,53 @@
# Generated by Django 5.2.15 on 2026-07-01 06:04
import django.db.models.deletion
from django.conf import settings
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
]
operations = [
migrations.CreateModel(
name='MenuItem',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('code', models.CharField(max_length=120, unique=True)),
('title', models.CharField(max_length=120)),
('path', models.CharField(blank=True, max_length=200)),
('icon', models.CharField(blank=True, max_length=80)),
('sort_order', models.PositiveIntegerField(default=0)),
('permission_code', models.CharField(blank=True, max_length=120)),
('is_visible', models.BooleanField(default=True)),
('is_enabled', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, related_name='children', to='accounts.menuitem')),
],
options={
'ordering': ['sort_order', 'id'],
},
),
migrations.CreateModel(
name='UserGroup',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=120, unique=True)),
('code', models.CharField(max_length=80, unique=True)),
('description', models.TextField(blank=True)),
('is_active', models.BooleanField(default=True)),
('created_at', models.DateTimeField(auto_now_add=True)),
('updated_at', models.DateTimeField(auto_now=True)),
('users', models.ManyToManyField(blank=True, related_name='business_groups', to=settings.AUTH_USER_MODEL)),
],
options={
'ordering': ['name'],
},
),
]

View File

@@ -1 +1,44 @@
# Use Django's built-in User, Group, and Permission models for MVP RBAC.
from django.conf import settings
from django.db import models
class UserGroup(models.Model):
name = models.CharField(max_length=120, unique=True)
code = models.CharField(max_length=80, unique=True)
description = models.TextField(blank=True)
users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="business_groups")
is_active = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["name"]
def __str__(self) -> str:
return self.name
class MenuItem(models.Model):
code = models.CharField(max_length=120, unique=True)
title = models.CharField(max_length=120)
path = models.CharField(max_length=200, blank=True)
icon = models.CharField(max_length=80, blank=True)
parent = models.ForeignKey(
"self",
null=True,
blank=True,
on_delete=models.CASCADE,
related_name="children",
)
sort_order = models.PositiveIntegerField(default=0)
permission_code = models.CharField(max_length=120, blank=True)
is_visible = models.BooleanField(default=True)
is_enabled = models.BooleanField(default=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["sort_order", "id"]
def __str__(self) -> str:
return self.title

View File

@@ -5,30 +5,39 @@ from dataclasses import dataclass
class PermissionSpec:
codename: str
name: str
module: str
PERMISSIONS: tuple[PermissionSpec, ...] = (
PermissionSpec("ticket.view", "View tickets"),
PermissionSpec("ticket.create", "Create tickets"),
PermissionSpec("ticket.edit", "Edit tickets"),
PermissionSpec("ticket.export", "Export tickets"),
PermissionSpec("ticket.assign", "Assign tickets"),
PermissionSpec("ticket.close", "Close tickets"),
PermissionSpec("knowledge.view", "View knowledge"),
PermissionSpec("knowledge.manage", "Manage knowledge"),
PermissionSpec("solution.view", "View solutions"),
PermissionSpec("solution.manage", "Manage solutions"),
PermissionSpec("agent.use", "Use Agents"),
PermissionSpec("agent.manage", "Manage Agents"),
PermissionSpec("model.manage", "Manage model configs"),
PermissionSpec("tool.manage", "Manage MCP tools"),
PermissionSpec("audit.view", "View audit logs"),
PermissionSpec("system.manage", "Manage system settings"),
PermissionSpec("ticket.view", "查看工单", "工单"),
PermissionSpec("ticket.create", "创建工单", "工单"),
PermissionSpec("ticket.edit", "编辑工单", "工单"),
PermissionSpec("ticket.export", "导出工单", "工单"),
PermissionSpec("ticket.assign", "分派工单", "工单"),
PermissionSpec("ticket.close", "关闭工单", "工单"),
PermissionSpec("solution.view", "查看解决方案", "解决方案"),
PermissionSpec("solution.manage", "管理解决方案", "解决方案"),
PermissionSpec("knowledge.view", "查看知识库", "知识库"),
PermissionSpec("knowledge.manage", "管理知识库", "知识库"),
PermissionSpec("agent.use", "使用 Agent", "AI 平台"),
PermissionSpec("agent.manage", "管理 Agent", "AI 平台"),
PermissionSpec("model.manage", "管理模型配置", "AI 平台"),
PermissionSpec("tool.manage", "管理 MCP 工具", "AI 平台"),
PermissionSpec("system.user.view", "查看用户", "系统治理"),
PermissionSpec("system.user.manage", "管理用户", "系统治理"),
PermissionSpec("system.user_group.view", "查看用户组", "系统治理"),
PermissionSpec("system.user_group.manage", "管理用户组", "系统治理"),
PermissionSpec("system.role.view", "查看角色权限", "系统治理"),
PermissionSpec("system.role.manage", "管理角色权限", "系统治理"),
PermissionSpec("system.menu.view", "查看菜单权限", "系统治理"),
PermissionSpec("system.menu.manage", "管理菜单权限", "系统治理"),
PermissionSpec("system.audit.view", "查看操作审计", "系统治理"),
PermissionSpec("system.setting.manage", "管理系统设置", "系统治理"),
)
DEFAULT_GROUPS: dict[str, tuple[str, ...]] = {
"System Admin": tuple(permission.codename for permission in PERMISSIONS),
"Knowledge Admin": (
"系统管理员": tuple(permission.codename for permission in PERMISSIONS),
"知识库管理员": (
"ticket.view",
"knowledge.view",
"knowledge.manage",
@@ -36,7 +45,7 @@ DEFAULT_GROUPS: dict[str, tuple[str, ...]] = {
"solution.manage",
"agent.use",
),
"Support Engineer": (
"技术支持": (
"ticket.view",
"ticket.create",
"ticket.edit",
@@ -46,16 +55,16 @@ DEFAULT_GROUPS: dict[str, tuple[str, ...]] = {
"solution.view",
"agent.use",
),
"Requester": (
"普通建单人": (
"ticket.view",
"ticket.create",
"knowledge.view",
"solution.view",
),
"Auditor": (
"只读审计员": (
"ticket.view",
"knowledge.view",
"solution.view",
"audit.view",
"system.audit.view",
),
}

View File

@@ -0,0 +1,23 @@
from django.contrib.auth import get_user_model
from django.contrib.auth.models import Group, Permission
from django.core.management import call_command
from apps.accounts.models import MenuItem, UserGroup
def test_bootstrap_governance_seeds_admin_roles_groups_permissions_and_menus(db, settings):
settings.DJANGO_SUPERUSER_USERNAME = "admin"
settings.DJANGO_SUPERUSER_PASSWORD = "admin12345"
settings.DJANGO_SUPERUSER_EMAIL = "admin@example.com"
call_command("bootstrap_governance")
call_command("bootstrap_governance")
user = get_user_model().objects.get(username="admin")
assert user.is_superuser is True
assert user.check_password("admin12345")
assert Group.objects.filter(name="系统管理员").exists()
assert Permission.objects.filter(codename="system.user.manage").exists()
assert UserGroup.objects.filter(code="default").exists()
assert MenuItem.objects.filter(code="system.users", title="用户管理").exists()
assert MenuItem.objects.filter(code="system.roles", permission_code="system.role.view").exists()