feat(library): 资产库重做为「成品库」— 图片成品三类 + 视频成品按项目素材包
定义:资产 = 平台产出的成品。原料(商品/模特)归各自的库、半成品(角色/三视图/场景/分镜)归项目, 资产库只装成品 —— tab 从 9 个收成 5 个。 - 后端 /api/assets/video-packs/:视频成品按项目素材包(与「导出全部」同源,取 VideoSegment.adopted_version 真实视频资产,给 TOS 直链 url 供 <video> 播放/取首帧);summary 改只数成品(tryon/kits/creations/others) - 资产库 _tab_q 加 others(我的上传+未归类兜底);角色/场景/商品图/素材 tab 移除(各回各家) - 前端 library:tab→模特上身图/平台套图/自由创作/视频成品/其他;视频成品=项目包卡(<video>首帧封面) → 点开弹窗看该项目所有片段(可播);图片成品/其他复用原网格 - 单测 VideoPacksTests:按项目分组 + 软删片段排除 验收:后端单测绿(基线 4 失败零新增);tsc+build 绿;无头 0 报错(5 tab + 视频包弹窗 3 段) 注:图片成品「按生成批次成组」待图片趴有真实数据时接线(当前 demo 已清空) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -93,8 +93,8 @@ class AssetSoftDeleteTests(TestCase):
|
||||
self.user, self.team = _mk_team("usd", "TeamSD")
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(self.user)
|
||||
self.live = Asset.objects.create(team=self.team, name="留存图", asset_type="image", source="ai_generated", category=Asset.Category.PERSON)
|
||||
self.dead = Asset.objects.create(team=self.team, name="废图", asset_type="image", source="ai_generated", category=Asset.Category.PERSON, is_deleted=True)
|
||||
self.live = Asset.objects.create(team=self.team, name="留存图", asset_type="image", source="ai_generated", category=Asset.Category.MODEL_TRYON)
|
||||
self.dead = Asset.objects.create(team=self.team, name="废图", asset_type="image", source="ai_generated", category=Asset.Category.MODEL_TRYON, is_deleted=True)
|
||||
|
||||
def test_list_excludes_soft_deleted(self):
|
||||
ids = {a["id"] for a in self.client.get("/api/assets/?page_size=50").json()["results"]}
|
||||
@@ -102,7 +102,43 @@ class AssetSoftDeleteTests(TestCase):
|
||||
self.assertNotIn(str(self.dead.id), ids)
|
||||
|
||||
def test_summary_excludes_soft_deleted(self):
|
||||
self.assertEqual(self.client.get("/api/assets/summary/").json()["people"], 1)
|
||||
self.assertEqual(self.client.get("/api/assets/summary/").json()["tryon"], 1)
|
||||
|
||||
|
||||
class VideoPacksTests(TestCase):
|
||||
"""视频成品按项目素材包分组(与导出同源:取 VideoSegment.adopted_version 的真实视频资产)。"""
|
||||
|
||||
def setUp(self):
|
||||
from apps.products.models import Product
|
||||
from apps.projects.models import Project, VideoSegment, VideoSegmentVersion
|
||||
from .models import AssetFile
|
||||
|
||||
self.user, self.team = _mk_team("uvp", "TeamVP")
|
||||
self.client = APIClient()
|
||||
self.client.force_authenticate(self.user)
|
||||
self.product = Product.objects.create(team=self.team, created_by=self.user, title="P")
|
||||
self.proj = Project.objects.create(team=self.team, name="项目甲", product=self.product, created_by=self.user)
|
||||
self.assets = []
|
||||
for i in range(2):
|
||||
a = Asset.objects.create(team=self.team, name=f"clip{i}", asset_type="video", source="ai_generated", category=Asset.Category.VIDEO_CLIP)
|
||||
AssetFile.objects.create(asset=a, object_key=f"k{i}.mp4", bucket="b", content_type="video/mp4", is_primary=True)
|
||||
seg = VideoSegment.objects.create(project=self.proj, sort_order=i)
|
||||
ver = VideoSegmentVersion.objects.create(video_segment=seg, asset=a, is_adopted=True)
|
||||
seg.adopted_version = ver
|
||||
seg.save(update_fields=["adopted_version"])
|
||||
self.assets.append(a)
|
||||
|
||||
def test_packs_grouped_by_project(self):
|
||||
data = self.client.get("/api/assets/video-packs/").json()
|
||||
self.assertEqual(len(data), 1)
|
||||
self.assertEqual(data[0]["project_name"], "项目甲")
|
||||
self.assertEqual(len(data[0]["clips"]), 2)
|
||||
self.assertIn("url", data[0]["clips"][0])
|
||||
|
||||
def test_soft_deleted_clip_excluded(self):
|
||||
Asset.objects.filter(id=self.assets[0].id).update(is_deleted=True)
|
||||
data = self.client.get("/api/assets/video-packs/").json()
|
||||
self.assertEqual(len(data[0]["clips"]), 1)
|
||||
|
||||
|
||||
class ReviewScopeTests(TestCase):
|
||||
|
||||
Reference in New Issue
Block a user