fix: 测试清单遗留bug批量修复(15条:商品库/消费/团队/设置/消息中心/平台套图/资产库)
- R6 商品图删除不生效: 封面asset移交+绕开prefetch缓存, 前端封面缩图补删除入口 - R48 消息中心长文本与UI框错位: 胶囊单行省略+优先级标签禁压缩 - R70 账单流水筛选: 后端加ledger_type过滤, count随筛选变化, 切筛选重置页码 - R72 月限额改后不刷新: 团队页/消费页自取最新teamSettings, 两页同源 - R83/R86 平台套图提示词框加大+模型胶囊嵌入输入框(复用Pill组件) - R96 未读角标: 修KeyTextTransform注解过滤触发MySQL 3141致接口500 - R100 工作台记录丢失: 新增GET /api/ai/tasks/workbench/ 同源恢复, 废除localStorage方案 - R104 卖点添加按钮移出输入框, 新建抽屉+编辑态统一 - R106 商品库宽屏锁5列, 10个/页铺满两行 - R108 资产删除改软删+单张删除+回收站二次确认; trash页商品/资产双分区+restore/purge端点 - R109 生成图自动入资产库(person演员立绘除外), 迁移0008存量回填, 移除全部加入资产库入口 - R65 无需改动(22268d6已修, 测旧部署所致) 验证: tsc/build/check/makemigrations --check 通过, 后端测试210/210, perf-probe PASS Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -5,6 +5,7 @@ import uuid
|
||||
import requests
|
||||
from django.db import transaction
|
||||
from django.db.models import Q
|
||||
from django.db.models.deletion import ProtectedError
|
||||
from django.http import StreamingHttpResponse
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
@@ -99,11 +100,24 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
search_fields = ["name", "description"]
|
||||
ordering_fields = ["created_at", "updated_at", "name"]
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
"""R108:资产删除 = 回收站软删(is_deleted 置位),不再物理删行(原默认 hard delete 不可恢复)。
|
||||
资产库列表/批次/任务中心批次弹窗/工作台记录/对话历史引用的都是同一 Asset 查询(均过滤
|
||||
is_deleted),软删后各处即联动消失(R109「生成记录与资产库数据绑定」)。TOS 文件保留,可恢复。"""
|
||||
instance.is_deleted = True
|
||||
instance.save(update_fields=["is_deleted", "updated_at"])
|
||||
|
||||
def get_queryset(self):
|
||||
"""服务端过滤,支持各页按需懒加载(不再前端取全量后客户端切片)。
|
||||
参数:tab(资产库分桶)/category/asset_type/source/product/q(搜索)/m_<key>(metadata 过滤)/ordering。
|
||||
默认按 -created_at 排序——无 ORDER BY 时分页会重/漏。"""
|
||||
qs = super().get_queryset().filter(is_deleted=False) # 软删资产不出现在资产库
|
||||
qs = super().get_queryset()
|
||||
# R108 垃圾桶:trash/restore/purge 只看软删资产(镜像商品垃圾桶的写法)。
|
||||
# perform_destroy 软删时 update updated_at → -updated_at ≈ 删除时间倒序;不过滤 in_library
|
||||
# (未入库的工作台图删了也得能恢复)。基类 queryset 的 defer(AITask 巨型 payload)保持生效。
|
||||
if self.action in ("trash", "restore", "purge"):
|
||||
return qs.filter(is_deleted=True).order_by("-updated_at")
|
||||
qs = qs.filter(is_deleted=False) # 软删资产不出现在资产库
|
||||
p = self.request.query_params
|
||||
# 资产库列表/批次默认只展示「已加入资产库」的资产(in_library=True);未加入的工作台生成图不出现在这里。
|
||||
# 仅对列表型 action 过滤——retrieve / set-library / submit-review 等仍要能取到未加入的资产。
|
||||
@@ -244,6 +258,41 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
asset.save(update_fields=["in_library"])
|
||||
return Response({"id": str(asset.id), "in_library": asset.in_library})
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="trash")
|
||||
def trash(self, request):
|
||||
"""垃圾桶:列出本团队已软删(is_deleted=True)的资产 —— R108 删除弹窗承诺
|
||||
「删除的图片将回收到垃圾桶」,此口兑现承诺(与商品垃圾桶 /api/products/trash/ 同构)。"""
|
||||
qs = self.filter_queryset(self.get_queryset())
|
||||
page = self.paginate_queryset(qs)
|
||||
if page is not None:
|
||||
return self.get_paginated_response(self.get_serializer(page, many=True).data)
|
||||
return Response(self.get_serializer(qs, many=True).data)
|
||||
|
||||
@action(detail=True, methods=["post"], url_path="restore")
|
||||
def restore(self, request, pk=None):
|
||||
"""从垃圾桶恢复:is_deleted → False。资产库/批次/商品素材区引用的都是同一 Asset 查询
|
||||
(均过滤 is_deleted),恢复后各处即联动复现;in_library 保持删除前原样,不额外置位。"""
|
||||
asset = self.get_object() # trash 态 queryset:只有软删资产可恢复
|
||||
asset.is_deleted = False
|
||||
asset.save(update_fields=["is_deleted", "updated_at"])
|
||||
return Response(self.get_serializer(asset).data, status=status.HTTP_200_OK)
|
||||
|
||||
@action(detail=True, methods=["delete"], url_path="purge")
|
||||
def purge(self, request, pk=None):
|
||||
"""彻底删除(仅垃圾桶内软删资产可彻底删,不可恢复)。与商品 purge 语义对齐:
|
||||
硬删 DB 行(AssetFile 记录级联删除),TOS 对象不动(商品 purge 同样不触碰对象存储)。
|
||||
分镜/视频片段/商品图等以 PROTECT 引用资产:被引用的资产硬删会炸 ProtectedError,
|
||||
如实回 409(留在垃圾桶,仍可恢复),别让它 500。"""
|
||||
asset = self.get_object()
|
||||
try:
|
||||
asset.delete()
|
||||
except ProtectedError:
|
||||
return Response(
|
||||
{"detail": "该资产仍被商品图/分镜/视频片段等引用,无法彻底删除;可恢复后先解除引用"},
|
||||
status=status.HTTP_409_CONFLICT,
|
||||
)
|
||||
return Response(status=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
@action(detail=False, methods=["get"], url_path="video-packs")
|
||||
def video_packs(self, request):
|
||||
"""视频成品按「项目素材包」打包:每个项目的已采用视频片段 = 一个包(与「导出全部」同源:
|
||||
|
||||
Reference in New Issue
Block a user