feat: add governance bootstrap models
This commit is contained in:
105
backend/apps/accounts/bootstrap.py
Normal file
105
backend/apps/accounts/bootstrap.py
Normal 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()
|
||||
@@ -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."))
|
||||
@@ -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."))
|
||||
|
||||
35
backend/apps/accounts/menus.py
Normal file
35
backend/apps/accounts/menus.py
Normal 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),
|
||||
)
|
||||
53
backend/apps/accounts/migrations/0001_initial.py
Normal file
53
backend/apps/accounts/migrations/0001_initial.py
Normal 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'],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
backend/apps/accounts/migrations/__init__.py
Normal file
0
backend/apps/accounts/migrations/__init__.py
Normal 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
|
||||
|
||||
@@ -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",
|
||||
),
|
||||
}
|
||||
|
||||
23
backend/apps/accounts/tests/test_bootstrap_governance.py
Normal file
23
backend/apps/accounts/tests/test_bootstrap_governance.py
Normal 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()
|
||||
Reference in New Issue
Block a user