Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
127 changes: 127 additions & 0 deletions _audit_serializers.py
Original file line number Diff line number Diff line change
@@ -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]}')
26 changes: 25 additions & 1 deletion application/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Expand Down
90 changes: 62 additions & 28 deletions taurus/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
)


Expand Down Expand Up @@ -842,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):
Expand Down Expand Up @@ -1269,6 +1277,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 (
Expand All @@ -1288,7 +1302,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
Expand All @@ -1307,24 +1323,30 @@ 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) ------------------
# 注意:这些 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,
Expand Down Expand Up @@ -1381,6 +1403,7 @@ class Meta:




class LogCommandSerializer(_EELogCommandSerializer):
"""Thin Wrapper — EE impl: taurus_ee.serializers.log_ext_center._EELogCommandSerializer"""
pass
Expand Down Expand Up @@ -1653,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,
Expand Down Expand Up @@ -2133,6 +2159,14 @@ 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 改动。
#
# 策略:这些 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
Expand Down
14 changes: 1 addition & 13 deletions taurus/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
)
Expand All @@ -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__)

Expand Down
Loading