feat: restore source parity and harden agent runtime
This commit is contained in:
@@ -0,0 +1,299 @@
|
||||
from typing import List
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from app.database import get_db
|
||||
from app.base_schema import ResponseModel
|
||||
from zq_smart_table.permission.schema import (
|
||||
SmartTableRoleCreate, SmartTableRoleUpdate, SmartTableRoleResponse,
|
||||
CollaboratorCreate, CollaboratorUpdate, CollaboratorResponse,
|
||||
FieldPermBatchUpdate, FieldPermMatrix, FieldPermItem,
|
||||
RowRuleUpdate, RowRuleResponse,
|
||||
MyPermissionResponse,
|
||||
)
|
||||
from zq_smart_table.permission.service import PermissionService
|
||||
from zq_smart_table.permission.model import SmartTableRole
|
||||
|
||||
router = APIRouter(tags=["多维表格-权限"])
|
||||
|
||||
|
||||
# ==================== My Permission ====================
|
||||
|
||||
@router.get(
|
||||
"/tables/{table_id}/my-permission",
|
||||
response_model=MyPermissionResponse,
|
||||
summary="获取当前用户对该表的有效权限",
|
||||
)
|
||||
async def get_my_permission(table_id: str, db: AsyncSession = Depends(get_db)):
|
||||
perm = await PermissionService.get_my_permission(db, table_id)
|
||||
return MyPermissionResponse(**perm)
|
||||
|
||||
|
||||
# ==================== Role ====================
|
||||
|
||||
@router.get(
|
||||
"/tables/{table_id}/roles",
|
||||
response_model=List[SmartTableRoleResponse],
|
||||
summary="获取表角色列表",
|
||||
)
|
||||
async def get_roles(table_id: str, db: AsyncSession = Depends(get_db)):
|
||||
await PermissionService.ensure_system_roles(db, table_id)
|
||||
roles = await PermissionService.get_roles(db, table_id)
|
||||
return roles
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tables/{table_id}/roles",
|
||||
response_model=SmartTableRoleResponse,
|
||||
summary="创建自定义角色",
|
||||
)
|
||||
async def create_role(
|
||||
table_id: str,
|
||||
data: SmartTableRoleCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
role = await PermissionService.create_custom_role(
|
||||
db, table_id, data.name, data.capabilities
|
||||
)
|
||||
return role
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tables/{table_id}/roles/{role_id}",
|
||||
response_model=SmartTableRoleResponse,
|
||||
summary="更新角色",
|
||||
)
|
||||
async def update_role(
|
||||
table_id: str, role_id: str,
|
||||
data: SmartTableRoleUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
role = await PermissionService.update_role(
|
||||
db, role_id, name=data.name, capabilities=data.capabilities
|
||||
)
|
||||
if not role:
|
||||
raise HTTPException(status_code=404, detail="角色不存在")
|
||||
return role
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/tables/{table_id}/roles/{role_id}",
|
||||
response_model=ResponseModel,
|
||||
summary="删除自定义角色",
|
||||
)
|
||||
async def delete_role(
|
||||
table_id: str, role_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
success = await PermissionService.delete_role(db, role_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="角色不存在或为系统预置角色")
|
||||
return ResponseModel(message="删除成功")
|
||||
|
||||
|
||||
# ==================== Collaborator ====================
|
||||
|
||||
@router.get(
|
||||
"/tables/{table_id}/collaborators",
|
||||
response_model=List[CollaboratorResponse],
|
||||
summary="获取协作者列表",
|
||||
)
|
||||
async def get_collaborators(table_id: str, db: AsyncSession = Depends(get_db)):
|
||||
await PermissionService.require_table_access(db, table_id)
|
||||
collabs = await PermissionService.get_collaborators(db, table_id)
|
||||
|
||||
role_cache: dict = {}
|
||||
results = []
|
||||
for c in collabs:
|
||||
if c.role_id not in role_cache:
|
||||
from sqlalchemy import select
|
||||
r = await db.execute(
|
||||
select(SmartTableRole).where(SmartTableRole.id == c.role_id)
|
||||
)
|
||||
role_cache[c.role_id] = r.scalar_one_or_none()
|
||||
|
||||
role = role_cache.get(c.role_id)
|
||||
subject_name = None
|
||||
subject_avatar = None
|
||||
|
||||
if c.subject_type == "user":
|
||||
from core.user.model import User
|
||||
from sqlalchemy import select as sel
|
||||
u = await db.execute(sel(User).where(User.id == c.subject_id))
|
||||
user = u.scalar_one_or_none()
|
||||
if user:
|
||||
subject_name = user.name or user.username
|
||||
subject_avatar = user.avatar
|
||||
elif c.subject_type == "dept":
|
||||
from core.dept.model import Dept
|
||||
from sqlalchemy import select as sel2
|
||||
d = await db.execute(sel2(Dept).where(Dept.id == c.subject_id))
|
||||
dept = d.scalar_one_or_none()
|
||||
if dept:
|
||||
subject_name = dept.name
|
||||
|
||||
results.append(CollaboratorResponse(
|
||||
id=c.id,
|
||||
table_id=c.table_id,
|
||||
subject_type=c.subject_type,
|
||||
subject_id=c.subject_id,
|
||||
role_id=c.role_id,
|
||||
role_name=role.name if role else None,
|
||||
role_type=role.role_type if role else None,
|
||||
subject_name=subject_name,
|
||||
subject_avatar=subject_avatar,
|
||||
sys_create_datetime=c.sys_create_datetime,
|
||||
))
|
||||
|
||||
return results
|
||||
|
||||
|
||||
@router.post(
|
||||
"/tables/{table_id}/collaborators",
|
||||
response_model=CollaboratorResponse,
|
||||
summary="添加协作者",
|
||||
)
|
||||
async def add_collaborator(
|
||||
table_id: str,
|
||||
data: CollaboratorCreate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
collab = await PermissionService.add_collaborator(
|
||||
db, table_id, data.subject_type, data.subject_id, data.role_id,
|
||||
)
|
||||
return CollaboratorResponse(
|
||||
id=collab.id,
|
||||
table_id=collab.table_id,
|
||||
subject_type=collab.subject_type,
|
||||
subject_id=collab.subject_id,
|
||||
role_id=collab.role_id,
|
||||
sys_create_datetime=collab.sys_create_datetime,
|
||||
)
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tables/{table_id}/collaborators/{collab_id}",
|
||||
response_model=CollaboratorResponse,
|
||||
summary="更新协作者角色",
|
||||
)
|
||||
async def update_collaborator(
|
||||
table_id: str, collab_id: str,
|
||||
data: CollaboratorUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
collab = await PermissionService.update_collaborator_role(db, collab_id, data.role_id)
|
||||
if not collab:
|
||||
raise HTTPException(status_code=404, detail="协作者不存在")
|
||||
return CollaboratorResponse(
|
||||
id=collab.id,
|
||||
table_id=collab.table_id,
|
||||
subject_type=collab.subject_type,
|
||||
subject_id=collab.subject_id,
|
||||
role_id=collab.role_id,
|
||||
sys_create_datetime=collab.sys_create_datetime,
|
||||
)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/tables/{table_id}/collaborators/{collab_id}",
|
||||
response_model=ResponseModel,
|
||||
summary="移除协作者",
|
||||
)
|
||||
async def remove_collaborator(
|
||||
table_id: str, collab_id: str,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
success = await PermissionService.remove_collaborator(db, collab_id)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="协作者不存在")
|
||||
return ResponseModel(message="移除成功")
|
||||
|
||||
|
||||
# ==================== Field Permission ====================
|
||||
|
||||
@router.get(
|
||||
"/tables/{table_id}/field-permissions",
|
||||
response_model=List[FieldPermMatrix],
|
||||
summary="获取列权限矩阵",
|
||||
)
|
||||
async def get_field_permissions(table_id: str, db: AsyncSession = Depends(get_db)):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
await PermissionService.require_multidimensional_table(db, table_id)
|
||||
roles = await PermissionService.get_roles(db, table_id)
|
||||
|
||||
result = []
|
||||
for role in roles:
|
||||
perms = await PermissionService.get_field_permissions(db, table_id, role.id)
|
||||
fields = [FieldPermItem(field_id=fid, access=acc) for fid, acc in perms.items()]
|
||||
result.append(FieldPermMatrix(
|
||||
role_id=role.id,
|
||||
role_name=role.name,
|
||||
role_type=role.role_type,
|
||||
fields=fields,
|
||||
))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tables/{table_id}/field-permissions",
|
||||
response_model=ResponseModel,
|
||||
summary="批量更新列权限",
|
||||
)
|
||||
async def update_field_permissions(
|
||||
table_id: str,
|
||||
data: FieldPermBatchUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
await PermissionService.require_multidimensional_table(db, table_id)
|
||||
permissions = [{"field_id": p.field_id, "access": p.access} for p in data.permissions]
|
||||
await PermissionService.batch_set_field_permissions(db, table_id, data.role_id, permissions)
|
||||
return ResponseModel(message="更新成功")
|
||||
|
||||
|
||||
# ==================== Row Rule ====================
|
||||
|
||||
@router.get(
|
||||
"/tables/{table_id}/row-rules",
|
||||
response_model=List[RowRuleResponse],
|
||||
summary="获取行权限规则",
|
||||
)
|
||||
async def get_row_rules(table_id: str, db: AsyncSession = Depends(get_db)):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
await PermissionService.require_multidimensional_table(db, table_id)
|
||||
|
||||
from sqlalchemy import select
|
||||
from zq_smart_table.permission.model import SmartTableRowRule
|
||||
result = await db.execute(
|
||||
select(SmartTableRowRule).where(
|
||||
SmartTableRowRule.table_id == table_id,
|
||||
SmartTableRowRule.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
|
||||
@router.put(
|
||||
"/tables/{table_id}/row-rules",
|
||||
response_model=RowRuleResponse,
|
||||
summary="更新行权限规则",
|
||||
)
|
||||
async def update_row_rule(
|
||||
table_id: str,
|
||||
data: RowRuleUpdate,
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await PermissionService.require_capability(db, table_id, "manage_permission")
|
||||
await PermissionService.require_multidimensional_table(db, table_id)
|
||||
rule = await PermissionService.upsert_row_rule(
|
||||
db, table_id, data.role_id, data.rule_type, data.mode, data.conditions,
|
||||
)
|
||||
return rule
|
||||
@@ -0,0 +1,115 @@
|
||||
from sqlalchemy import Column, String, Boolean, Index
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
from sqlalchemy import JSON
|
||||
|
||||
from app.base_model import BaseModel
|
||||
|
||||
PermJSON = JSON().with_variant(JSONB(), "postgresql")
|
||||
|
||||
|
||||
SYSTEM_CAPABILITIES = {
|
||||
"owner": {
|
||||
"manage_table": True,
|
||||
"manage_permission": True,
|
||||
"manage_field": True,
|
||||
"manage_view": True,
|
||||
"add_record": True,
|
||||
"edit_record": True,
|
||||
"delete_record": True,
|
||||
"export_data": True,
|
||||
"import_data": True,
|
||||
},
|
||||
"manager": {
|
||||
"manage_table": False,
|
||||
"manage_permission": True,
|
||||
"manage_field": True,
|
||||
"manage_view": True,
|
||||
"add_record": True,
|
||||
"edit_record": True,
|
||||
"delete_record": True,
|
||||
"export_data": True,
|
||||
"import_data": True,
|
||||
},
|
||||
"editor": {
|
||||
"manage_table": False,
|
||||
"manage_permission": False,
|
||||
"manage_field": False,
|
||||
"manage_view": False,
|
||||
"add_record": True,
|
||||
"edit_record": True,
|
||||
"delete_record": False,
|
||||
"export_data": True,
|
||||
"import_data": False,
|
||||
},
|
||||
"viewer": {
|
||||
"manage_table": False,
|
||||
"manage_permission": False,
|
||||
"manage_field": False,
|
||||
"manage_view": False,
|
||||
"add_record": False,
|
||||
"edit_record": False,
|
||||
"delete_record": False,
|
||||
"export_data": False,
|
||||
"import_data": False,
|
||||
},
|
||||
}
|
||||
|
||||
ROLE_PRIORITY = {"owner": 100, "manager": 80, "editor": 60, "viewer": 40, "custom": 50}
|
||||
|
||||
|
||||
class SmartTableRole(BaseModel):
|
||||
"""多维表格 - 表角色定义"""
|
||||
__tablename__ = "smart_table_role"
|
||||
|
||||
table_id = Column(String(21), nullable=True, index=True, comment="所属表ID(null=系统预置)")
|
||||
name = Column(String(64), nullable=False, comment="角色名称")
|
||||
role_type = Column(String(20), nullable=False, default="custom", comment="owner/manager/editor/viewer/custom")
|
||||
capabilities = Column(PermJSON, default=dict, comment="能力配置JSON")
|
||||
is_system = Column(Boolean, default=False, comment="是否系统预置(不可删除)")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_smart_table_role_table", "table_id", "role_type"),
|
||||
)
|
||||
|
||||
|
||||
class SmartTableCollaborator(BaseModel):
|
||||
"""多维表格 - 表协作者"""
|
||||
__tablename__ = "smart_table_collaborator"
|
||||
|
||||
table_id = Column(String(21), nullable=False, index=True, comment="所属表ID")
|
||||
subject_type = Column(String(20), nullable=False, comment="授权对象类型: user/dept/role")
|
||||
subject_id = Column(String(21), nullable=False, index=True, comment="授权对象ID(用户/部门/角色)")
|
||||
role_id = Column(String(21), nullable=False, index=True, comment="表角色ID")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_smart_collab_table_subject", "table_id", "subject_type", "subject_id", unique=True),
|
||||
)
|
||||
|
||||
|
||||
class SmartTableFieldPerm(BaseModel):
|
||||
"""多维表格 - 列权限"""
|
||||
__tablename__ = "smart_table_field_perm"
|
||||
|
||||
table_id = Column(String(21), nullable=False, index=True, comment="所属表ID")
|
||||
role_id = Column(String(21), nullable=False, index=True, comment="表角色ID")
|
||||
field_id = Column(String(21), nullable=False, index=True, comment="字段ID")
|
||||
access = Column(String(10), default="write", comment="访问级别: write/read/hidden")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_smart_fperm_role_field", "table_id", "role_id", "field_id", unique=True),
|
||||
)
|
||||
|
||||
|
||||
class SmartTableRowRule(BaseModel):
|
||||
"""多维表格 - 行权限规则"""
|
||||
__tablename__ = "smart_table_row_rule"
|
||||
|
||||
table_id = Column(String(21), nullable=False, index=True, comment="所属表ID")
|
||||
role_id = Column(String(21), nullable=False, index=True, comment="表角色ID")
|
||||
rule_type = Column(String(10), nullable=False, comment="规则类型: view/edit")
|
||||
mode = Column(String(20), default="all", comment="模式: all/conditions/creator_only")
|
||||
conditions = Column(PermJSON, default=list, comment="过滤条件(复用视图filter结构)")
|
||||
|
||||
__table_args__ = (
|
||||
Index("ix_smart_row_rule_role_type", "table_id", "role_id", "rule_type", unique=True),
|
||||
)
|
||||
@@ -0,0 +1,122 @@
|
||||
from typing import Optional, List, Any, Dict
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
from app.base_schema import CSTDatetime
|
||||
|
||||
|
||||
# ==================== Role ====================
|
||||
|
||||
class SmartTableRoleCreate(BaseModel):
|
||||
name: str = Field(..., min_length=1, max_length=64, description="角色名称")
|
||||
role_type: str = Field(default="custom", description="角色类型")
|
||||
capabilities: Dict[str, bool] = Field(default_factory=dict, description="能力配置")
|
||||
|
||||
|
||||
class SmartTableRoleUpdate(BaseModel):
|
||||
name: Optional[str] = Field(None, min_length=1, max_length=64, description="角色名称")
|
||||
capabilities: Optional[Dict[str, bool]] = Field(None, description="能力配置")
|
||||
|
||||
|
||||
class SmartTableRoleResponse(BaseModel):
|
||||
id: str
|
||||
table_id: Optional[str] = None
|
||||
name: str
|
||||
role_type: str
|
||||
capabilities: Dict[str, bool] = {}
|
||||
is_system: bool = False
|
||||
sys_create_datetime: Optional[CSTDatetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ==================== Collaborator ====================
|
||||
|
||||
class CollaboratorCreate(BaseModel):
|
||||
subject_type: str = Field(..., description="授权对象类型: user/dept/role")
|
||||
subject_id: str = Field(..., description="授权对象ID")
|
||||
role_id: str = Field(..., description="表角色ID")
|
||||
|
||||
|
||||
class CollaboratorUpdate(BaseModel):
|
||||
role_id: str = Field(..., description="表角色ID")
|
||||
|
||||
|
||||
class CollaboratorResponse(BaseModel):
|
||||
id: str
|
||||
table_id: str
|
||||
subject_type: str
|
||||
subject_id: str
|
||||
role_id: str
|
||||
role_name: Optional[str] = None
|
||||
role_type: Optional[str] = None
|
||||
subject_name: Optional[str] = None
|
||||
subject_avatar: Optional[str] = None
|
||||
sys_create_datetime: Optional[CSTDatetime] = None
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ==================== Field Permission ====================
|
||||
|
||||
class FieldPermItem(BaseModel):
|
||||
field_id: str = Field(..., description="字段ID")
|
||||
access: str = Field(default="write", description="访问级别: write/read/hidden")
|
||||
|
||||
|
||||
class FieldPermBatchUpdate(BaseModel):
|
||||
role_id: str = Field(..., description="表角色ID")
|
||||
permissions: List[FieldPermItem] = Field(..., description="列权限列表")
|
||||
|
||||
|
||||
class FieldPermResponse(BaseModel):
|
||||
id: str
|
||||
table_id: str
|
||||
role_id: str
|
||||
field_id: str
|
||||
access: str = "write"
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
class FieldPermMatrix(BaseModel):
|
||||
"""列权限矩阵:按角色分组"""
|
||||
role_id: str
|
||||
role_name: str
|
||||
role_type: str
|
||||
fields: List[FieldPermItem] = []
|
||||
|
||||
|
||||
# ==================== Row Rule ====================
|
||||
|
||||
class RowRuleUpdate(BaseModel):
|
||||
role_id: str = Field(..., description="表角色ID")
|
||||
rule_type: str = Field(..., description="规则类型: view/edit")
|
||||
mode: str = Field(default="all", description="模式: all/conditions/creator_only")
|
||||
conditions: List[Dict[str, Any]] = Field(default_factory=list, description="过滤条件")
|
||||
|
||||
|
||||
class RowRuleResponse(BaseModel):
|
||||
id: str
|
||||
table_id: str
|
||||
role_id: str
|
||||
rule_type: str
|
||||
mode: str = "all"
|
||||
conditions: List[Dict[str, Any]] = []
|
||||
|
||||
model_config = ConfigDict(from_attributes=True)
|
||||
|
||||
|
||||
# ==================== My Permission ====================
|
||||
|
||||
class MyPermissionResponse(BaseModel):
|
||||
"""当前用户对某表的有效权限"""
|
||||
role_type: str = Field(description="角色类型: owner/manager/editor/viewer/custom/superadmin")
|
||||
role_name: str = Field(description="角色名称")
|
||||
capabilities: Dict[str, bool] = Field(default_factory=dict, description="能力配置")
|
||||
field_permissions: Dict[str, str] = Field(
|
||||
default_factory=dict,
|
||||
description="列权限映射: fieldId -> write/read/hidden"
|
||||
)
|
||||
row_view_mode: str = Field(default="all", description="行查看模式")
|
||||
row_edit_mode: str = Field(default="all", description="行编辑模式")
|
||||
@@ -0,0 +1,709 @@
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
|
||||
from sqlalchemy import select, delete as sa_delete
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
|
||||
from zq_smart_table.permission.model import (
|
||||
SmartTableRole, SmartTableCollaborator, SmartTableFieldPerm, SmartTableRowRule,
|
||||
SYSTEM_CAPABILITIES, ROLE_PRIORITY,
|
||||
)
|
||||
from zq_smart_table.model import SmartTable
|
||||
from utils.context import get_current_user_info_from_context
|
||||
|
||||
|
||||
SUPERADMIN_CAPABILITIES = {k: True for k in SYSTEM_CAPABILITIES["owner"]}
|
||||
|
||||
|
||||
class PermissionService:
|
||||
"""多维表格权限判定服务"""
|
||||
|
||||
# ─── 系统预置角色 ───
|
||||
|
||||
@classmethod
|
||||
async def ensure_system_roles(cls, db: AsyncSession, table_id: str) -> Dict[str, str]:
|
||||
"""确保表拥有 4 个系统预置角色,返回 {role_type: role_id}"""
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.table_id == table_id,
|
||||
SmartTableRole.is_system == True, # noqa: E712
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
existing = {r.role_type: r for r in result.scalars().all()}
|
||||
|
||||
role_map: Dict[str, str] = {}
|
||||
names = {"owner": "所有者", "manager": "管理者", "editor": "编辑者", "viewer": "只读者"}
|
||||
|
||||
for rt, caps in SYSTEM_CAPABILITIES.items():
|
||||
if rt in existing:
|
||||
role_map[rt] = existing[rt].id
|
||||
else:
|
||||
role = SmartTableRole(
|
||||
table_id=table_id,
|
||||
name=names[rt],
|
||||
role_type=rt,
|
||||
capabilities=caps,
|
||||
is_system=True,
|
||||
)
|
||||
db.add(role)
|
||||
await db.flush()
|
||||
role_map[rt] = role.id
|
||||
|
||||
await db.commit()
|
||||
return role_map
|
||||
|
||||
# ─── 初始化所有者 ───
|
||||
|
||||
@classmethod
|
||||
async def init_table_owner(cls, db: AsyncSession, table_id: str, user_id: str) -> None:
|
||||
"""创建表时自动将创建者设为所有者"""
|
||||
role_map = await cls.ensure_system_roles(db, table_id)
|
||||
owner_role_id = role_map["owner"]
|
||||
|
||||
existing = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.subject_type == "user",
|
||||
SmartTableCollaborator.subject_id == user_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
if existing.scalar_one_or_none():
|
||||
return
|
||||
|
||||
collab = SmartTableCollaborator(
|
||||
table_id=table_id,
|
||||
subject_type="user",
|
||||
subject_id=user_id,
|
||||
role_id=owner_role_id,
|
||||
)
|
||||
db.add(collab)
|
||||
await db.commit()
|
||||
|
||||
# ─── 获取有效角色 ───
|
||||
|
||||
@classmethod
|
||||
async def get_effective_role(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
user_id: str, dept_id: Optional[str] = None,
|
||||
role_ids: Optional[List[str]] = None,
|
||||
is_superuser: bool = False,
|
||||
) -> Optional[Dict[str, Any]]:
|
||||
"""
|
||||
获取用户对某表的有效角色。
|
||||
优先级: superadmin > user直接 > dept > system_role
|
||||
多个匹配时取权限最高的角色。
|
||||
"""
|
||||
if is_superuser:
|
||||
return {
|
||||
"role_type": "superadmin",
|
||||
"role_name": "超级管理员",
|
||||
"capabilities": SUPERADMIN_CAPABILITIES,
|
||||
"role_id": None,
|
||||
}
|
||||
|
||||
# 查同表创建者(兜底:如果 collaborator 记录缺失但用户是表的创建者)
|
||||
table = await db.execute(
|
||||
select(SmartTable).where(SmartTable.id == table_id, SmartTable.is_deleted == False) # noqa: E712
|
||||
)
|
||||
table_obj = table.scalar_one_or_none()
|
||||
if table_obj and table_obj.sys_creator_id == user_id:
|
||||
role_map = await cls.ensure_system_roles(db, table_id)
|
||||
owner_role_id = role_map["owner"]
|
||||
return {
|
||||
"role_type": "owner",
|
||||
"role_name": "所有者",
|
||||
"capabilities": dict(SYSTEM_CAPABILITIES["owner"]),
|
||||
"role_id": owner_role_id,
|
||||
}
|
||||
|
||||
# 查询所有匹配的协作者记录
|
||||
conditions = [
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
]
|
||||
|
||||
subject_conditions = [
|
||||
(SmartTableCollaborator.subject_type == "user") & (SmartTableCollaborator.subject_id == user_id)
|
||||
]
|
||||
if dept_id:
|
||||
subject_conditions.append(
|
||||
(SmartTableCollaborator.subject_type == "dept") & (SmartTableCollaborator.subject_id == dept_id)
|
||||
)
|
||||
if role_ids:
|
||||
for rid in role_ids:
|
||||
subject_conditions.append(
|
||||
(SmartTableCollaborator.subject_type == "role") & (SmartTableCollaborator.subject_id == rid)
|
||||
)
|
||||
|
||||
from sqlalchemy import or_
|
||||
conditions.append(or_(*subject_conditions))
|
||||
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(*conditions)
|
||||
)
|
||||
collabs = list(result.scalars().all())
|
||||
|
||||
if not collabs:
|
||||
return None
|
||||
|
||||
# 加载对应角色,取优先级最高的
|
||||
best_collab = None
|
||||
best_role = None
|
||||
best_priority = -1
|
||||
|
||||
for c in collabs:
|
||||
role_result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.id == c.role_id,
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
role = role_result.scalar_one_or_none()
|
||||
if not role:
|
||||
continue
|
||||
|
||||
# user 直接授权优先级额外 +10
|
||||
priority = ROLE_PRIORITY.get(role.role_type, 50)
|
||||
if c.subject_type == "user":
|
||||
priority += 10
|
||||
|
||||
if priority > best_priority:
|
||||
best_priority = priority
|
||||
best_collab = c
|
||||
best_role = role
|
||||
|
||||
if not best_role:
|
||||
return None
|
||||
|
||||
return {
|
||||
"role_type": best_role.role_type,
|
||||
"role_name": best_role.name,
|
||||
"capabilities": dict(best_role.capabilities) if best_role.capabilities else {},
|
||||
"role_id": best_role.id,
|
||||
}
|
||||
|
||||
# ─── 能力检查 ───
|
||||
|
||||
@classmethod
|
||||
async def check_capability(
|
||||
cls, db: AsyncSession, table_id: str, capability: str,
|
||||
) -> bool:
|
||||
"""检查当前用户是否具有某项能力"""
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
return False
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
if not role_info:
|
||||
return False
|
||||
|
||||
return role_info["capabilities"].get(capability, False)
|
||||
|
||||
@classmethod
|
||||
async def require_capability(
|
||||
cls, db: AsyncSession, table_id: str, capability: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""检查能力,无权限则抛 403"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="未登录")
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
if not role_info:
|
||||
raise HTTPException(status_code=403, detail="无权访问此表")
|
||||
|
||||
if not role_info["capabilities"].get(capability, False):
|
||||
raise HTTPException(status_code=403, detail=f"无权执行此操作({capability})")
|
||||
|
||||
return role_info
|
||||
|
||||
@classmethod
|
||||
async def require_table_access(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""检查表访问权限(至少 viewer),无权限则抛 403"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
raise HTTPException(status_code=401, detail="未登录")
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
if not role_info:
|
||||
raise HTTPException(status_code=403, detail="无权访问此表")
|
||||
|
||||
return role_info
|
||||
|
||||
# ─── 列权限 ───
|
||||
|
||||
@classmethod
|
||||
async def get_field_permissions(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
) -> Dict[str, str]:
|
||||
"""获取某角色的列权限映射: {field_id: access}"""
|
||||
result = await db.execute(
|
||||
select(SmartTableFieldPerm).where(
|
||||
SmartTableFieldPerm.table_id == table_id,
|
||||
SmartTableFieldPerm.role_id == role_id,
|
||||
SmartTableFieldPerm.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
return {fp.field_id: fp.access for fp in result.scalars().all()}
|
||||
|
||||
@classmethod
|
||||
async def get_accessible_field_ids(
|
||||
cls, db: AsyncSession, table_id: str, role_id: Optional[str],
|
||||
mode: str = "read",
|
||||
) -> Optional[List[str]]:
|
||||
"""
|
||||
获取角色可访问的字段ID列表。
|
||||
mode="read" 返回 access != "hidden" 的字段
|
||||
mode="write" 返回 access == "write" 的字段
|
||||
如果没有配置列权限,返回 None 表示不限制。
|
||||
"""
|
||||
if not role_id:
|
||||
return None
|
||||
|
||||
perms = await cls.get_field_permissions(db, table_id, role_id)
|
||||
if not perms:
|
||||
return None
|
||||
|
||||
if mode == "write":
|
||||
return [fid for fid, acc in perms.items() if acc == "write"]
|
||||
else:
|
||||
return [fid for fid, acc in perms.items() if acc != "hidden"]
|
||||
|
||||
@classmethod
|
||||
async def batch_set_field_permissions(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
permissions: List[Dict[str, str]],
|
||||
) -> None:
|
||||
"""批量设置列权限"""
|
||||
await db.execute(
|
||||
sa_delete(SmartTableFieldPerm).where(
|
||||
SmartTableFieldPerm.table_id == table_id,
|
||||
SmartTableFieldPerm.role_id == role_id,
|
||||
)
|
||||
)
|
||||
|
||||
for p in permissions:
|
||||
fp = SmartTableFieldPerm(
|
||||
table_id=table_id,
|
||||
role_id=role_id,
|
||||
field_id=p["field_id"],
|
||||
access=p.get("access", "write"),
|
||||
)
|
||||
db.add(fp)
|
||||
|
||||
await db.commit()
|
||||
|
||||
# ─── 行权限 ───
|
||||
|
||||
@classmethod
|
||||
async def get_row_rules(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
) -> List[SmartTableRowRule]:
|
||||
result = await db.execute(
|
||||
select(SmartTableRowRule).where(
|
||||
SmartTableRowRule.table_id == table_id,
|
||||
SmartTableRowRule.role_id == role_id,
|
||||
SmartTableRowRule.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def upsert_row_rule(
|
||||
cls, db: AsyncSession, table_id: str, role_id: str,
|
||||
rule_type: str, mode: str, conditions: List[Dict[str, Any]],
|
||||
) -> SmartTableRowRule:
|
||||
"""创建或更新行权限规则"""
|
||||
result = await db.execute(
|
||||
select(SmartTableRowRule).where(
|
||||
SmartTableRowRule.table_id == table_id,
|
||||
SmartTableRowRule.role_id == role_id,
|
||||
SmartTableRowRule.rule_type == rule_type,
|
||||
SmartTableRowRule.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
rule = result.scalar_one_or_none()
|
||||
|
||||
if rule:
|
||||
rule.mode = mode
|
||||
rule.conditions = conditions
|
||||
else:
|
||||
rule = SmartTableRowRule(
|
||||
table_id=table_id,
|
||||
role_id=role_id,
|
||||
rule_type=rule_type,
|
||||
mode=mode,
|
||||
conditions=conditions,
|
||||
)
|
||||
db.add(rule)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(rule)
|
||||
return rule
|
||||
|
||||
# ─── 协作者管理 ───
|
||||
|
||||
@classmethod
|
||||
async def get_collaborators(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> List[SmartTableCollaborator]:
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
).order_by(SmartTableCollaborator.sys_create_datetime)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def add_collaborator(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
subject_type: str, subject_id: str, role_id: str,
|
||||
) -> SmartTableCollaborator:
|
||||
existing = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.table_id == table_id,
|
||||
SmartTableCollaborator.subject_type == subject_type,
|
||||
SmartTableCollaborator.subject_id == subject_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
collab = existing.scalar_one_or_none()
|
||||
if collab:
|
||||
collab.role_id = role_id
|
||||
else:
|
||||
collab = SmartTableCollaborator(
|
||||
table_id=table_id,
|
||||
subject_type=subject_type,
|
||||
subject_id=subject_id,
|
||||
role_id=role_id,
|
||||
)
|
||||
db.add(collab)
|
||||
|
||||
await db.commit()
|
||||
await db.refresh(collab)
|
||||
return collab
|
||||
|
||||
@classmethod
|
||||
async def update_collaborator_role(
|
||||
cls, db: AsyncSession, collab_id: str, role_id: str,
|
||||
) -> Optional[SmartTableCollaborator]:
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.id == collab_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
collab = result.scalar_one_or_none()
|
||||
if not collab:
|
||||
return None
|
||||
collab.role_id = role_id
|
||||
await db.commit()
|
||||
await db.refresh(collab)
|
||||
return collab
|
||||
|
||||
@classmethod
|
||||
async def remove_collaborator(
|
||||
cls, db: AsyncSession, collab_id: str,
|
||||
) -> bool:
|
||||
result = await db.execute(
|
||||
select(SmartTableCollaborator).where(
|
||||
SmartTableCollaborator.id == collab_id,
|
||||
SmartTableCollaborator.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
collab = result.scalar_one_or_none()
|
||||
if not collab:
|
||||
return False
|
||||
collab.is_deleted = True
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
# ─── 角色管理 ───
|
||||
|
||||
@classmethod
|
||||
async def get_roles(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> List[SmartTableRole]:
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.table_id == table_id,
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
).order_by(SmartTableRole.sys_create_datetime)
|
||||
)
|
||||
return list(result.scalars().all())
|
||||
|
||||
@classmethod
|
||||
async def create_custom_role(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
name: str, capabilities: Dict[str, bool],
|
||||
) -> SmartTableRole:
|
||||
role = SmartTableRole(
|
||||
table_id=table_id,
|
||||
name=name,
|
||||
role_type="custom",
|
||||
capabilities=capabilities,
|
||||
is_system=False,
|
||||
)
|
||||
db.add(role)
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
return role
|
||||
|
||||
@classmethod
|
||||
async def update_role(
|
||||
cls, db: AsyncSession, role_id: str,
|
||||
name: Optional[str] = None, capabilities: Optional[Dict[str, bool]] = None,
|
||||
) -> Optional[SmartTableRole]:
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.id == role_id,
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
return None
|
||||
if name is not None:
|
||||
role.name = name
|
||||
if capabilities is not None:
|
||||
role.capabilities = capabilities
|
||||
await db.commit()
|
||||
await db.refresh(role)
|
||||
return role
|
||||
|
||||
@classmethod
|
||||
async def delete_role(
|
||||
cls, db: AsyncSession, role_id: str,
|
||||
) -> bool:
|
||||
result = await db.execute(
|
||||
select(SmartTableRole).where(
|
||||
SmartTableRole.id == role_id,
|
||||
SmartTableRole.is_system == False, # noqa: E712
|
||||
SmartTableRole.is_deleted == False, # noqa: E712
|
||||
)
|
||||
)
|
||||
role = result.scalar_one_or_none()
|
||||
if not role:
|
||||
return False
|
||||
role.is_deleted = True
|
||||
await db.commit()
|
||||
return True
|
||||
|
||||
# ─── 获取 my-permission ───
|
||||
|
||||
@classmethod
|
||||
async def get_my_permission(
|
||||
cls, db: AsyncSession, table_id: str,
|
||||
) -> Dict[str, Any]:
|
||||
"""获取当前用户对某表的完整权限信息"""
|
||||
user_info = get_current_user_info_from_context()
|
||||
if not user_info:
|
||||
return {
|
||||
"role_type": "none",
|
||||
"role_name": "无权限",
|
||||
"capabilities": {},
|
||||
"field_permissions": {},
|
||||
"row_view_mode": "none",
|
||||
"row_edit_mode": "none",
|
||||
}
|
||||
|
||||
role_info = await cls.get_effective_role(
|
||||
db, table_id,
|
||||
user_id=user_info.get("user_id", ""),
|
||||
dept_id=user_info.get("dept_id"),
|
||||
role_ids=user_info.get("role_ids", []),
|
||||
is_superuser=user_info.get("is_superuser", False),
|
||||
)
|
||||
|
||||
if not role_info:
|
||||
return {
|
||||
"role_type": "none",
|
||||
"role_name": "无权限",
|
||||
"capabilities": {},
|
||||
"field_permissions": {},
|
||||
"row_view_mode": "none",
|
||||
"row_edit_mode": "none",
|
||||
}
|
||||
|
||||
role_id = role_info.get("role_id")
|
||||
|
||||
field_perms: Dict[str, str] = {}
|
||||
row_view_mode = "all"
|
||||
row_edit_mode = "all"
|
||||
|
||||
if role_id:
|
||||
field_perms = await cls.get_field_permissions(db, table_id, role_id)
|
||||
|
||||
row_rules = await cls.get_row_rules(db, table_id, role_id)
|
||||
for rr in row_rules:
|
||||
if rr.rule_type == "view":
|
||||
row_view_mode = rr.mode
|
||||
elif rr.rule_type == "edit":
|
||||
row_edit_mode = rr.mode
|
||||
|
||||
out: Dict[str, Any] = {
|
||||
"role_type": role_info["role_type"],
|
||||
"role_name": role_info["role_name"],
|
||||
"capabilities": role_info["capabilities"],
|
||||
"field_permissions": field_perms,
|
||||
"row_view_mode": row_view_mode,
|
||||
"row_edit_mode": row_edit_mode,
|
||||
}
|
||||
|
||||
# 文档无列/行数据维度,列权限与行规则不对文档内容生效;归一化返回值以免与多维表格混淆
|
||||
tbl_row = await db.execute(
|
||||
select(SmartTable).where(SmartTable.id == table_id, SmartTable.is_deleted == False) # noqa: E712
|
||||
)
|
||||
tbl_obj = tbl_row.scalar_one_or_none()
|
||||
if tbl_obj and getattr(tbl_obj, "type", "table") == "document":
|
||||
out["field_permissions"] = {}
|
||||
out["row_view_mode"] = "all"
|
||||
out["row_edit_mode"] = "all"
|
||||
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
async def require_multidimensional_table(cls, db: AsyncSession, table_id: str) -> None:
|
||||
"""列权限、行权限仅适用于多维表格(type=table),文档页应使用协作者角色与能力位。"""
|
||||
from fastapi import HTTPException
|
||||
|
||||
result = await db.execute(
|
||||
select(SmartTable).where(SmartTable.id == table_id, SmartTable.is_deleted == False) # noqa: E712
|
||||
)
|
||||
tbl = result.scalar_one_or_none()
|
||||
if not tbl:
|
||||
raise HTTPException(status_code=404, detail="表不存在")
|
||||
if getattr(tbl, "type", "table") == "document":
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="文档不支持列权限与行权限配置,请通过协作者角色控制访问",
|
||||
)
|
||||
|
||||
# ─── 行权限过滤 ───
|
||||
|
||||
@classmethod
|
||||
async def build_row_filter_conditions(
|
||||
cls, db: AsyncSession, table_id: str, role_id: Optional[str],
|
||||
user_id: str, rule_type: str = "view",
|
||||
) -> list:
|
||||
"""
|
||||
根据行权限规则构建 SQLAlchemy 过滤条件列表。
|
||||
rule_type: "view" 或 "edit"
|
||||
返回空列表表示不限制。
|
||||
"""
|
||||
from zq_smart_table.model import SmartRecord
|
||||
from app.db_compat import json_extract
|
||||
|
||||
if not role_id:
|
||||
return []
|
||||
|
||||
row_rules = await cls.get_row_rules(db, table_id, role_id)
|
||||
rule = next((r for r in row_rules if r.rule_type == rule_type), None)
|
||||
if not rule or rule.mode == "all":
|
||||
return []
|
||||
|
||||
if rule.mode == "creator_only":
|
||||
return [SmartRecord.sys_creator_id == user_id]
|
||||
|
||||
if rule.mode == "conditions" and rule.conditions:
|
||||
from sqlalchemy import or_, and_
|
||||
conds = []
|
||||
for c in rule.conditions:
|
||||
fid = c.get("field_id")
|
||||
op = c.get("operator", "equals")
|
||||
val = c.get("value")
|
||||
if not fid:
|
||||
continue
|
||||
col = json_extract(SmartRecord.values, fid)
|
||||
if op == "equals":
|
||||
conds.append(col == str(val) if val is not None else col == None) # noqa: E711
|
||||
elif op == "contains":
|
||||
conds.append(col.ilike(f"%{val}%") if val else col == col)
|
||||
elif op == "isEmpty":
|
||||
conds.append(or_(col == None, col == "")) # noqa: E711
|
||||
elif op == "isNotEmpty":
|
||||
conds.append(and_(col != None, col != "")) # noqa: E711
|
||||
elif op == "greaterThan":
|
||||
from sqlalchemy import cast, String
|
||||
conds.append(cast(col, String) > str(val))
|
||||
elif op == "lessThan":
|
||||
from sqlalchemy import cast, String
|
||||
conds.append(cast(col, String) < str(val))
|
||||
return conds
|
||||
|
||||
return []
|
||||
|
||||
@classmethod
|
||||
async def check_row_edit_permission(
|
||||
cls, db: AsyncSession, table_id: str, record, role_id: Optional[str], user_id: str,
|
||||
) -> bool:
|
||||
"""检查当前用户是否有权编辑指定记录(基于行编辑权限规则)"""
|
||||
if not role_id:
|
||||
return True
|
||||
|
||||
row_rules = await cls.get_row_rules(db, table_id, role_id)
|
||||
rule = next((r for r in row_rules if r.rule_type == "edit"), None)
|
||||
if not rule or rule.mode == "all":
|
||||
return True
|
||||
|
||||
if rule.mode == "creator_only":
|
||||
return getattr(record, "sys_creator_id", None) == user_id
|
||||
|
||||
if rule.mode == "conditions" and rule.conditions:
|
||||
from app.db_compat import json_extract
|
||||
values = record.values or {}
|
||||
for c in rule.conditions:
|
||||
fid = c.get("field_id")
|
||||
op = c.get("operator", "equals")
|
||||
val = c.get("value")
|
||||
if not fid:
|
||||
continue
|
||||
cell_val = str(values.get(fid, ""))
|
||||
if op == "equals" and cell_val != str(val):
|
||||
return False
|
||||
if op == "contains" and str(val or "") not in cell_val:
|
||||
return False
|
||||
if op == "isEmpty" and cell_val != "":
|
||||
return False
|
||||
if op == "isNotEmpty" and cell_val == "":
|
||||
return False
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
# ─── 过滤记录值(去掉不可见字段) ───
|
||||
|
||||
@classmethod
|
||||
def filter_record_values(
|
||||
cls, values: Dict[str, Any], accessible_fields: Optional[List[str]],
|
||||
) -> Dict[str, Any]:
|
||||
"""过滤记录中不可见的字段值"""
|
||||
if accessible_fields is None:
|
||||
return values
|
||||
return {k: v for k, v in values.items() if k in accessible_fields}
|
||||
Reference in New Issue
Block a user