feat: 支持治理配置通过admin维护

This commit is contained in:
2026-06-04 02:14:07 +08:00
parent 0d4e02b9dc
commit 5808dd794d
6 changed files with 327 additions and 1 deletions

View File

@@ -2,6 +2,7 @@ from pathlib import Path
import yaml
from django.conf import settings
from django.db.utils import OperationalError, ProgrammingError
def governance_defaults() -> dict:
@@ -81,8 +82,61 @@ def read_governance_yaml() -> dict:
def load_governance_config() -> dict:
defaults = governance_defaults()
config = read_governance_yaml()
db_config = load_governance_config_from_db()
for key, default_value in defaults.items():
configured_value = config.get(key)
configured_value = db_config.get(key) or config.get(key)
if isinstance(default_value, list) and configured_value:
defaults[key] = configured_value
return defaults
def load_governance_config_from_db() -> dict:
try:
from apps.platform_ui.models import FeishuNotifyConfig, OwnerMapping, WordTemplateMapping
except Exception:
return {}
try:
owner_mappings = [
{
"owner_role": item.owner_role,
"owner_name": item.owner_name,
"department": item.department,
"chapter_scope": item.chapter_scope,
"risk_scope": item.risk_scope,
"feishu_user_id": item.feishu_user_id,
"feishu_open_id": item.feishu_open_id,
"feishu_name": item.feishu_name,
"notify_enabled": "" if item.notify_enabled else "",
}
for item in OwnerMapping.objects.filter(is_active=True)
]
feishu_configs = [
{
"config_name": item.config_name,
"notify_reason": item.notify_reason,
"channel": item.channel,
"message_template": item.message_template,
"status": item.status,
}
for item in FeishuNotifyConfig.objects.filter(is_active=True)
]
template_mappings = [
{
"template_name": item.template_name,
"output_type": item.output_type,
"version": item.version,
"placeholder_count": item.placeholder_count,
"status": item.status,
"field_mapping_summary": item.field_mapping_summary,
}
for item in WordTemplateMapping.objects.filter(is_active=True)
]
except (OperationalError, ProgrammingError, RuntimeError):
return {}
return {
"owner_mappings": owner_mappings,
"feishu_configs": feishu_configs,
"template_mappings": template_mappings,
}

40
apps/platform_ui/admin.py Normal file
View File

@@ -0,0 +1,40 @@
from django.contrib import admin
from .models import FeishuNotifyConfig, OwnerMapping, WordTemplateMapping
@admin.register(OwnerMapping)
class OwnerMappingAdmin(admin.ModelAdmin):
list_display = (
"owner_role",
"owner_name",
"department",
"chapter_scope",
"risk_scope",
"feishu_user_id",
"notify_enabled",
"is_active",
)
list_filter = ("notify_enabled", "is_active", "department")
search_fields = ("owner_role", "owner_name", "department", "chapter_scope", "risk_scope")
@admin.register(FeishuNotifyConfig)
class FeishuNotifyConfigAdmin(admin.ModelAdmin):
list_display = ("config_name", "notify_reason", "channel", "status", "is_active")
list_filter = ("notify_reason", "status", "is_active")
search_fields = ("config_name", "channel", "message_template")
@admin.register(WordTemplateMapping)
class WordTemplateMappingAdmin(admin.ModelAdmin):
list_display = (
"template_name",
"output_type",
"version",
"placeholder_count",
"status",
"is_active",
)
list_filter = ("output_type", "status", "is_active")
search_fields = ("template_name", "version", "field_mapping_summary")

View File

@@ -0,0 +1,108 @@
# Generated by Django 5.2.14 on 2026-06-03 18:11
from django.db import migrations, models
class Migration(migrations.Migration):
initial = True
dependencies = []
operations = [
migrations.CreateModel(
name="FeishuNotifyConfig",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("config_name", models.CharField(max_length=100)),
(
"notify_reason",
models.CharField(
choices=[
("task_completed", "task_completed"),
("task_failed", "task_failed"),
],
db_index=True,
max_length=32,
),
),
("channel", models.CharField(blank=True, max_length=100)),
("message_template", models.CharField(blank=True, max_length=255)),
("status", models.CharField(blank=True, max_length=32)),
("is_active", models.BooleanField(db_index=True, default=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["notify_reason", "id"],
},
),
migrations.CreateModel(
name="OwnerMapping",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("owner_role", models.CharField(db_index=True, max_length=100)),
("owner_name", models.CharField(max_length=100)),
("department", models.CharField(blank=True, max_length=100)),
("chapter_scope", models.CharField(blank=True, max_length=100)),
("risk_scope", models.CharField(blank=True, max_length=255)),
("feishu_user_id", models.CharField(blank=True, max_length=100)),
("feishu_open_id", models.CharField(blank=True, max_length=100)),
("feishu_name", models.CharField(blank=True, max_length=100)),
("notify_enabled", models.BooleanField(default=True)),
("is_active", models.BooleanField(db_index=True, default=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["owner_role", "id"],
},
),
migrations.CreateModel(
name="WordTemplateMapping",
fields=[
(
"id",
models.BigAutoField(
auto_created=True,
primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("template_name", models.CharField(max_length=100)),
(
"output_type",
models.CharField(
default="registration_word_export_report", max_length=100
),
),
("version", models.CharField(blank=True, max_length=50)),
("placeholder_count", models.PositiveIntegerField(default=0)),
("status", models.CharField(blank=True, max_length=32)),
("field_mapping_summary", models.CharField(blank=True, max_length=255)),
("is_active", models.BooleanField(db_index=True, default=True)),
("created_at", models.DateTimeField(auto_now_add=True)),
("updated_at", models.DateTimeField(auto_now=True)),
],
options={
"ordering": ["template_name", "id"],
},
),
]

View File

View File

@@ -0,0 +1,78 @@
from django.db import models
class OwnerMapping(models.Model):
"""
责任人映射。
首版以 Django Admin 作为手工维护入口,字段口径与通知载荷保持一致。
"""
owner_role = models.CharField(max_length=100, db_index=True)
owner_name = models.CharField(max_length=100)
department = models.CharField(max_length=100, blank=True)
chapter_scope = models.CharField(max_length=100, blank=True)
risk_scope = models.CharField(max_length=255, blank=True)
feishu_user_id = models.CharField(max_length=100, blank=True)
feishu_open_id = models.CharField(max_length=100, blank=True)
feishu_name = models.CharField(max_length=100, blank=True)
notify_enabled = models.BooleanField(default=True)
is_active = models.BooleanField(default=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["owner_role", "id"]
def __str__(self) -> str:
return f"{self.owner_role}-{self.owner_name}"
class FeishuNotifyConfig(models.Model):
"""
飞书通知配置。
"""
NOTIFY_REASON_COMPLETED = "task_completed"
NOTIFY_REASON_FAILED = "task_failed"
NOTIFY_REASON_CHOICES = [
(NOTIFY_REASON_COMPLETED, "task_completed"),
(NOTIFY_REASON_FAILED, "task_failed"),
]
config_name = models.CharField(max_length=100)
notify_reason = models.CharField(max_length=32, choices=NOTIFY_REASON_CHOICES, db_index=True)
channel = models.CharField(max_length=100, blank=True)
message_template = models.CharField(max_length=255, blank=True)
status = models.CharField(max_length=32, blank=True)
is_active = models.BooleanField(default=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["notify_reason", "id"]
def __str__(self) -> str:
return self.config_name
class WordTemplateMapping(models.Model):
"""
Word 模板与字段映射摘要。
"""
template_name = models.CharField(max_length=100)
output_type = models.CharField(max_length=100, default="registration_word_export_report")
version = models.CharField(max_length=50, blank=True)
placeholder_count = models.PositiveIntegerField(default=0)
status = models.CharField(max_length=32, blank=True)
field_mapping_summary = models.CharField(max_length=255, blank=True)
is_active = models.BooleanField(default=True, db_index=True)
created_at = models.DateTimeField(auto_now_add=True)
updated_at = models.DateTimeField(auto_now=True)
class Meta:
ordering = ["template_name", "id"]
def __str__(self) -> str:
return self.template_name

View File

@@ -1,7 +1,9 @@
from django.urls import reverse
from django.test import override_settings
from django.contrib import admin
from apps.platform_ui.services import get_platform_demo_context
from apps.platform_ui.models import FeishuNotifyConfig, OwnerMapping, WordTemplateMapping
def test_knowledge_base_page_shows_governance_sections(client):
@@ -100,3 +102,47 @@ template_mappings:
assert "Word 模板与字段映射" in content
assert "风险摘要导出模板" in content
assert "风险等级 / 产品名称 / 责任人" in content
def test_get_platform_demo_context_prefers_database_governance_entries(db):
OwnerMapping.objects.create(
owner_role="数据库负责人",
owner_name="周八",
department="注册事务部",
chapter_scope="CH5",
risk_scope="数据库覆盖",
feishu_user_id="ou_db_1",
feishu_open_id="on_db_1",
feishu_name="周八",
notify_enabled=True,
is_active=True,
)
FeishuNotifyConfig.objects.create(
config_name="数据库通知配置",
notify_reason="task_failed",
channel="群机器人",
message_template="异常摘要 + @处理人",
status="启用",
is_active=True,
)
WordTemplateMapping.objects.create(
template_name="数据库模板",
output_type="registration_word_export_report",
version="V3.0",
placeholder_count=16,
status="启用",
field_mapping_summary="产品名称 / 规格 / 风险等级",
is_active=True,
)
context = get_platform_demo_context()
assert context["owner_mappings"][0]["owner_name"] == "周八"
assert context["feishu_configs"][0]["config_name"] == "数据库通知配置"
assert context["template_mappings"][0]["template_name"] == "数据库模板"
def test_governance_models_are_registered_in_admin():
assert OwnerMapping in admin.site._registry
assert FeishuNotifyConfig in admin.site._registry
assert WordTemplateMapping in admin.site._registry