From 96548ed66be4d7f58c5dab5e22cabee90e0172bd Mon Sep 17 00:00:00 2001 From: jinweijie Date: Fri, 4 Sep 2026 10:20:40 +0800 Subject: [PATCH 1/8] ``` feat(settings): add fallback mechanism for missing conf.env module - Wrap conf.env import in try-except block to handle ModuleNotFoundError - Add comprehensive fallback configuration with default SQLite settings - Include detailed warning log message guiding users to create env config - Set default database, Redis, and application settings for development use - Configure default allowed hosts and column exclude apps ``` --- application/settings.py | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/application/settings.py b/application/settings.py index 76540fe..c7bbf31 100644 --- a/application/settings.py +++ b/application/settings.py @@ -28,7 +28,31 @@ # ******************** Dynamic Config ******************** # # ================================================= # -from conf.env import * +try: + from conf.env import * +except ModuleNotFoundError: + import logging as _lg + _lg.getLogger(__name__).warning( + "conf.env module not found, using default settings (SQLite). " + "Copy conf/env.example.py to conf/env.py and customize it for production use." + ) + DATABASE_ENGINE = "django.db.backends.sqlite3" + DATABASE_NAME = os.path.join(BASE_DIR, "db.sqlite3") + DATABASE_USER = "" + DATABASE_PASSWORD = "" + DATABASE_HOST = "" + DATABASE_PORT = "" + TABLE_PREFIX = "" + REDIS_DB = 0 + CELERY_BROKER_DB = 1 + REDIS_PASSWORD = "" + REDIS_HOST = "127.0.0.1" + REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' + DEBUG = True + ENABLE_LOGIN_ANALYSIS_LOG = True + LOGIN_NO_CAPTCHA_AUTH = True + ALLOWED_HOSTS = ["*"] + COLUMN_EXCLUDE_APPS = [] # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.2/howto/deployment/checklist/ From f09133bbd2548cd384efd8dbd961cdb455cf43f7 Mon Sep 17 00:00:00 2001 From: jinweijie Date: Fri, 4 Sep 2026 10:36:46 +0800 Subject: [PATCH 2/8] ``` refactor(serializers): remove unused workflow approval models and add EE import check Remove obsolete workflow approval related models including WorkflowApprove, WorkflowApprovalRule, WorkflowApprovalNode, WorkflowApprovalInstance, WorkflowApprovalNodeExecution, and other unused models like HostLog, ManagedProgram, ProgramInstallTemplate, ProgramHostBinding, etc. Add conditional import logic for enterprise edition serializers with proper fallback handling to prevent AttributeError when EE module is missing. The thin wrapper serializers now only load when _EE_SUPERVISOR_PROGRAM_OK flag is True, otherwise fallback serializers are used. Fix DRF field info error in ProgramCommandViewSet when EE module is not available. ``` --- taurus/serializers.py | 51 ++++++++++++++++++++++--------------------- taurus/views.py | 14 +----------- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/taurus/serializers.py b/taurus/serializers.py index cd12e2e..01f2485 100644 --- a/taurus/serializers.py +++ b/taurus/serializers.py @@ -6,19 +6,11 @@ from dvadmin.utils.validator import CustomUniqueValidator from .models import ( Workflow, WorkflowCategory, WorkflowStep, WorkflowExecution, WorkflowStepExecution, - WorkflowApprove, - WorkflowApprovalRule, WorkflowApprovalNode, WorkflowApprovalInstance, WorkflowApprovalNodeExecution, Host, Schedule, ScheduleExecution, RegistrationToken, HostHeartbeat, HeartbeatServer, ProgramInstallConfig, ProgramInstallPolicy, ProgramCommand, - HostLog, ManagedProgram, ProgramInstallTemplate, ProgramHostBinding, - LogCommand, OpsExecution, OpsExecutionApproval, - ScriptCategory, Script, ScriptVersion, ScriptPermission, ScriptTask, + ManagedProgram, ProgramInstallTemplate, ProgramHostBinding, + OpsExecution, ScriptCategory, Script, ScriptVersion, ScriptPermission, ScriptTask, ScriptTaskExecution, - ScriptAudit, ScriptApprove, - ScriptApprovalRule, ScriptApprovalNode, ScriptApprovalInstance, ScriptApprovalNodeExecution, - ScriptCheckRule, - SharePermissionDef, ScriptSharePermission, WorkflowSharePermission, - ShareLink, ShareLinkAccessLog, ) @@ -1269,6 +1261,12 @@ class Meta: # [M2.4 Thin Wrapper] Supervisor 程序管理高级 Serializers(F_PROGRAM_INSTALL_TEMPLATE / # F_PROGRAM_HOST_BINDING / F_PROGRAM_INSTALL_CONFIG / F_PROGRAM_COMMAND_BATCH / # F_PROGRAM_INSTALL_POLICY — EE 专属。原类名保留不动,thin pass 继承 taurus_ee.*。 +# +# 注意:这些 Serializer 均已在上方定义了 CE 基础版本(含正确的 Meta.model)。 +# Thin Wrapper 仅在 EE 成功导入时才重新定义(继承 EE 扩展);若 EE 缺失, +# 必须保留原 CE 版本不变,否则继承 _EEFallbackSerializer (model=None) 会导致 +# DRF 在 get_field_info 阶段抛出 AttributeError: 'NoneType' object has no attribute '_meta' +# (尤其是 ProgramCommandViewSet,其基础 CRUD 不走 EE Gate)。 # ============================================================================ try: from taurus_ee.serializers.supervisor_program import ( @@ -1288,7 +1286,9 @@ class Meta: _EEProgramInstallPolicyCreateSerializer as _EE_PIP_CreateSer, _EEProgramInstallPolicyUpdateSerializer as _EE_PIP_UpdateSer, ) + _EE_SUPERVISOR_PROGRAM_OK = True except ImportError: + _EE_SUPERVISOR_PROGRAM_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EE_PC_CreateSer(_FBSer): pass class _EE_PC_Ser(_FBSer): pass @@ -1307,21 +1307,22 @@ class _EE_PIT_Ser(_FBSer): pass class _EE_PIT_UpdateSer(_FBSer): pass -class ProgramInstallTemplateSerializer(_EE_PIT_Ser): pass # noqa: E701 Thin Wrapper -class ProgramInstallTemplateCreateSerializer(_EE_PIT_CreateSer): pass # noqa: E701 -class ProgramInstallTemplateUpdateSerializer(_EE_PIT_UpdateSer): pass # noqa: E701 -class ProgramHostBindingSerializer(_EE_PHB_Ser): pass # noqa: E701 -class ProgramHostBindingCreateSerializer(_EE_PHB_CreateSer): pass # noqa: E701 -class ProgramHostBindingUpdateSerializer(_EE_PHB_UpdateSer): pass # noqa: E701 -class ProgramInstallConfigSerializer(_EE_PIC_Ser): pass # noqa: E701 -class ProgramInstallConfigCreateSerializer(_EE_PIC_CreateSer): pass # noqa: E701 -class ProgramInstallConfigUpdateSerializer(_EE_PIC_UpdateSer): pass # noqa: E701 -class ProgramCommandSerializer(_EE_PC_Ser): pass # noqa: E701 -class ProgramCommandCreateSerializer(_EE_PC_CreateSer): pass # noqa: E701 -class ProgramCommandUpdateSerializer(_EE_PC_UpdateSer): pass # noqa: E701 -class ProgramInstallPolicySerializer(_EE_PIP_Ser): pass # noqa: E701 -class ProgramInstallPolicyCreateSerializer(_EE_PIP_CreateSer): pass # noqa: E701 -class ProgramInstallPolicyUpdateSerializer(_EE_PIP_UpdateSer): pass # noqa: E701 +if _EE_SUPERVISOR_PROGRAM_OK: + class ProgramInstallTemplateSerializer(_EE_PIT_Ser): pass # noqa: E701 Thin Wrapper + class ProgramInstallTemplateCreateSerializer(_EE_PIT_CreateSer): pass # noqa: E701 + class ProgramInstallTemplateUpdateSerializer(_EE_PIT_UpdateSer): pass # noqa: E701 + class ProgramHostBindingSerializer(_EE_PHB_Ser): pass # noqa: E701 + class ProgramHostBindingCreateSerializer(_EE_PHB_CreateSer): pass # noqa: E701 + class ProgramHostBindingUpdateSerializer(_EE_PHB_UpdateSer): pass # noqa: E701 + class ProgramInstallConfigSerializer(_EE_PIC_Ser): pass # noqa: E701 + class ProgramInstallConfigCreateSerializer(_EE_PIC_CreateSer): pass # noqa: E701 + class ProgramInstallConfigUpdateSerializer(_EE_PIC_UpdateSer): pass # noqa: E701 + class ProgramCommandSerializer(_EE_PC_Ser): pass # noqa: E701 + class ProgramCommandCreateSerializer(_EE_PC_CreateSer): pass # noqa: E701 + class ProgramCommandUpdateSerializer(_EE_PC_UpdateSer): pass # noqa: E701 + class ProgramInstallPolicySerializer(_EE_PIP_Ser): pass # noqa: E701 + class ProgramInstallPolicyCreateSerializer(_EE_PIP_CreateSer): pass # noqa: E701 + class ProgramInstallPolicyUpdateSerializer(_EE_PIP_UpdateSer): pass # noqa: E701 # ------------------ [M2.5 Thin Wrapper] HostLog & LogCommand (log center EE) ------------------ diff --git a/taurus/views.py b/taurus/views.py index 9552a1b..68e86cb 100644 --- a/taurus/views.py +++ b/taurus/views.py @@ -15,21 +15,16 @@ from taurus.models import ( Workflow, WorkflowCategory, WorkflowStep, WorkflowExecution, WorkflowStepExecution, - WorkflowApprove, Host, Schedule, ScheduleExecution, RegistrationToken, HostHeartbeat, HeartbeatServer, ProgramInstallConfig, ProgramInstallPolicy, ProgramCommand, ManagedProgram, HostLog, ProgramInstallTemplate, ProgramHostBinding, LogCommand, OpsExecution, OpsExecutionApproval, ScriptCategory, Script, ScriptVersion, ScriptPermission, ScriptTask, ScriptTaskExecution, ScriptAudit, ScriptApprove, - ScriptApprovalRule, ScriptApprovalNode, ScriptApprovalInstance, ScriptApprovalNodeExecution, SharePermissionDef, ScriptSharePermission, WorkflowSharePermission, - ShareLink, ShareLinkAccessLog, ) from taurus.serializers import ( WorkflowSerializer, WorkflowListSerializer, WorkflowExportSerializer, WorkflowStepSerializer, WorkflowCategorySerializer, WorkflowCategoryCreateSerializer, WorkflowCategoryUpdateSerializer, - WorkflowApproveSerializer, WorkflowApproveCreateSerializer, - WorkflowApproveApproveSerializer, WorkflowApproveRejectSerializer, WorkflowExecutionSerializer, ScheduleSerializer, ScheduleListSerializer, ScheduleExecutionSerializer, HostSerializer, ExecutorRegisterSerializer, RegistrationTokenSerializer, SupervisorHeartbeatSerializer, HostHeartbeatSerializer, HeartbeatServerSerializer, @@ -50,18 +45,12 @@ ScriptVersionSerializer, ScriptPermissionSerializer, ScriptTaskSerializer, ScriptTaskCreateSerializer, ScriptTaskUpdateSerializer, ScriptTaskExecutionSerializer, - ScriptAuditSerializer, ScriptApproveSerializer, - ScriptApprovalRuleSerializer, ScriptApprovalNodeSerializer, - ScriptApprovalInstanceSerializer, ScriptApprovalNodeExecutionSerializer, SharePermissionDefSerializer, ScriptSharePermissionSerializer, WorkflowSharePermissionSerializer, - SharePermissionBatchCreateSerializer, ShareLinkSerializer, - ShareLinkActivateSerializer, ShareLinkAccessLogSerializer, - TaskCenterItemSerializer, + SharePermissionBatchCreateSerializer, TaskCenterItemSerializer, ) from taurus.utils.share_permission import ( SharePermissionChecker, - ShareLinkService, ShareVisibleQS, require_share_perm, ) @@ -75,7 +64,6 @@ from rest_framework.decorators import api_view, permission_classes from rest_framework.response import Response from rest_framework import status as drf_status -from rest_framework.permissions import AllowAny logger = logging.getLogger(__name__) From 24e31913ba7d2d4c3904a01907d56e1cdd99e637 Mon Sep 17 00:00:00 2001 From: jinweijie Date: Fri, 4 Sep 2026 10:39:24 +0800 Subject: [PATCH 3/8] ``` chore(serializers): add audit script to check Meta.model=None bug in Thin Wrapper Serializers Add audit script to identify serializers with Meta.model=None issues when EE is absent. The script checks various serializer classes including WorkflowDAGVersionSerializer, ProgramInstallTemplateSerializers, HostLogSerializers, OpsExecutionApprovalSerializers, Script approval serializers, SharePermission serializers, Workflow approval serializers, Scheduler HA serializers, and Extension center placeholders. The audit identifies problematic serializers by attempting to instantiate them and accessing their fields property which triggers DRF get_field_info and reveals ModelSerializer subclasses with Meta.model=None configuration that could cause HTTP 400/500 errors when ViewSet has no EE gate. ``` --- _audit_serializers.py | 127 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 127 insertions(+) create mode 100644 _audit_serializers.py diff --git a/_audit_serializers.py b/_audit_serializers.py new file mode 100644 index 0000000..f10f993 --- /dev/null +++ b/_audit_serializers.py @@ -0,0 +1,127 @@ +"""Audit all Thin Wrapper Serializers for Meta.model=None bug when EE is absent.""" +import os, sys, traceback +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'application.settings') +sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) + +import django +django.setup() + +from taurus import serializers as ser_module +import inspect + +# Grep: all classes defined in taurus.serializers that have a Meta inner class +# We will check Meta.model and flag those with None (or missing) + +SERIALIZERS_TO_CHECK = [ + # M2.2 Workflow DAG + 'WorkflowDAGVersionSerializer', + # M2.4 Supervisor program (already fixed — verify) + 'ProgramInstallTemplateSerializer', + 'ProgramInstallTemplateCreateSerializer', + 'ProgramInstallTemplateUpdateSerializer', + 'ProgramHostBindingSerializer', + 'ProgramHostBindingCreateSerializer', + 'ProgramHostBindingUpdateSerializer', + 'ProgramInstallConfigSerializer', + 'ProgramInstallConfigCreateSerializer', + 'ProgramInstallConfigUpdateSerializer', + 'ProgramCommandSerializer', + 'ProgramCommandCreateSerializer', + 'ProgramCommandUpdateSerializer', + 'ProgramInstallPolicySerializer', + 'ProgramInstallPolicyCreateSerializer', + 'ProgramInstallPolicyUpdateSerializer', + # M2.5 HostLog / LogCommand + 'HostLogSerializer', + 'HostLogReceiveSerializer', + 'LogCommandSerializer', + 'LogCommandCreateSerializer', + 'LogCommandUpdateSerializer', + # Ops approval + 'OpsExecutionApprovalSerializer', + 'OpsExecutionApprovalListSerializer', + 'OpsExecutionApprovalCreateSerializer', + 'OpsExecutionApprovalActionSerializer', + # Script library EE + 'ScriptAuditSerializer', + 'ScriptApproveSerializer', + 'ScriptApprovalRuleSerializer', + 'ScriptApprovalNodeSerializer', + 'ScriptApprovalInstanceSerializer', + 'ScriptApprovalNodeExecutionSerializer', + 'ScriptCheckRuleSerializer', + # Share permission + 'SharePermissionDefSerializer', + 'ScriptSharePermissionSerializer', + 'WorkflowSharePermissionSerializer', + 'SharePermissionBatchCreateSerializer', + 'ShareLinkSerializer', + 'ShareLinkActivateSerializer', + 'ShareLinkAccessLogSerializer', + # Workflow approval + 'WorkflowApproveSerializer', + 'WorkflowApproveCreateSerializer', + 'WorkflowApproveApproveSerializer', + 'WorkflowApproveRejectSerializer', + 'WorkflowApproveCompatSerializer', + 'WorkflowApprovalRuleSerializer', + 'WorkflowApprovalNodeSerializer', + 'WorkflowApprovalInstanceSerializer', + 'WorkflowApprovalNodeExecutionSerializer', + # Scheduler HA + 'ScheduleExecutionHASerializer', + 'ScriptTaskExecutionUnifiedSerializer', + 'SchedulerAlertRuleSerializer', + 'UnifiedScheduleListRequestSerializer', + 'UnifiedScheduleStatsSerializer', + 'SchedulerAlertEventSerializer', + # Extension center placeholders + 'ContactLeadSerializer', + 'TaskCenterItemSerializer', + 'KnowledgeBasePlaceholderSerializer', + 'InspectionCenterPlaceholderSerializer', + 'ToolsCenterPlaceholderSerializer', + 'BackupRestoreCenterPlaceholderSerializer', + 'DownloadCenterPlaceholderSerializer', +] + +problems = [] +oks = [] +for name in SERIALIZERS_TO_CHECK: + cls = getattr(ser_module, name, None) + if cls is None: + problems.append((name, 'CLASS NOT FOUND', None)) + continue + meta = getattr(cls, 'Meta', None) + model = getattr(meta, 'model', None) if meta else None + # Test if serializer instantiation + .fields access works (this triggers DRF get_field_info) + try: + inst = cls() + _ = inst.fields # This is the crash site path + fields_ok = True + except Exception as e: + fields_ok = False + problems.append((name, f'fields lookup ERROR: {type(e).__name__}: {e}', model)) + continue + if model is None: + # Check if this is intentional (non-model serializer, e.g. serializers.Serializer base) + from rest_framework import serializers as drf_ser + is_non_model_serializer = issubclass(cls, drf_ser.Serializer) and not issubclass(cls, drf_ser.ModelSerializer) + if is_non_model_serializer: + oks.append((name, f'OK (non-model Serializer)', model)) + else: + problems.append((name, 'Meta.model = None (but ModelSerializer subclass)', model)) + else: + oks.append((name, f'OK model={model.__name__}', model)) + +print('='*90) +print(f'CHECKED {len(SERIALIZERS_TO_CHECK)} SERIALIZERS — {len(problems)} PROBLEMS, {len(oks)} OK') +print('='*90) +if problems: + print('\n[PROBLEMS — likely cause HTTP 400/500 when ViewSet has no EE gate]:') + for p in problems: + print(f' ⚠️ {p[0]:<55s} | {p[1]} | model={p[2]}') +print() +print('[OK]:') +for o in oks: + print(f' ✅ {o[0]:<55s} | {o[1]}') \ No newline at end of file From 248f4474b9f759a8d0cf033e0eb7ea3503b595f3 Mon Sep 17 00:00:00 2001 From: jinweijie Date: Fri, 4 Sep 2026 11:12:43 +0800 Subject: [PATCH 4/8] ``` feat(serializers): add conditional EE serializer definitions with import guards Add defensive import guards for all EE serializers to prevent AttributeError when EE modules are not available. Introduce *_OK flags to conditionally define serializer classes only when EE imports succeed. This fixes an issue where CE functionality would fail when accessing WorkflowDAGVersionSerializer during workflow publishing due to missing Meta.model attribute when EE is not installed. Also adds CE base implementation for WorkflowDAGVersionSerializer to ensure core functionality remains available without EE dependencies. ``` --- taurus/serializers.py | 256 +++++++++++++++++++++++++----------------- 1 file changed, 156 insertions(+), 100 deletions(-) diff --git a/taurus/serializers.py b/taurus/serializers.py index 01f2485..02ef748 100644 --- a/taurus/serializers.py +++ b/taurus/serializers.py @@ -834,18 +834,34 @@ class WorkflowExecuteSerializer(serializers.Serializer): # [M2.2 Thin Wrapper] WorkflowDAGVersionSerializer → EE 实现 +# +# 先提供 CE 基础版本(真实 Meta.model),否则 EE 缺失时 WorkflowViewSet.publish +# (CE 功能,无 EE Gate)在成功返回时会访问 WorkflowDAGVersionSerializer(dag_ver).data +# 触发 DRF get_field_info → AttributeError: 'NoneType' object has no attribute '_meta'。 +from taurus.workflow.models import WorkflowDAGVersion as _WorkflowDAGVersionModel # noqa: E402 (circular import guard) +class WorkflowDAGVersionSerializer(CustomModelSerializer): + """Workflow DAG published version serializer (CE base — EE overrides via Thin Wrapper below).""" + class Meta: + model = _WorkflowDAGVersionModel + fields = '__all__' + read_only_fields = ['id', 'create_datetime', 'update_datetime', 'creator'] + + try: from taurus_ee.serializers.workflow_dag import ( WorkflowDAGVersionSerializer as _EEWorkflowDAGVersionSerializer, ) + _EE_WORKFLOW_DAG_SER_OK = True except ImportError: + _EE_WORKFLOW_DAG_SER_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEWorkflowDAGVersionSerializer(_FBSer): pass -class WorkflowDAGVersionSerializer(_EEWorkflowDAGVersionSerializer): - """Thin wrapper — EE implementation in taurus_ee.serializers.workflow_dag""" - pass +if _EE_WORKFLOW_DAG_SER_OK: + class WorkflowDAGVersionSerializer(_EEWorkflowDAGVersionSerializer): # noqa: F811 + """Thin wrapper — EE implementation in taurus_ee.serializers.workflow_dag""" + pass class WorkflowNodeExecutionSerializer(CustomModelSerializer): @@ -1326,6 +1342,9 @@ class ProgramInstallPolicyUpdateSerializer(_EE_PIP_UpdateSer): pass # noqa: E70 # ------------------ [M2.5 Thin Wrapper] HostLog & LogCommand (log center EE) ------------------ +# 这些 Serializer 是 EE 专属功能(HostLogViewSet / LogCommandViewSet / TaskCenterViewSet +# 等均带 dispatch EE Gate),但仍然加 _EE_OK 守卫,避免未来有人绕过 gate 直接使用 +# 时踩到 _EEFallbackSerializer 的 Meta.model=None 问题。 try: from taurus_ee.serializers.log_ext_center import ( _EEHostLogSerializer, @@ -1341,7 +1360,9 @@ class ProgramInstallPolicyUpdateSerializer(_EE_PIP_UpdateSer): pass # noqa: E70 _EEBackupRestoreCenterPlaceholderSerializer, _EEDownloadCenterPlaceholderSerializer, ) + _EE_LOG_EXT_OK = True except ImportError: + _EE_LOG_EXT_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEBackupRestoreCenterPlaceholderSerializer(_FBSer): pass class _EEContactLeadSerializer(_FBSer): pass @@ -1357,14 +1378,15 @@ class _EETaskCenterItemSerializer(_FBSer): pass class _EEToolsCenterPlaceholderSerializer(_FBSer): pass -class HostLogSerializer(_EEHostLogSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogSerializer""" - pass +if _EE_LOG_EXT_OK: + class HostLogSerializer(_EEHostLogSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogSerializer""" + pass -class HostLogReceiveSerializer(_EEHostLogReceiveSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogReceiveSerializer""" - pass + class HostLogReceiveSerializer(_EEHostLogReceiveSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogReceiveSerializer""" + pass class ManagedProgramSerializer(CustomModelSerializer): @@ -1382,19 +1404,20 @@ class Meta: -class LogCommandSerializer(_EELogCommandSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandSerializer""" - pass +if _EE_LOG_EXT_OK: + class LogCommandSerializer(_EELogCommandSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandSerializer""" + pass -class LogCommandCreateSerializer(_EELogCommandCreateSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandCreateSerializer""" - pass + class LogCommandCreateSerializer(_EELogCommandCreateSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandCreateSerializer""" + pass -class LogCommandUpdateSerializer(_EELogCommandUpdateSerializer): - """Thin Wrapper (NEW M2.5) — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandUpdateSerializer""" - pass + class LogCommandUpdateSerializer(_EELogCommandUpdateSerializer): + """Thin Wrapper (NEW M2.5) — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandUpdateSerializer""" + pass @@ -1661,7 +1684,9 @@ def get_duration(self, obj): _EEOpsExecutionApprovalCreateSerializer, _EEOpsExecutionApprovalActionSerializer, ) + _EE_OPS_APPROVAL_OK = True except ImportError: + _EE_OPS_APPROVAL_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEOpsExecutionApprovalActionSerializer(_FBSer): pass class _EEOpsExecutionApprovalCreateSerializer(_FBSer): pass @@ -1669,10 +1694,11 @@ class _EEOpsExecutionApprovalListSerializer(_FBSer): pass class _EEOpsExecutionApprovalSerializer(_FBSer): pass -class OpsExecutionApprovalSerializer(_EEOpsExecutionApprovalSerializer): pass -class OpsExecutionApprovalListSerializer(_EEOpsExecutionApprovalListSerializer): pass -class OpsExecutionApprovalCreateSerializer(_EEOpsExecutionApprovalCreateSerializer): pass -class OpsExecutionApprovalActionSerializer(_EEOpsExecutionApprovalActionSerializer): pass +if _EE_OPS_APPROVAL_OK: + class OpsExecutionApprovalSerializer(_EEOpsExecutionApprovalSerializer): pass + class OpsExecutionApprovalListSerializer(_EEOpsExecutionApprovalListSerializer): pass + class OpsExecutionApprovalCreateSerializer(_EEOpsExecutionApprovalCreateSerializer): pass + class OpsExecutionApprovalActionSerializer(_EEOpsExecutionApprovalActionSerializer): pass # ========================== ScriptlibrarySerializationserver ========================== @@ -2134,10 +2160,16 @@ def get_host_summary(self, obj) -> dict: # Script* EE 系列 Thin Wrapper(实现在 taurus_ee.serializers.*) # 保留原名:taurus/urls.py + views.py 中 `from taurus.serializers import X` # 所有旧引用保持 0 改动。 +# +# 防御性修复:所有 try/except 均引入 _*_OK 标志位,仅在 EE 导入成功时才重新定义类。 +# 否则不产生 class 重新定义(这些 Serializer 本就只能在 EE-gated 路径下实例化; +# 若强行实例化会报 NameError,但这比 Meta.model=None 静默抛 AttributeError 好诊断)。 # ================================================================ try: from taurus_ee.serializers.script_audit import ScriptAuditSerializer as _EEScriptAuditSerializer + _EE_SCRIPT_AUDIT_OK = True except ImportError: + _EE_SCRIPT_AUDIT_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptAuditSerializer(_FBSer): pass @@ -2149,7 +2181,9 @@ class _EEScriptAuditSerializer(_FBSer): pass ScriptApprovalInstanceSerializer as _EEScriptApprovalInstanceSerializer, ScriptApprovalNodeExecutionSerializer as _EEScriptApprovalNodeExecutionSerializer, ) + _EE_SCRIPT_APPROVAL_OK = True except ImportError: + _EE_SCRIPT_APPROVAL_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptApprovalInstanceSerializer(_FBSer): pass class _EEScriptApprovalNodeExecutionSerializer(_FBSer): pass @@ -2159,7 +2193,9 @@ class _EEScriptApproveSerializer(_FBSer): pass try: from taurus_ee.serializers.script_check import ScriptCheckRuleSerializer as _EEScriptCheckRuleSerializer + _EE_SCRIPT_CHECK_OK = True except ImportError: + _EE_SCRIPT_CHECK_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptCheckRuleSerializer(_FBSer): pass @@ -2173,7 +2209,9 @@ class _EEScriptCheckRuleSerializer(_FBSer): pass ShareLinkActivateSerializer as _EEShareLinkActivateSerializer, ShareLinkAccessLogSerializer as _EEShareLinkAccessLogSerializer, ) + _EE_SHARE_PERM_OK = True except ImportError: + _EE_SHARE_PERM_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptSharePermissionSerializer(_FBSer): pass class _EEShareLinkAccessLogSerializer(_FBSer): pass @@ -2184,63 +2222,67 @@ class _EESharePermissionDefSerializer(_FBSer): pass class _EEWorkflowSharePermissionSerializer(_FBSer): pass -class ScriptAuditSerializer(_EEScriptAuditSerializer): - """Thin Wrapper → taurus_ee.serializers.script_audit.ScriptAuditSerializer""" - pass +if _EE_SCRIPT_AUDIT_OK: + class ScriptAuditSerializer(_EEScriptAuditSerializer): + """Thin Wrapper → taurus_ee.serializers.script_audit.ScriptAuditSerializer""" + pass -class ScriptApproveSerializer(_EEScriptApproveSerializer): - """Thin Wrapper.""" - pass +if _EE_SCRIPT_APPROVAL_OK: + class ScriptApproveSerializer(_EEScriptApproveSerializer): + """Thin Wrapper.""" + pass -class ScriptApprovalRuleSerializer(_EEScriptApprovalRuleSerializer): - pass + class ScriptApprovalRuleSerializer(_EEScriptApprovalRuleSerializer): + pass -class ScriptApprovalNodeSerializer(_EEScriptApprovalNodeSerializer): - pass + class ScriptApprovalNodeSerializer(_EEScriptApprovalNodeSerializer): + pass -class ScriptApprovalInstanceSerializer(_EEScriptApprovalInstanceSerializer): - pass + class ScriptApprovalInstanceSerializer(_EEScriptApprovalInstanceSerializer): + pass -class ScriptApprovalNodeExecutionSerializer(_EEScriptApprovalNodeExecutionSerializer): - pass + class ScriptApprovalNodeExecutionSerializer(_EEScriptApprovalNodeExecutionSerializer): + pass -class ScriptCheckRuleSerializer(_EEScriptCheckRuleSerializer): - pass +if _EE_SCRIPT_CHECK_OK: + class ScriptCheckRuleSerializer(_EEScriptCheckRuleSerializer): + pass # ---------------- share permission Thin Wrappers ---------------- -class SharePermissionDefSerializer(_EESharePermissionDefSerializer): - pass +if _EE_SHARE_PERM_OK: + class SharePermissionDefSerializer(_EESharePermissionDefSerializer): + pass -class ScriptSharePermissionSerializer(_EEScriptSharePermissionSerializer): - pass + class ScriptSharePermissionSerializer(_EEScriptSharePermissionSerializer): + pass -class WorkflowSharePermissionSerializer(_EEWorkflowSharePermissionSerializer): - pass + class WorkflowSharePermissionSerializer(_EEWorkflowSharePermissionSerializer): + pass -class SharePermissionBatchCreateSerializer(_EESharePermissionBatchCreateSerializer): - pass + class SharePermissionBatchCreateSerializer(_EESharePermissionBatchCreateSerializer): + pass -class ShareLinkSerializer(_EEShareLinkSerializer): - pass + class ShareLinkSerializer(_EEShareLinkSerializer): + pass -class ShareLinkActivateSerializer(_EEShareLinkActivateSerializer): - pass + class ShareLinkActivateSerializer(_EEShareLinkActivateSerializer): + pass -class ShareLinkAccessLogSerializer(_EEShareLinkAccessLogSerializer): - pass + class ShareLinkAccessLogSerializer(_EEShareLinkAccessLogSerializer): + pass # ================================================================ @@ -2248,8 +2290,9 @@ class ShareLinkAccessLogSerializer(_EEShareLinkAccessLogSerializer): # Thin Wrapper 提前覆盖,此处不再保留,避免与继承 EE 的版本双定义。 # 原 class ScriptApproveSerializer -> model = ScriptApprove 由 EE Meta 继承保证。 # ================================================================ -class ScriptApproveSerializer(_EEScriptApproveSerializer): # noqa: F811 - """(Double re-declare 仅兼容,与上方 Thin Wrapper 等价。)""" +if _EE_SCRIPT_APPROVAL_OK: + class ScriptApproveSerializer(_EEScriptApproveSerializer): # noqa: F811 + """(Double re-declare 仅兼容,与上方 Thin Wrapper 等价。)""" # 保留 Meta 注释以展示老代码模型 # model = ScriptApprove (已在 EE 版 Meta 中定义) pass @@ -2292,7 +2335,9 @@ class _LegacyMetaAnchor_ScriptApprove: # Never used WorkflowApprovalInstanceSerializer as _EEWorkflowApprovalInstanceSerializer, WorkflowApprovalNodeExecutionSerializer as _EEWorkflowApprovalNodeExecutionSerializer, ) + _EE_WF_APPROVAL_OK = True except ImportError: + _EE_WF_APPROVAL_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEWorkflowApprovalInstanceSerializer(_FBSer): pass class _EEWorkflowApprovalNodeExecutionSerializer(_FBSer): pass @@ -2305,15 +2350,16 @@ class _EEWorkflowApproveRejectSerializer(_FBSer): pass class _EEWorkflowApproveSerializer(_FBSer): pass -class WorkflowApproveSerializer(_EEWorkflowApproveSerializer): pass -class WorkflowApproveCreateSerializer(_EEWorkflowApproveCreateSerializer): pass -class WorkflowApproveApproveSerializer(_EEWorkflowApproveApproveSerializer): pass -class WorkflowApproveRejectSerializer(_EEWorkflowApproveRejectSerializer): pass -class WorkflowApproveCompatSerializer(_EEWorkflowApproveCompatSerializer): pass -class WorkflowApprovalRuleSerializer(_EEWorkflowApprovalRuleSerializer): pass -class WorkflowApprovalNodeSerializer(_EEWorkflowApprovalNodeSerializer): pass -class WorkflowApprovalInstanceSerializer(_EEWorkflowApprovalInstanceSerializer): pass -class WorkflowApprovalNodeExecutionSerializer(_EEWorkflowApprovalNodeExecutionSerializer): pass +if _EE_WF_APPROVAL_OK: + class WorkflowApproveSerializer(_EEWorkflowApproveSerializer): pass + class WorkflowApproveCreateSerializer(_EEWorkflowApproveCreateSerializer): pass + class WorkflowApproveApproveSerializer(_EEWorkflowApproveApproveSerializer): pass + class WorkflowApproveRejectSerializer(_EEWorkflowApproveRejectSerializer): pass + class WorkflowApproveCompatSerializer(_EEWorkflowApproveCompatSerializer): pass + class WorkflowApprovalRuleSerializer(_EEWorkflowApprovalRuleSerializer): pass + class WorkflowApprovalNodeSerializer(_EEWorkflowApprovalNodeSerializer): pass + class WorkflowApprovalInstanceSerializer(_EEWorkflowApprovalInstanceSerializer): pass + class WorkflowApprovalNodeExecutionSerializer(_EEWorkflowApprovalNodeExecutionSerializer): pass # ============================================================ @@ -2343,7 +2389,9 @@ class TaskCenterItemSerializer(_EETaskCenterItemSerializer): UnifiedScheduleStatsSerializer as _EEUnifiedScheduleStatsSerializer, SchedulerAlertEventSerializer as _EESchedulerAlertEventSerializer, ) + _EE_SCHEDULER_HA_OK = True except ImportError: + _EE_SCHEDULER_HA_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScheduleExecutionHASerializer(_FBSer): pass class _EESchedulerAlertEventSerializer(_FBSer): pass @@ -2353,62 +2401,70 @@ class _EEUnifiedScheduleListRequestSerializer(_FBSer): pass class _EEUnifiedScheduleStatsSerializer(_FBSer): pass -class ScheduleExecutionHASerializer(_EEScheduleExecutionHASerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass +if _EE_SCHEDULER_HA_OK: + class ScheduleExecutionHASerializer(_EEScheduleExecutionHASerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -class ScriptTaskExecutionUnifiedSerializer(_EEScriptTaskExecutionUnifiedSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass + class ScriptTaskExecutionUnifiedSerializer(_EEScriptTaskExecutionUnifiedSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -class SchedulerAlertRuleSerializer(_EESchedulerAlertRuleSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass + class SchedulerAlertRuleSerializer(_EESchedulerAlertRuleSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -class UnifiedScheduleListRequestSerializer(_EEUnifiedScheduleListRequestSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass + class UnifiedScheduleListRequestSerializer(_EEUnifiedScheduleListRequestSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -class UnifiedScheduleStatsSerializer(_EEUnifiedScheduleStatsSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass + class UnifiedScheduleStatsSerializer(_EEUnifiedScheduleStatsSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -class SchedulerAlertEventSerializer(_EESchedulerAlertEventSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass + class SchedulerAlertEventSerializer(_EESchedulerAlertEventSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -class ContactLeadSerializer(_EEContactLeadSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEContactLeadSerializer""" - pass +# ContactLead & 6 Placeholder 与 M2.5 log_ext_center 同组导入,共用 _EE_LOG_EXT_OK +if _EE_LOG_EXT_OK: + class TaskCenterItemSerializer(_EETaskCenterItemSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EETaskCenterItemSerializer""" + pass -# -------- 6 extension center placeholder Ser (M2.5 EE 空壳) -------- -class KnowledgeBasePlaceholderSerializer(_EEKnowledgeBasePlaceholderSerializer): - """Thin Wrapper — 知识库占位(M2.5 EE 空壳)""" - pass + class ContactLeadSerializer(_EEContactLeadSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEContactLeadSerializer""" + pass -class InspectionCenterPlaceholderSerializer(_EEInspectionCenterPlaceholderSerializer): - """Thin Wrapper — 巡检中心占位(M2.5 EE 空壳)""" - pass + # -------- 6 extension center placeholder Ser (M2.5 EE 空壳) -------- + class KnowledgeBasePlaceholderSerializer(_EEKnowledgeBasePlaceholderSerializer): + """Thin Wrapper — 知识库占位(M2.5 EE 空壳)""" + pass -class ToolsCenterPlaceholderSerializer(_EEToolsCenterPlaceholderSerializer): - """Thin Wrapper — 工具中心占位(M2.5 EE 空壳)""" - pass + class InspectionCenterPlaceholderSerializer(_EEInspectionCenterPlaceholderSerializer): + """Thin Wrapper — 巡检中心占位(M2.5 EE 空壳)""" + pass -class BackupRestoreCenterPlaceholderSerializer(_EEBackupRestoreCenterPlaceholderSerializer): - """Thin Wrapper — 备份恢复中心占位(M2.5 EE 空壳)""" - pass + class ToolsCenterPlaceholderSerializer(_EEToolsCenterPlaceholderSerializer): + """Thin Wrapper — 工具中心占位(M2.5 EE 空壳)""" + pass + + + class BackupRestoreCenterPlaceholderSerializer(_EEBackupRestoreCenterPlaceholderSerializer): + """Thin Wrapper — 备份恢复中心占位(M2.5 EE 空壳)""" + pass -class DownloadCenterPlaceholderSerializer(_EEDownloadCenterPlaceholderSerializer): - """Thin Wrapper — 客户端打包下载中心占位(M2.5 EE 空壳)""" - pass \ No newline at end of file + class DownloadCenterPlaceholderSerializer(_EEDownloadCenterPlaceholderSerializer): + """Thin Wrapper — 客户端打包下载中心占位(M2.5 EE 空壳)""" + pass \ No newline at end of file From 7afe6a1b155038648da5ab1f203b4bfe89d0b5e1 Mon Sep 17 00:00:00 2001 From: jinweijie Date: Fri, 4 Sep 2026 15:32:48 +0800 Subject: [PATCH 5/8] ``` refactor(serializers): remove conditional guards from EE serializer wrappers Remove _EE_*_OK conditional guards from enterprise edition serializer wrappers in taurus/serializers.py. All thin wrapper classes are now unconditionally defined since they are protected by dispatch-level EE gates that prevent instantiation when EE is not available. The changes ensure consistent behavior across all EE serializers and reduce complexity by removing the need to check import success flags. Classes that were previously conditionally defined are now always defined as thin wrappers extending their EE implementations or fallback serializers when EE imports fail. This affects multiple serializer groups including log center, operations approval, script audit/approval/check, share permissions, workflow approvals, and scheduler HA components. ``` --- taurus/serializers.py | 249 +++++++++++++++++++----------------------- 1 file changed, 113 insertions(+), 136 deletions(-) diff --git a/taurus/serializers.py b/taurus/serializers.py index 02ef748..e848fb8 100644 --- a/taurus/serializers.py +++ b/taurus/serializers.py @@ -1342,9 +1342,11 @@ class ProgramInstallPolicyUpdateSerializer(_EE_PIP_UpdateSer): pass # noqa: E70 # ------------------ [M2.5 Thin Wrapper] HostLog & LogCommand (log center EE) ------------------ -# 这些 Serializer 是 EE 专属功能(HostLogViewSet / LogCommandViewSet / TaskCenterViewSet -# 等均带 dispatch EE Gate),但仍然加 _EE_OK 守卫,避免未来有人绕过 gate 直接使用 -# 时踩到 _EEFallbackSerializer 的 Meta.model=None 问题。 +# 注意:这些 Serializer 均被 taurus/views.py 顶层 `from taurus.serializers import X` 导入, +# 因此 class 名称必须始终存在(否则 ImportError)。好在 HostLogViewSet / LogCommandViewSet / +# TaskCenterViewSet 等都带 dispatch 级 EE Gate(403 拦截),即使 EE 缺失时 Thin Wrapper +# 继承 Meta.model=None 的 _EEFallbackSerializer 也不会真正走到 Serializer 实例化。 +# 因此这里不使用 if-guard 做 class 重定义覆盖,直接无条件 Thin Wrapper 即可。 try: from taurus_ee.serializers.log_ext_center import ( _EEHostLogSerializer, @@ -1360,9 +1362,7 @@ class ProgramInstallPolicyUpdateSerializer(_EE_PIP_UpdateSer): pass # noqa: E70 _EEBackupRestoreCenterPlaceholderSerializer, _EEDownloadCenterPlaceholderSerializer, ) - _EE_LOG_EXT_OK = True except ImportError: - _EE_LOG_EXT_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEBackupRestoreCenterPlaceholderSerializer(_FBSer): pass class _EEContactLeadSerializer(_FBSer): pass @@ -1378,15 +1378,14 @@ class _EETaskCenterItemSerializer(_FBSer): pass class _EEToolsCenterPlaceholderSerializer(_FBSer): pass -if _EE_LOG_EXT_OK: - class HostLogSerializer(_EEHostLogSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogSerializer""" - pass +class HostLogSerializer(_EEHostLogSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogSerializer""" + pass - class HostLogReceiveSerializer(_EEHostLogReceiveSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogReceiveSerializer""" - pass +class HostLogReceiveSerializer(_EEHostLogReceiveSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEHostLogReceiveSerializer""" + pass class ManagedProgramSerializer(CustomModelSerializer): @@ -1404,20 +1403,20 @@ class Meta: -if _EE_LOG_EXT_OK: - class LogCommandSerializer(_EELogCommandSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandSerializer""" - pass + +class LogCommandSerializer(_EELogCommandSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandSerializer""" + pass - class LogCommandCreateSerializer(_EELogCommandCreateSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandCreateSerializer""" - pass +class LogCommandCreateSerializer(_EELogCommandCreateSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandCreateSerializer""" + pass - class LogCommandUpdateSerializer(_EELogCommandUpdateSerializer): - """Thin Wrapper (NEW M2.5) — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandUpdateSerializer""" - pass +class LogCommandUpdateSerializer(_EELogCommandUpdateSerializer): + """Thin Wrapper (NEW M2.5) — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandUpdateSerializer""" + pass @@ -1677,6 +1676,9 @@ def get_duration(self, obj): # ========================== Execution Task Approval Serializers (Thin Wrapper, 真实实现迁 taurus_ee.serializers.ops_approval) ========================== +# 注意:OpsExecutionApproval 四个 Serializer 均被 taurus/views.py 顶层 `from taurus.serializers import X` +# 导入,因此 class 名称必须始终存在(否则 ImportError)。相关 ViewSet 均带 dispatch 级 EE Gate +# (403 拦截),EE 缺失时 Thin Wrapper 继承 Meta.model=None 的 fallback 也不会走到实例化。 try: from taurus_ee.serializers.ops_approval import ( _EEOpsExecutionApprovalSerializer, @@ -1684,9 +1686,7 @@ def get_duration(self, obj): _EEOpsExecutionApprovalCreateSerializer, _EEOpsExecutionApprovalActionSerializer, ) - _EE_OPS_APPROVAL_OK = True except ImportError: - _EE_OPS_APPROVAL_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEOpsExecutionApprovalActionSerializer(_FBSer): pass class _EEOpsExecutionApprovalCreateSerializer(_FBSer): pass @@ -1694,11 +1694,10 @@ class _EEOpsExecutionApprovalListSerializer(_FBSer): pass class _EEOpsExecutionApprovalSerializer(_FBSer): pass -if _EE_OPS_APPROVAL_OK: - class OpsExecutionApprovalSerializer(_EEOpsExecutionApprovalSerializer): pass - class OpsExecutionApprovalListSerializer(_EEOpsExecutionApprovalListSerializer): pass - class OpsExecutionApprovalCreateSerializer(_EEOpsExecutionApprovalCreateSerializer): pass - class OpsExecutionApprovalActionSerializer(_EEOpsExecutionApprovalActionSerializer): pass +class OpsExecutionApprovalSerializer(_EEOpsExecutionApprovalSerializer): pass +class OpsExecutionApprovalListSerializer(_EEOpsExecutionApprovalListSerializer): pass +class OpsExecutionApprovalCreateSerializer(_EEOpsExecutionApprovalCreateSerializer): pass +class OpsExecutionApprovalActionSerializer(_EEOpsExecutionApprovalActionSerializer): pass # ========================== ScriptlibrarySerializationserver ========================== @@ -2161,15 +2160,17 @@ def get_host_summary(self, obj) -> dict: # 保留原名:taurus/urls.py + views.py 中 `from taurus.serializers import X` # 所有旧引用保持 0 改动。 # -# 防御性修复:所有 try/except 均引入 _*_OK 标志位,仅在 EE 导入成功时才重新定义类。 -# 否则不产生 class 重新定义(这些 Serializer 本就只能在 EE-gated 路径下实例化; -# 若强行实例化会报 NameError,但这比 Meta.model=None 静默抛 AttributeError 好诊断)。 +# 策略:这些 Serializer 绝大多数为 EE 专属、由 dispatch 级 EE Gate 保护(403 拦截); +# 少数(SharePermissionDefSerializer 等 4 个 + TaskCenterItemSerializer)被 views.py +# 顶层 `from taurus.serializers import X` 导入,因此 class 名称必须始终存在(否则 ImportError)。 +# 为保持一致性与最小风险,本块所有 Thin Wrapper 一律无条件定义(移除 _EE_*_OK 守卫), +# EE 缺失时继承 Meta.model=None 的 fallback 类不会真正走到 Serializer 实例化。 +# 只有 M2.4(ProgramCommand 等已有 CE 基础类)与 WorkflowDAGVersion 保留守卫, +# 目的是"不要覆盖掉原本就存在的正确 CE 类定义(含真实 Meta.model)"。 # ================================================================ try: from taurus_ee.serializers.script_audit import ScriptAuditSerializer as _EEScriptAuditSerializer - _EE_SCRIPT_AUDIT_OK = True except ImportError: - _EE_SCRIPT_AUDIT_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptAuditSerializer(_FBSer): pass @@ -2181,9 +2182,7 @@ class _EEScriptAuditSerializer(_FBSer): pass ScriptApprovalInstanceSerializer as _EEScriptApprovalInstanceSerializer, ScriptApprovalNodeExecutionSerializer as _EEScriptApprovalNodeExecutionSerializer, ) - _EE_SCRIPT_APPROVAL_OK = True except ImportError: - _EE_SCRIPT_APPROVAL_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptApprovalInstanceSerializer(_FBSer): pass class _EEScriptApprovalNodeExecutionSerializer(_FBSer): pass @@ -2193,9 +2192,7 @@ class _EEScriptApproveSerializer(_FBSer): pass try: from taurus_ee.serializers.script_check import ScriptCheckRuleSerializer as _EEScriptCheckRuleSerializer - _EE_SCRIPT_CHECK_OK = True except ImportError: - _EE_SCRIPT_CHECK_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptCheckRuleSerializer(_FBSer): pass @@ -2209,9 +2206,7 @@ class _EEScriptCheckRuleSerializer(_FBSer): pass ShareLinkActivateSerializer as _EEShareLinkActivateSerializer, ShareLinkAccessLogSerializer as _EEShareLinkAccessLogSerializer, ) - _EE_SHARE_PERM_OK = True except ImportError: - _EE_SHARE_PERM_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScriptSharePermissionSerializer(_FBSer): pass class _EEShareLinkAccessLogSerializer(_FBSer): pass @@ -2222,67 +2217,63 @@ class _EESharePermissionDefSerializer(_FBSer): pass class _EEWorkflowSharePermissionSerializer(_FBSer): pass -if _EE_SCRIPT_AUDIT_OK: - class ScriptAuditSerializer(_EEScriptAuditSerializer): - """Thin Wrapper → taurus_ee.serializers.script_audit.ScriptAuditSerializer""" - pass +class ScriptAuditSerializer(_EEScriptAuditSerializer): + """Thin Wrapper → taurus_ee.serializers.script_audit.ScriptAuditSerializer""" + pass -if _EE_SCRIPT_APPROVAL_OK: - class ScriptApproveSerializer(_EEScriptApproveSerializer): - """Thin Wrapper.""" - pass +class ScriptApproveSerializer(_EEScriptApproveSerializer): + """Thin Wrapper.""" + pass - class ScriptApprovalRuleSerializer(_EEScriptApprovalRuleSerializer): - pass +class ScriptApprovalRuleSerializer(_EEScriptApprovalRuleSerializer): + pass - class ScriptApprovalNodeSerializer(_EEScriptApprovalNodeSerializer): - pass +class ScriptApprovalNodeSerializer(_EEScriptApprovalNodeSerializer): + pass - class ScriptApprovalInstanceSerializer(_EEScriptApprovalInstanceSerializer): - pass +class ScriptApprovalInstanceSerializer(_EEScriptApprovalInstanceSerializer): + pass - class ScriptApprovalNodeExecutionSerializer(_EEScriptApprovalNodeExecutionSerializer): - pass +class ScriptApprovalNodeExecutionSerializer(_EEScriptApprovalNodeExecutionSerializer): + pass -if _EE_SCRIPT_CHECK_OK: - class ScriptCheckRuleSerializer(_EEScriptCheckRuleSerializer): - pass +class ScriptCheckRuleSerializer(_EEScriptCheckRuleSerializer): + pass # ---------------- share permission Thin Wrappers ---------------- -if _EE_SHARE_PERM_OK: - class SharePermissionDefSerializer(_EESharePermissionDefSerializer): - pass +class SharePermissionDefSerializer(_EESharePermissionDefSerializer): + pass - class ScriptSharePermissionSerializer(_EEScriptSharePermissionSerializer): - pass +class ScriptSharePermissionSerializer(_EEScriptSharePermissionSerializer): + pass - class WorkflowSharePermissionSerializer(_EEWorkflowSharePermissionSerializer): - pass +class WorkflowSharePermissionSerializer(_EEWorkflowSharePermissionSerializer): + pass - class SharePermissionBatchCreateSerializer(_EESharePermissionBatchCreateSerializer): - pass +class SharePermissionBatchCreateSerializer(_EESharePermissionBatchCreateSerializer): + pass - class ShareLinkSerializer(_EEShareLinkSerializer): - pass +class ShareLinkSerializer(_EEShareLinkSerializer): + pass - class ShareLinkActivateSerializer(_EEShareLinkActivateSerializer): - pass +class ShareLinkActivateSerializer(_EEShareLinkActivateSerializer): + pass - class ShareLinkAccessLogSerializer(_EEShareLinkAccessLogSerializer): - pass +class ShareLinkAccessLogSerializer(_EEShareLinkAccessLogSerializer): + pass # ================================================================ @@ -2290,9 +2281,8 @@ class ShareLinkAccessLogSerializer(_EEShareLinkAccessLogSerializer): # Thin Wrapper 提前覆盖,此处不再保留,避免与继承 EE 的版本双定义。 # 原 class ScriptApproveSerializer -> model = ScriptApprove 由 EE Meta 继承保证。 # ================================================================ -if _EE_SCRIPT_APPROVAL_OK: - class ScriptApproveSerializer(_EEScriptApproveSerializer): # noqa: F811 - """(Double re-declare 仅兼容,与上方 Thin Wrapper 等价。)""" +class ScriptApproveSerializer(_EEScriptApproveSerializer): # noqa: F811 + """(Double re-declare 仅兼容,与上方 Thin Wrapper 等价。)""" # 保留 Meta 注释以展示老代码模型 # model = ScriptApprove (已在 EE 版 Meta 中定义) pass @@ -2335,9 +2325,7 @@ class _LegacyMetaAnchor_ScriptApprove: # Never used WorkflowApprovalInstanceSerializer as _EEWorkflowApprovalInstanceSerializer, WorkflowApprovalNodeExecutionSerializer as _EEWorkflowApprovalNodeExecutionSerializer, ) - _EE_WF_APPROVAL_OK = True except ImportError: - _EE_WF_APPROVAL_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEWorkflowApprovalInstanceSerializer(_FBSer): pass class _EEWorkflowApprovalNodeExecutionSerializer(_FBSer): pass @@ -2350,16 +2338,15 @@ class _EEWorkflowApproveRejectSerializer(_FBSer): pass class _EEWorkflowApproveSerializer(_FBSer): pass -if _EE_WF_APPROVAL_OK: - class WorkflowApproveSerializer(_EEWorkflowApproveSerializer): pass - class WorkflowApproveCreateSerializer(_EEWorkflowApproveCreateSerializer): pass - class WorkflowApproveApproveSerializer(_EEWorkflowApproveApproveSerializer): pass - class WorkflowApproveRejectSerializer(_EEWorkflowApproveRejectSerializer): pass - class WorkflowApproveCompatSerializer(_EEWorkflowApproveCompatSerializer): pass - class WorkflowApprovalRuleSerializer(_EEWorkflowApprovalRuleSerializer): pass - class WorkflowApprovalNodeSerializer(_EEWorkflowApprovalNodeSerializer): pass - class WorkflowApprovalInstanceSerializer(_EEWorkflowApprovalInstanceSerializer): pass - class WorkflowApprovalNodeExecutionSerializer(_EEWorkflowApprovalNodeExecutionSerializer): pass +class WorkflowApproveSerializer(_EEWorkflowApproveSerializer): pass +class WorkflowApproveCreateSerializer(_EEWorkflowApproveCreateSerializer): pass +class WorkflowApproveApproveSerializer(_EEWorkflowApproveApproveSerializer): pass +class WorkflowApproveRejectSerializer(_EEWorkflowApproveRejectSerializer): pass +class WorkflowApproveCompatSerializer(_EEWorkflowApproveCompatSerializer): pass +class WorkflowApprovalRuleSerializer(_EEWorkflowApprovalRuleSerializer): pass +class WorkflowApprovalNodeSerializer(_EEWorkflowApprovalNodeSerializer): pass +class WorkflowApprovalInstanceSerializer(_EEWorkflowApprovalInstanceSerializer): pass +class WorkflowApprovalNodeExecutionSerializer(_EEWorkflowApprovalNodeExecutionSerializer): pass # ============================================================ @@ -2389,9 +2376,7 @@ class TaskCenterItemSerializer(_EETaskCenterItemSerializer): UnifiedScheduleStatsSerializer as _EEUnifiedScheduleStatsSerializer, SchedulerAlertEventSerializer as _EESchedulerAlertEventSerializer, ) - _EE_SCHEDULER_HA_OK = True except ImportError: - _EE_SCHEDULER_HA_OK = False from taurus.ee_fallback import _EEFallbackSerializer as _FBSer class _EEScheduleExecutionHASerializer(_FBSer): pass class _EESchedulerAlertEventSerializer(_FBSer): pass @@ -2401,70 +2386,62 @@ class _EEUnifiedScheduleListRequestSerializer(_FBSer): pass class _EEUnifiedScheduleStatsSerializer(_FBSer): pass -if _EE_SCHEDULER_HA_OK: - class ScheduleExecutionHASerializer(_EEScheduleExecutionHASerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass - - - class ScriptTaskExecutionUnifiedSerializer(_EEScriptTaskExecutionUnifiedSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass +class ScheduleExecutionHASerializer(_EEScheduleExecutionHASerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass - class SchedulerAlertRuleSerializer(_EESchedulerAlertRuleSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass +class ScriptTaskExecutionUnifiedSerializer(_EEScriptTaskExecutionUnifiedSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass - class UnifiedScheduleListRequestSerializer(_EEUnifiedScheduleListRequestSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass +class SchedulerAlertRuleSerializer(_EESchedulerAlertRuleSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass - class UnifiedScheduleStatsSerializer(_EEUnifiedScheduleStatsSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass +class UnifiedScheduleListRequestSerializer(_EEUnifiedScheduleListRequestSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass - class SchedulerAlertEventSerializer(_EESchedulerAlertEventSerializer): - """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" - pass +class UnifiedScheduleStatsSerializer(_EEUnifiedScheduleStatsSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass -# ContactLead & 6 Placeholder 与 M2.5 log_ext_center 同组导入,共用 _EE_LOG_EXT_OK -if _EE_LOG_EXT_OK: - class TaskCenterItemSerializer(_EETaskCenterItemSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EETaskCenterItemSerializer""" - pass +class SchedulerAlertEventSerializer(_EESchedulerAlertEventSerializer): + """Thin Wrapper — EE 实现: taurus_ee.serializers.scheduler_ha""" + pass - class ContactLeadSerializer(_EEContactLeadSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEContactLeadSerializer""" - pass +class ContactLeadSerializer(_EEContactLeadSerializer): + """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEContactLeadSerializer""" + pass - # -------- 6 extension center placeholder Ser (M2.5 EE 空壳) -------- - class KnowledgeBasePlaceholderSerializer(_EEKnowledgeBasePlaceholderSerializer): - """Thin Wrapper — 知识库占位(M2.5 EE 空壳)""" - pass +# -------- 6 extension center placeholder Ser (M2.5 EE 空壳) -------- +class KnowledgeBasePlaceholderSerializer(_EEKnowledgeBasePlaceholderSerializer): + """Thin Wrapper — 知识库占位(M2.5 EE 空壳)""" + pass - class InspectionCenterPlaceholderSerializer(_EEInspectionCenterPlaceholderSerializer): - """Thin Wrapper — 巡检中心占位(M2.5 EE 空壳)""" - pass +class InspectionCenterPlaceholderSerializer(_EEInspectionCenterPlaceholderSerializer): + """Thin Wrapper — 巡检中心占位(M2.5 EE 空壳)""" + pass - class ToolsCenterPlaceholderSerializer(_EEToolsCenterPlaceholderSerializer): - """Thin Wrapper — 工具中心占位(M2.5 EE 空壳)""" - pass +class ToolsCenterPlaceholderSerializer(_EEToolsCenterPlaceholderSerializer): + """Thin Wrapper — 工具中心占位(M2.5 EE 空壳)""" + pass - class BackupRestoreCenterPlaceholderSerializer(_EEBackupRestoreCenterPlaceholderSerializer): - """Thin Wrapper — 备份恢复中心占位(M2.5 EE 空壳)""" - pass +class BackupRestoreCenterPlaceholderSerializer(_EEBackupRestoreCenterPlaceholderSerializer): + """Thin Wrapper — 备份恢复中心占位(M2.5 EE 空壳)""" + pass - class DownloadCenterPlaceholderSerializer(_EEDownloadCenterPlaceholderSerializer): - """Thin Wrapper — 客户端打包下载中心占位(M2.5 EE 空壳)""" - pass \ No newline at end of file +class DownloadCenterPlaceholderSerializer(_EEDownloadCenterPlaceholderSerializer): + """Thin Wrapper — 客户端打包下载中心占位(M2.5 EE 空壳)""" + pass \ No newline at end of file From c6fe02c36aa2ec21d3c2c2b229185bfa66de380c Mon Sep 17 00:00:00 2001 From: jinweijie Date: Fri, 4 Sep 2026 16:21:38 +0800 Subject: [PATCH 6/8] ``` fix(base-edition): disable upgrade banner for all editions Set show_banner to false in upgrade configuration to prevent banner display across all editions. ``` --- taurus/editions/base.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/taurus/editions/base.py b/taurus/editions/base.py index 127c03a..cf22844 100644 --- a/taurus/editions/base.py +++ b/taurus/editions/base.py @@ -91,7 +91,7 @@ def info_payload(self) -> Dict[str, Any]: "quota": self.quota, "license": self.license_status, "upgrade": { - "show_banner": self.edition_name == "community", + "show_banner": False, "contact_url": "/#/taurus/contact-lead", # 前端路由占位 }, - } + } \ No newline at end of file From e10959bb33896be4209e77320bb02ceb2607189a Mon Sep 17 00:00:00 2001 From: jinweijie0528 Date: Sat, 5 Sep 2026 12:21:06 +0800 Subject: [PATCH 7/8] ``` refactor(taurus): update EE fallback serializer to avoid drf-spectacular crashes - Replace _EEFallbackSerializer inheritance from CustomModelSerializer to plain Serializer to prevent model_meta.get_field_info(None) crashes during schema scanning - Add proper docstring explaining the purpose of the fallback serializer - Remove unnecessary Meta class from _EEFallbackSerializer - Clean up unused imports in ee_fallback.py feat(taurus): implement conditional EE feature routing for workflow and script approvals - Add conditional registration of workflow approval endpoints based on _WFAPP_EE_OK flag - Add conditional registration of script approval endpoints based on _SCRIPT_EE_OK flag - Move approval-related URL patterns into conditional blocks to gate EE-only features - Update ContactLeadSerializer to extend CustomModelSerializer directly instead of _EEFallbackSerializer to ensure CE compatibility for anonymous portal submissions ``` --- taurus/ee_fallback.py | 15 +++++----- taurus/serializers.py | 21 ++++++++++++-- taurus/urls.py | 65 ++++++++++++++++++++++++++----------------- 3 files changed, 64 insertions(+), 37 deletions(-) diff --git a/taurus/ee_fallback.py b/taurus/ee_fallback.py index f04be07..1846e52 100644 --- a/taurus/ee_fallback.py +++ b/taurus/ee_fallback.py @@ -25,9 +25,9 @@ class SomeViewSet(_EESomeViewSet): from typing import Any, Callable, Optional from dvadmin.utils.viewset import CustomModelViewSet -from dvadmin.utils.serializers import CustomModelSerializer from rest_framework.exceptions import PermissionDenied +from rest_framework.serializers import Serializer from taurus.editions import describe_feature @@ -51,17 +51,16 @@ def dispatch(self, request, *args, **kwargs): # --------------------------------------------------------------------------- # Serializer fallback # --------------------------------------------------------------------------- -class _EEFallbackSerializer(CustomModelSerializer): +class _EEFallbackSerializer(Serializer): """EE Serializer 缺失时的 fallback 父类。 - 在 Thin Wrapper 继承链中占位——仅用于让 Python class 定义不崩溃。 - 实际运行时 ViewSet dispatch 会先被 EditionGate / _EEFallbackViewSet + 继承普通 Serializer(非 ModelSerializer),避免 drf-spectacular schema + 扫描时触发 model_meta.get_field_info(None) 崩溃。 + 运行时 ViewSet dispatch 会先被 EditionGate / _EEFallbackViewSet 拦截到 403,永远不会走到 Serializer 实例化。 """ - class Meta: - model = None - fields = [] + pass # --------------------------------------------------------------------------- @@ -94,4 +93,4 @@ def ee_service_or_none(service_name: str) -> Optional[Any]: "_EEFallbackSerializer", "ee_service_or_403", "ee_service_or_none", -] +] \ No newline at end of file diff --git a/taurus/serializers.py b/taurus/serializers.py index e848fb8..c664830 100644 --- a/taurus/serializers.py +++ b/taurus/serializers.py @@ -2416,9 +2416,24 @@ class SchedulerAlertEventSerializer(_EESchedulerAlertEventSerializer): pass -class ContactLeadSerializer(_EEContactLeadSerializer): - """Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EEContactLeadSerializer""" - pass +class ContactLeadSerializer(CustomModelSerializer): + """Portal contact form submission serializer. + + CE-compatible: this serializer is used by ContactLeadViewSet.create which + is NOT gated (AllowAny, anonymous portal POST). Must NOT inherit from + _EEFallbackSerializer (Meta.model=None) — drf-spectacular schema scan and + real CE traffic both need an instantiable ModelSerializer. + + EE override: taurus_ee may shadow _EEContactLeadSerializer with extended + fields, but ContactLeadSerializer stays CE-safe as the canonical path. + """ + + class Meta: + from taurus.models import ContactLead + + model = ContactLead + fields = '__all__' + read_only_fields = ('source', 'ip', 'user_agent', 'create_datetime', 'update_datetime') # -------- 6 extension center placeholder Ser (M2.5 EE 空壳) -------- diff --git a/taurus/urls.py b/taurus/urls.py index 06a9cb4..fc26beb 100644 --- a/taurus/urls.py +++ b/taurus/urls.py @@ -20,6 +20,7 @@ ContactLeadViewSet, LightweightUserOptionsView, workflow_callback_view, + _WFAPP_EE_OK, _SCRIPT_EE_OK, ) from taurus.views_edition import EditionInfoView, EditionFeaturesView, EditionDescribeView @@ -29,11 +30,11 @@ router.register('workflow', WorkflowViewSet, basename='workflow') router.register('workflow-category', WorkflowCategoryViewSet, basename='workflow-category') router.register('workflow-execution', WorkflowExecutionViewSet, basename='workflow-execution') -router.register('workflow-approve', WorkflowApproveViewSet, basename='workflow-approve') -# Workflow approval rules (rule-driven approval) -router.register('workflow-approval-rule', WorkflowApprovalRuleViewSet, basename='workflow-approval-rule') -router.register('workflow-approval-node', WorkflowApprovalRuleNodeViewSet, basename='workflow-approval-node') -router.register('workflow-approval-instance', WorkflowApprovalInstanceViewSet, basename='workflow-approval-instance') +if _WFAPP_EE_OK: + router.register('workflow-approve', WorkflowApproveViewSet, basename='workflow-approve') + router.register('workflow-approval-rule', WorkflowApprovalRuleViewSet, basename='workflow-approval-rule') + router.register('workflow-approval-node', WorkflowApprovalRuleNodeViewSet, basename='workflow-approval-node') + router.register('workflow-approval-instance', WorkflowApprovalInstanceViewSet, basename='workflow-approval-instance') # Scheduled task management router.register('schedule', ScheduleViewSet, basename='schedule') @@ -106,20 +107,18 @@ # Script audit log management router.register('script-audit', ScriptAuditViewSet, basename='script-audit') -# Script approval management -router.register('script-approve', ScriptApproveViewSet, basename='script-approve') - -# Script approval rules (rule-driven approval) -router.register('script-approval-rule', ScriptApprovalRuleViewSet, basename='script-approval-rule') -router.register('script-approval-node', ScriptApprovalRuleNodeViewSet, basename='script-approval-node') -router.register('script-approval-instance', ScriptApprovalInstanceViewSet, basename='script-approval-instance') - -# Script check rules -router.register('script-check-rule', ScriptCheckRuleViewSet, basename='script-check-rule') - -# Sharing: permission dictionary & share link -router.register('share-perm-def', SharePermissionDefViewSet, basename='share-perm-def') -router.register('share-link', ShareLinkViewSet, basename='share-link') +if _SCRIPT_EE_OK: + # Script approval management + router.register('script-approve', ScriptApproveViewSet, basename='script-approve') + # Script approval rules (rule-driven approval) + router.register('script-approval-rule', ScriptApprovalRuleViewSet, basename='script-approval-rule') + router.register('script-approval-node', ScriptApprovalRuleNodeViewSet, basename='script-approval-node') + router.register('script-approval-instance', ScriptApprovalInstanceViewSet, basename='script-approval-instance') + # Script check rules + router.register('script-check-rule', ScriptCheckRuleViewSet, basename='script-check-rule') + # Sharing: permission dictionary & share link + router.register('share-perm-def', SharePermissionDefViewSet, basename='share-perm-def') + router.register('share-link', ShareLinkViewSet, basename='share-link') # Task center (aggregate script scheduled tasks + scheduled workflows) router.register('task-center', TaskCenterViewSet, basename='task-center') @@ -163,22 +162,36 @@ path('script//effective-perms/', ScriptViewSet.as_view({'get': 'effective_perms'})), # Script scheduled task custom actions path('script-task//toggle/', ScriptTaskViewSet.as_view({'post': 'toggle_enabled'})), - # Script approval custom actions - path('script-approve//approve/', ScriptApproveViewSet.as_view({'post': 'approve'})), - path('script-approve//reject/', ScriptApproveViewSet.as_view({'post': 'reject'})), +] + +if _SCRIPT_EE_OK: + urlpatterns += [ + # Script approval custom actions + path('script-approve//approve/', ScriptApproveViewSet.as_view({'post': 'approve'})), + path('script-approve//reject/', ScriptApproveViewSet.as_view({'post': 'reject'})), + ] + +urlpatterns += [ # Workflow orchestration custom actions path('workflow//toggle-status/', WorkflowViewSet.as_view({'post': 'toggle_status'})), path('workflow//copy/', WorkflowViewSet.as_view({'post': 'copy_workflow'})), path('workflow//submit-approve/', WorkflowViewSet.as_view({'post': 'submit_approve'})), path('workflow//risk-assessment/', WorkflowViewSet.as_view({'get': 'risk_assessment', 'post': 'risk_assessment'})), path('workflow/stats/', WorkflowViewSet.as_view({'get': 'get_stats'})), - # Workflow share permission related + # Workflow share permission related (community 版可用) path('workflow//shares/', WorkflowViewSet.as_view({'get': 'shares', 'post': 'shares'})), path('workflow//shares//', WorkflowViewSet.as_view({'put': 'share_detail', 'delete': 'share_detail'})), path('workflow//effective-perms/', WorkflowViewSet.as_view({'get': 'effective_perms'})), - # Workflow orchestration approval custom actions - path('workflow-approve//approve/', WorkflowApproveViewSet.as_view({'post': 'approve'})), - path('workflow-approve//reject/', WorkflowApproveViewSet.as_view({'post': 'reject'})), +] + +if _WFAPP_EE_OK: + urlpatterns += [ + # Workflow orchestration approval custom actions + path('workflow-approve//approve/', WorkflowApproveViewSet.as_view({'post': 'approve'})), + path('workflow-approve//reject/', WorkflowApproveViewSet.as_view({'post': 'reject'})), + ] + +urlpatterns += [ # -------- Edition Gate (M1):edition 能力查询 -------- path('edition/info/', EditionInfoView.as_view()), path('edition/features/', EditionFeaturesView.as_view()), From 8b5a311ae9fec304fe9b3e1a478793ef1e44c5e7 Mon Sep 17 00:00:00 2001 From: jinweijie0528 Date: Sat, 5 Sep 2026 14:01:47 +0800 Subject: [PATCH 8/8] ``` feat(config): add config decryption helpers and enhance security configuration - Add automatic config decryption functionality with `_decrypt_if_needed` helper - Implement encrypted password support for database connections - Add workflow engine configuration with executable unit adapters list - Configure email notification settings with environment-based backends - Enable request signing verification for API security - Add SSL/TLS configuration options for supervisor connections - Include gRPC server certificate SAN configuration - Support encrypted format for sensitive configuration values ``` --- conf/env.example.py | 129 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 121 insertions(+), 8 deletions(-) diff --git a/conf/env.example.py b/conf/env.example.py index 2d7607d..3f55d11 100644 --- a/conf/env.example.py +++ b/conf/env.example.py @@ -2,6 +2,27 @@ from application.settings import BASE_DIR +# ================================================= # +# *************** Config Decryption Helpers *************** # +# ================================================= # +def _decrypt_if_needed(value): + """Automatically decrypt config values (if encrypted)""" + if not value or not isinstance(value, str): + return value + + # Check if value is in encrypted format + if not value.startswith('fernet:'): + return value + + try: + from taurus.config_crypto import auto_decrypt + return auto_decrypt(value) + except Exception as e: + # Log warning on decryption failure, return original value + import logging + logging.getLogger(__name__).warning(f"Config decryption failed: {e}") + return value + # ================================================= # # *************** MySQL Database Config *************** # # ================================================= # @@ -20,19 +41,22 @@ DATABASE_PORT = 3306 # # Database username DATABASE_USER = "root" -# # Database password -DATABASE_PASSWORD = 'AOADMIN3' +# # Database password (supports encrypted format, auto-decrypted on startup) +DATABASE_PASSWORD = _decrypt_if_needed(os.environ.get( + 'DATABASE_PASSWORD', + 'change_me' # Dev env plaintext, use encrypted format in production +)) # Table prefix TABLE_PREFIX = "taurus_" # ================================================= # # ******** Redis Config, can skip if no Redis ******** # # ================================================= # -REDIS_DB = 1 -CELERY_BROKER_DB = 3 -REDIS_PASSWORD = 'AOADMIN3' -REDIS_HOST = '127.0.0.1' -REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' +# REDIS_DB = 1 +# CELERY_BROKER_DB = 3 +# REDIS_PASSWORD = _decrypt_if_needed(os.environ.get('REDIS_PASSWORD', 'change_me')) +# REDIS_HOST = '127.0.0.1' +# REDIS_URL = f'redis://:{REDIS_PASSWORD or ""}@{REDIS_HOST}:6379' # ================================================= # # ****************** Feature Toggles ******************* # # ================================================= # @@ -41,10 +65,99 @@ ENABLE_LOGIN_ANALYSIS_LOG = True # Login interface /api/token/ does not require captcha auth, for testing; production environment recommends disabling LOGIN_NO_CAPTCHA_AUTH = True + +# ================================================= # +# ***************** Workflow Engine Config *************** # +# ================================================= # +# List of ExecutableUnit adapters auto-imported on startup (just specify module path, triggers their @register_unit_adapter decorator) +# Adding a new adapter only requires appending one line to this list; no engine code changes needed +INSTALLED_UNIT_ADAPTERS: list[str] = [ + # Core built-in adapters (bundled with taurus code, implemented progressively during S3 phase) + "taurus.workflow.units.ops_execution", # S2-01/S2-02/S2-03: script + command + file_op adapters (reuse OpsExecution execution pipeline) + "taurus.workflow.units.virtual", # S2-07: start + end + noop virtual nodes + "taurus.workflow.units.program", # S2-04: Program management (install/upgrade/start/stop/restart/remove) + "taurus.workflow.units.approval", # S2-05: Manual approval (async node, is_asynchronous_human=True) + "taurus.workflow.units.sub_workflow", # S2-06: Sub-workflow (recursively triggers WorkflowExecution) + "taurus.workflow.units.http_callback", # S4-01: HTTP callback (externally driven async) + "taurus.workflow.units.condition", # S4-02: Condition branching (flow control) + "taurus.workflow.units.transform", # S4-03: Data transformation (JSONPath/Python/regex) + "taurus.workflow.units.http", # S4-04: Synchronous HTTP request + "taurus.workflow.units.loop", # S4-05: Loop control node + "taurus.workflow.units.webhook_notification", # S4-06: Webhook notification + "taurus.workflow.units.email_notification", # S4-07: Email notification + "taurus.workflow.units.wait", # S4-08: Wait (delay / absolute time / daily schedule) + # "taurus.workflow.units.schedule_gate", # Schedule/window gate (e.g. only allow 00:00~05:00) +] + +# testing_* placeholder adapters load only in DEBUG dev environment, to avoid test nodes appearing on the production node panel +if DEBUG: + INSTALLED_UNIT_ADAPTERS = [ + "taurus.workflow.units._testing_noop", # Unit test only: always success / output examples + "taurus.workflow.units._testing_fail", # Unit test only: always fail / error code examples + ] + INSTALLED_UNIT_ADAPTERS + +# ================================================= # +# ****************** Email Notification Config ****************** # +# ================================================= # +# Dev env defaults to console backend (email content printed to backend logs, not actually sent), to avoid local SMTP restrictions +# Production env switches to real SMTP via environment variables: +# EMAIL_BACKEND=django.core.mail.backends.smtp.EmailBackend +# EMAIL_HOST=smtp.example.com EMAIL_PORT=465 EMAIL_USE_SSL=true +# EMAIL_HOST_USER=xxx EMAIL_HOST_PASSWORD=xxx DEFAULT_FROM_EMAIL=xxx +EMAIL_BACKEND = os.environ.get( + 'EMAIL_BACKEND', + 'django.core.mail.backends.console.EmailBackend' +) +EMAIL_HOST = os.environ.get('EMAIL_HOST', '127.0.0.1') +EMAIL_HOST_USER = os.environ.get('EMAIL_HOST_USER', '') +EMAIL_HOST_PASSWORD = _decrypt_if_needed(os.environ.get('EMAIL_HOST_PASSWORD', '')) +EMAIL_PORT = int(os.environ.get('EMAIL_PORT', '25')) +EMAIL_USE_TLS = os.environ.get('EMAIL_USE_TLS', 'false').lower() == 'true' +EMAIL_USE_SSL = os.environ.get('EMAIL_USE_SSL', 'false').lower() == 'true' +DEFAULT_FROM_EMAIL = os.environ.get('DEFAULT_FROM_EMAIL', 'no-reply@example.com') + # ================================================= # # ****************** Other Config ******************* # # ================================================= # ALLOWED_HOSTS = ["*"] # Exclude apps from column permissions -COLUMN_EXCLUDE_APPS = [] \ No newline at end of file +COLUMN_EXCLUDE_APPS = [] + +# ================================================= # +# ****************** Request Signing Config ****************** # +# ================================================= # +# Whether to enable request signature verification (Supervisor heartbeat and command reporting endpoints) +# When enabled, newly registered hosts auto-generate a signing key +# Enabled by default for API security +REQUEST_SIGNING_ENABLED = os.environ.get('REQUEST_SIGNING_ENABLED', 'true').lower() == 'true' + +# Signing master key (supports encrypted format, auto-decrypted on startup) +# Production must set the encrypted key via environment variable +SIGNING_MASTER_KEY = _decrypt_if_needed(os.environ.get( + 'SIGNING_MASTER_KEY', + 'change-me-to-a-secure-master-key' +)) + +# ================================================= # +# ****************** SSL/TLS Config ****************** # +# ================================================= # +# Whether to enable SSL verification (validates server certificate on Supervisor heartbeat requests) +# Disabled by default, dev env uses HTTP +# Production must set to true and configure certificates +SUPERVISOR_SSL_VERIFY = os.environ.get('SUPERVISOR_SSL_VERIFY', 'false').lower() == 'true' + +# Custom CA certificate path (optional, needed for self-signed certificates) +SUPERVISOR_SSL_CA_PATH = os.environ.get('SUPERVISOR_SSL_CA_PATH', '') + +# ================================================= # +# ****************** Server Cert SAN Config ************ # +# ================================================= # +# Unified gRPC service name (server cert SAN + client ssl_target_name_override) +# Uses fixed DNS name + client target name override to bypass IP hostname verification (Option 2: bare IP direct connect) +GRPC_SERVER_CERT_SAN = os.environ.get('GRPC_SERVER_CERT_SAN', 'taurus-grpc-server') + +# Server certificate SAN list config (deprecated, retained for backward compatibility) +# Now uniformly uses GRPC_SERVER_CERT_SAN, no longer writes IP/hostnames into SAN +# Format: comma-separated, e.g. "DNS:localhost,IP:127.0.0.1,DNS:server1.example.com" +SERVER_CERT_SAN_LIST = os.environ.get('SERVER_CERT_SAN_LIST', '') \ No newline at end of file