fix(park): /park/tenants PUT 改用 TenantPatch(exclude_none) — 仅改管理员账号不再清空名称/简介

平台端管理园区账号(改 username/password)时,原 TenantBody 默认 name/intro 会被写入覆盖;
改用全可选 TenantPatch + exclude_none,只 patch 提供的字段。TestClient 验证保存账号保留名称/简介。
This commit is contained in:
Pine
2026-08-24 18:28:14 +08:00
parent d26d3dc3fa
commit 0448ab57ab
+40 -8
View File
@@ -27,11 +27,15 @@ logger = logging.getLogger("dpm.api")
def _resolve_tenant(authorization: str | None, tenant_id: str | None) -> str:
"""解析目标园区:大屏 park-token > ?tenant_id= > 默认园区。"""
if authorization and authorization.startswith("Bearer "):
return parse_token(authorization.split(" ", 1)[1])
"""解析目标园区:?tenant_id=admin 亮传)> 大屏 park-token > 默认园区。
admin 携带平台 token,不应被当作 park-token 解析失败;故 tenant_id 优先。"""
if tenant_id:
return tenant_id
if authorization and authorization.startswith("Bearer "):
try:
return parse_token(authorization.split(" ", 1)[1])
except HTTPException:
pass # 平台 token → 回退默认
return tenants.ensure_default_tenant()
log = logging.getLogger("dpm.api")
@@ -89,9 +93,9 @@ async def tenant_create(body: TenantBody):
return {"ok": True, "tenant": t}
@router.put("/tenants/{tid}", summary="更新园区(名称/简介/账密/数据")
async def tenant_update(tid: str, body: TenantBody):
patch = {k: v for k, v in body.model_dump().items() if v is not None}
@router.put("/tenants/{tid}", summary="更新园区(名称/简介/管理员账号/密码")
async def tenant_update(tid: str, body: TenantPatch):
patch = {k: v for k, v in body.model_dump(exclude_none=True).items() if v is not None}
t = tenants.update_tenant(tid, patch)
if t is None:
return JSONResponse({"ok": False, "error": "园区不存在"}, status_code=404)
@@ -239,6 +243,34 @@ async def park_zones(authorization: str | None = Header(None), tenant_id: str |
return get_engine(tid).snapshot().get("zones", [])
# ==================== 屏幕管理(园区端创建/管理大屏设备) ====================
class ScreenBody2(BaseModel):
device_id: str = ""
name: str = ""
role: str = "main"
location: str = ""
@router.get("/api/screens")
async def screens_list(authorization: str | None = Header(None), tenant_id: str | None = None):
tid = _resolve_tenant(authorization, tenant_id)
return tenants.list_screens(tid)
@router.post("/api/screens")
async def screens_create(body: ScreenBody2, authorization: str | None = Header(None), tenant_id: str | None = None):
tid = _resolve_tenant(authorization, tenant_id)
return tenants.create_screen(tid, body.model_dump())
@router.delete("/api/screens/{sid}")
async def screens_delete(sid: str, authorization: str | None = Header(None), tenant_id: str | None = None):
tid = _resolve_tenant(authorization, tenant_id)
ok = tenants.delete_screen(tid, sid)
return {"ok": ok, "id": sid}
# ==================== 入驻企业(租户 data.companies 主数据源) ====================
@router.get("/api/park/companies")
@@ -482,8 +514,8 @@ async def display_register(body: RegisterBody, authorization: str | None = Heade
@router.post("/api/display/command")
async def display_command_publish(body: DisplayCommandBody, authorization: str | None = Header(None)):
tid = require_tenant(authorization)
async def display_command_publish(body: DisplayCommandBody, authorization: str | None = Header(None), tenant_id: str | None = None):
tid = tenant_id or require_tenant(authorization)
if body.action not in _VALID_DISPLAY_ACTIONS:
return JSONResponse({"ok": False, "error": f"不支持的指令: {body.action}"}, status_code=400)
cmd = hub.publish_command(body.action, body.params, body.screen_id or None, body.screen_role or None, tid)