feat: initialize deployment governance

This commit is contained in:
hailin
2026-07-01 14:29:24 +08:00
parent 9fc8101f96
commit 85f3fd9af4
12 changed files with 215 additions and 37 deletions

View File

@@ -7,6 +7,14 @@ from apps.accounts.menus import DEFAULT_MENUS
from apps.accounts.models import MenuItem, UserGroup
from apps.accounts.permissions import DEFAULT_GROUPS, PERMISSIONS
LEGACY_ROLE_RENAMES = {
"System Admin": "系统管理员",
"Knowledge Admin": "知识库管理员",
"Support Engineer": "技术支持",
"Requester": "普通建单人",
"Auditor": "只读审计员",
}
def seed_permissions() -> dict[str, Permission]:
content_type, _ = ContentType.objects.get_or_create(
@@ -29,11 +37,26 @@ def seed_permissions() -> dict[str, Permission]:
def seed_roles(permissions_by_codename: dict[str, Permission]) -> None:
migrate_legacy_roles()
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 migrate_legacy_roles() -> None:
for legacy_name, target_name in LEGACY_ROLE_RENAMES.items():
legacy_group = Group.objects.filter(name=legacy_name).first()
if legacy_group is None:
continue
target_group = Group.objects.filter(name=target_name).first()
if target_group is None:
legacy_group.name = target_name
legacy_group.save(update_fields=["name"])
continue
target_group.user_set.add(*legacy_group.user_set.all())
legacy_group.delete()
def seed_user_groups() -> None:
UserGroup.objects.update_or_create(
code="default",

View File

@@ -19,17 +19,74 @@ DEFAULT_MENUS: tuple[MenuSpec, ...] = (
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(
"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(
"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.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

@@ -6,7 +6,6 @@ from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = [
@@ -15,39 +14,63 @@ class Migration(migrations.Migration):
operations = [
migrations.CreateModel(
name='MenuItem',
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')),
(
"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'],
"ordering": ["sort_order", "id"],
},
),
migrations.CreateModel(
name='UserGroup',
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)),
(
"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'],
"ordering": ["name"],
},
),
]

View File

@@ -6,7 +6,9 @@ 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")
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)

View File

@@ -21,3 +21,19 @@ def test_bootstrap_governance_seeds_admin_roles_groups_permissions_and_menus(db,
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()
def test_bootstrap_governance_migrates_legacy_english_roles(db, settings):
legacy_group = Group.objects.create(name="Support Engineer")
user = get_user_model().objects.create_user(username="support", password="password")
user.groups.add(legacy_group)
settings.DJANGO_SUPERUSER_USERNAME = "admin"
settings.DJANGO_SUPERUSER_PASSWORD = "admin12345"
settings.DJANGO_SUPERUSER_EMAIL = "admin@example.com"
call_command("bootstrap_governance")
assert not Group.objects.filter(name="Support Engineer").exists()
support_role = Group.objects.get(name="技术支持")
assert support_role.user_set.filter(username="support").exists()

View File

@@ -83,5 +83,9 @@ CSRF_TRUSTED_ORIGINS = os.getenv(
"http://localhost:5173,http://127.0.0.1:5173",
).split(",")
DJANGO_SUPERUSER_USERNAME = os.getenv("DJANGO_SUPERUSER_USERNAME", "admin")
DJANGO_SUPERUSER_PASSWORD = os.getenv("DJANGO_SUPERUSER_PASSWORD", "admin12345")
DJANGO_SUPERUSER_EMAIL = os.getenv("DJANGO_SUPERUSER_EMAIL", "admin@example.com")
CELERY_BROKER_URL = os.getenv("CELERY_BROKER_URL", "redis://localhost:6379/0")
CELERY_RESULT_BACKEND = os.getenv("CELERY_RESULT_BACKEND", "redis://localhost:6379/1")