feat: 支持治理配置通过admin维护
This commit is contained in:
@@ -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
40
apps/platform_ui/admin.py
Normal 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")
|
||||
108
apps/platform_ui/migrations/0001_initial.py
Normal file
108
apps/platform_ui/migrations/0001_initial.py
Normal 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"],
|
||||
},
|
||||
),
|
||||
]
|
||||
0
apps/platform_ui/migrations/__init__.py
Normal file
0
apps/platform_ui/migrations/__init__.py
Normal file
78
apps/platform_ui/models.py
Normal file
78
apps/platform_ui/models.py
Normal 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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user