fix: 统一视频自由创作标题
This commit is contained in:
@@ -504,7 +504,9 @@ def _store_free_video_media(*, task: AITask, media: str) -> Asset:
|
||||
id=asset_id,
|
||||
team=task.team,
|
||||
created_by=task.created_by,
|
||||
name=(prompt[:50] or "自由创作视频"),
|
||||
# Asset.name 是通用资产字段,受 255 字符上限约束;完整显示标题由资产接口从
|
||||
# 关联任务的 request_payload.prompt 派生,避免再出现 50 字符业务截断。
|
||||
name=(prompt[:255] or "自由创作视频"),
|
||||
asset_type=Asset.Type.VIDEO,
|
||||
source=Asset.Source.AI_GENERATED,
|
||||
category=Asset.Category.FREE_CREATE,
|
||||
|
||||
@@ -254,6 +254,24 @@ class FreeVideoAssetDeletionTests(TestCase):
|
||||
self.assertEqual(trash_data["video_url"], "http://tos.example/video.mp4")
|
||||
self.assertEqual(trash_data["thumbnail_url"], "http://tos.example/free-video-poster.jpg")
|
||||
|
||||
def test_asset_library_uses_full_free_video_title_and_searches_it(self):
|
||||
"""资产库应从关联任务恢复历史截断标题,并支持搜索完整标题的后半段。"""
|
||||
full_prompt = "@O1CN01anEq9u245Jn8Ccedn_!!4611686018427385691-0-s-完整后缀"
|
||||
self.task.request_payload["prompt"] = full_prompt
|
||||
self.task.save(update_fields=["request_payload"])
|
||||
self.asset.name = full_prompt[:50] # 模拟旧版本写库的截断数据
|
||||
self.asset.save(update_fields=["name"])
|
||||
|
||||
client = APIClient()
|
||||
client.force_authenticate(self.user)
|
||||
listing = client.get("/api/assets/?tab=video_creations&page_size=10").json()
|
||||
|
||||
self.assertEqual(listing["count"], 1)
|
||||
self.assertEqual(listing["results"][0]["display_name"], full_prompt)
|
||||
|
||||
search = client.get(f"/api/assets/?tab=video_creations&q={full_prompt[-4:]}&page_size=10").json()
|
||||
self.assertEqual([row["id"] for row in search["results"]], [str(self.asset.id)])
|
||||
|
||||
def test_direct_asset_delete_stays_in_asset_trash(self):
|
||||
client = APIClient()
|
||||
client.force_authenticate(self.user)
|
||||
|
||||
@@ -60,9 +60,25 @@ class AssetFileSerializer(serializers.ModelSerializer):
|
||||
|
||||
class AssetSerializer(serializers.ModelSerializer):
|
||||
files = AssetFileSerializer(many=True, read_only=True)
|
||||
# 自由创作视频的可见标题以任务原始提示词为准,Asset.name 仅保留通用资产名称/兼容搜索。
|
||||
# 这样历史的 50 字符截断资产也能直接恢复完整标题,无需数据迁移。
|
||||
display_name = serializers.SerializerMethodField()
|
||||
# 资产归属商品(只读):商品详情页据此只展示「该商品」的 AI 素材,而非全团队
|
||||
product = serializers.SerializerMethodField()
|
||||
|
||||
def get_display_name(self, obj):
|
||||
task = obj.origin_task
|
||||
if (
|
||||
obj.category == Asset.Category.FREE_CREATE
|
||||
and obj.asset_type == Asset.Type.VIDEO
|
||||
and task is not None
|
||||
and task.task_type == "free_video"
|
||||
):
|
||||
prompt = (task.request_payload or {}).get("prompt")
|
||||
if isinstance(prompt, str) and prompt.strip():
|
||||
return prompt.strip()
|
||||
return obj.name
|
||||
|
||||
def get_product(self, obj):
|
||||
"""解析资产所属商品:
|
||||
1) 独立生图(图片创作/模特图/平台套图):生图时已写入 metadata.product_id;
|
||||
@@ -81,6 +97,7 @@ class AssetSerializer(serializers.ModelSerializer):
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"display_name",
|
||||
"asset_type",
|
||||
"source",
|
||||
"category",
|
||||
|
||||
@@ -94,7 +94,7 @@ def _batch_name(name: str) -> str:
|
||||
|
||||
class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
# select_related 回溯链:asset → origin_task → project,供序列化器解析资产归属商品(避免 N+1)。
|
||||
# ★ defer 掉 AITask 的两个巨型 JSON 列(request/response payload):列表序列化只需 project.product_id,
|
||||
# ★ defer 掉 AITask 的两个巨型 JSON 列(request/response payload):绝大多数列表序列化只需 project.product_id,
|
||||
# 不 defer 的话 select_related 会把每个资产关联 AITask 的完整 AI 请求/响应 payload 全拖出来,
|
||||
# 200 个资产实测 100s+(数据越多越慢);defer 后 <1s、product 仍正常解析、无额外查询。
|
||||
queryset = (
|
||||
@@ -134,6 +134,12 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
return trash_qs.exclude(owned_by_deleted_free_creation).order_by("-updated_at")
|
||||
qs = qs.filter(is_deleted=False, purged_at__isnull=True) # 软删资产不出现在资产库
|
||||
p = self.request.query_params
|
||||
tab = p.get("tab")
|
||||
# 视频自由创作的显示标题来自关联任务的原始 prompt;只在该专用 tab 取回 JSON,避免普通资产列表
|
||||
# 重新加载巨型请求/响应 payload。
|
||||
if tab == "video_creations":
|
||||
# Django 没有 QuerySet.undefer();先清除默认延迟字段,再仅保留不需要的 response_payload 延迟加载。
|
||||
qs = qs.defer(None).defer("origin_task__response_payload")
|
||||
# 资产库列表/批次默认只展示「已加入资产库」的资产(in_library=True);未加入的工作台生成图不出现在这里。
|
||||
# 仅对列表型 action 过滤——retrieve / set-library / submit-review 等仍要能取到未加入的资产。
|
||||
# 任务中心要看「全部生成图」(含未入库),传 ?in_library=all 旁路;?in_library=false 只看未入库。
|
||||
@@ -145,8 +151,8 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
qs = qs.filter(in_library=False)
|
||||
else:
|
||||
qs = qs.filter(in_library=True)
|
||||
if p.get("tab"):
|
||||
qs = qs.filter(_tab_q(p["tab"]))
|
||||
if tab:
|
||||
qs = qs.filter(_tab_q(tab))
|
||||
if p.get("category"):
|
||||
qs = qs.filter(category=p["category"])
|
||||
if p.get("asset_type"):
|
||||
@@ -175,7 +181,13 @@ class AssetViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
| Q(product_images__product_id=pid)
|
||||
).distinct()
|
||||
if p.get("q"):
|
||||
qs = qs.filter(Q(name__icontains=p["q"]) | Q(description__icontains=p["q"]))
|
||||
query = p["q"]
|
||||
title_q = Q(name__icontains=query) | Q(description__icontains=query)
|
||||
# display_name 不落在 Asset 表;视频自由创作搜索需同时匹配关联任务的完整提示词,
|
||||
# 才能找到历史被截为前 50 字符的标题后半段。
|
||||
if tab == "video_creations":
|
||||
title_q |= Q(origin_task__task_type="free_video", origin_task__request_payload__prompt__icontains=query)
|
||||
qs = qs.filter(title_q)
|
||||
for key, val in p.items():
|
||||
if key.startswith("m_") and val:
|
||||
qs = qs.filter(**{f"metadata__{key[2:]}": val})
|
||||
|
||||
Reference in New Issue
Block a user