# 一次性灌演示数据(airshelf 团队)· 幂等 · 仅 ORM 插行,不碰表结构 # 运行: ./.venv/Scripts/python.exe seed_demo.py import os import django os.environ.setdefault("DJANGO_SETTINGS_MODULE", "airshelf.settings.development") django.setup() from django.db import transaction from django.utils import timezone from apps.common.api import get_current_team from apps.accounts.models import User from apps.assets.models import Asset, AssetFile from apps.products.models import Product, ProductImage, ProductSellingPoint from apps.projects.models import ( BaseAssetGroup, ExportJob, Project, ProjectStage, ScriptSegment, ScriptVersion, StoryboardFrame, StoryboardVersion, SubtitleTrack, BgmTrack, Timeline, TimelineClip, VideoSegment, VideoSegmentVersion, ) user = User.objects.get(username="airshelf") team = get_current_team(user) DEMO = "演示 · 透真玻尿酸补水面膜" if Product.objects.filter(team=team, title=DEMO).exists(): print("ALREADY SEEDED — skip") raise SystemExit src = list(AssetFile.objects.exclude(object_key="").values_list("object_key", "bucket")) if not src: print("NO SOURCE TOS OBJECTS — abort") raise SystemExit ctr = {"i": 0} now = timezone.now() def mkasset(name, category, atype="image", source=Asset.Source.AI_GENERATED): a = Asset.objects.create(team=team, created_by=user, name=name, asset_type=atype, source=source, category=category) ok, bk = src[ctr["i"] % len(src)] ctr["i"] += 1 AssetFile.objects.create(asset=a, object_key=ok, bucket=bk, content_type="image/png", size_bytes=120000, is_primary=True) return a with transaction.atomic(): prod_imgs = [mkasset("补水面膜 · 正面主图", "product_image", source=Asset.Source.UPLOAD), mkasset("补水面膜 · 质地细节", "product_image", source=Asset.Source.UPLOAD)] persons = [mkasset("模特 · 林夏(都市白领)", "person"), mkasset("模特 · 夜见(清新自然)", "person")] scenes = [mkasset("场景 · 深夜办公桌", "scene"), mkasset("场景 · 暖光化妆台", "scene")] clips = [mkasset("片段 · 场1 深夜办公桌", "video_clip"), mkasset("片段 · 场2 面膜特写", "video_clip"), mkasset("片段 · 场3 化妆台定格", "video_clip")] final = mkasset("成片 · 补水面膜痛点种草", "final_video", atype="video") bgm = mkasset("BGM · 轻电子节奏", "uncategorized", atype="audio") p = Product.objects.create( team=team, created_by=user, title=DEMO, brand="透真", category="美妆个护", target_audience="22-32 岁女性 · 敏感肌 · 办公室通勤", description="30g 玻尿酸大精华面膜,深夜急救补水,敏感肌可用。", cover_asset=prod_imgs[0], ) for i, a in enumerate(prod_imgs): ProductImage.objects.create(product=p, asset=a, sort_order=i, is_primary=(i == 0)) for i, (t, d) in enumerate([("玻尿酸双效保湿", "4 小时持久水润"), ("30g 大精华", "一片顶三片"), ("敏感肌可用", "无香精 · 无酒精")]): ProductSellingPoint.objects.create(product=p, title=t, detail=d, sort_order=i) Product.objects.create(team=team, created_by=user, title="演示 · 南卡 Lite Pro 蓝牙耳机", brand="南卡", category="数码 3C", description="主动降噪 · 35h 续航。") proj = Project.objects.create(team=team, created_by=user, name="演示 · 补水面膜 · 痛点种草 v1", product=p, status=Project.Status.COMPLETED, current_stage="export") for st in ["script", "base_assets", "storyboard", "video", "export"]: ProjectStage.objects.create(project=proj, stage=st, status=ProjectStage.Status.SUCCEEDED, started_at=now, completed_at=now) sv = ScriptVersion.objects.create(project=proj, title="痛点种草 v1", content="深夜办公→痛点→产品→使用→卖点收尾", source="ai", is_adopted=True) segs = [] for i, (dur, nar, vis) in enumerate([ (15, "加班三天,脸已经不能看了…", "深夜办公桌,疲惫特写"), (15, "还好我有这个透真玻尿酸面膜", "面膜包装特写"), (15, "敷完起来脸是软的,化妆都服帖", "化妆台,产品定格"), ]): segs.append(ScriptSegment.objects.create(script_version=sv, sort_order=i, duration_seconds=dur, narration=nar, visual_prompt=vis)) for kind, adopted, cands in [ (BaseAssetGroup.Kind.PRODUCT, prod_imgs[0], prod_imgs), (BaseAssetGroup.Kind.PERSON, persons[0], persons), (BaseAssetGroup.Kind.SCENE, scenes[0], scenes), ]: g = BaseAssetGroup.objects.create(project=proj, kind=kind, prompt=f"{kind} 基础资产", adopted_asset=adopted, version=1) g.candidate_assets.set(cands) sb = StoryboardVersion.objects.create(project=proj, prompt="统一商品/人物/场景风格,生成可指导视频的分镜", is_adopted=True) for i, (a, seg) in enumerate(zip([scenes[0], prod_imgs[1], scenes[1]], segs)): StoryboardFrame.objects.create(storyboard=sb, script_segment=seg, asset=a, sort_order=i, prompt=seg.visual_prompt) for i, (seg, clip) in enumerate(zip(segs, clips)): vs = VideoSegment.objects.create(project=proj, script_segment=seg, sort_order=i, target_duration_seconds=15, status=VideoSegment.Status.SUCCEEDED) vv = VideoSegmentVersion.objects.create(video_segment=vs, asset=clip, prompt=seg.visual_prompt, is_adopted=True) vs.adopted_version = vv vs.save(update_fields=["adopted_version"]) tl = Timeline.objects.create(project=proj, name="补水面膜成片", aspect_ratio="9:16", resolution="1080x1920", duration_seconds=45) for i, clip in enumerate(clips): TimelineClip.objects.create(timeline=tl, asset=clip, sort_order=i, start_ms=i * 15000, duration_ms=15000) SubtitleTrack.objects.create(timeline=tl, content=[{"start_ms": i * 15000, "text": seg.narration} for i, seg in enumerate(segs)], enabled=True) BgmTrack.objects.create(timeline=tl, asset=bgm, volume=60, start_ms=0) ExportJob.objects.create(timeline=tl, status=ExportJob.Status.SUCCEEDED, output_asset=final, progress=100) print("SEEDED ok | product=%s project=%s assets=%d products=%d" % ( p.id, proj.id, Asset.objects.filter(team=team).count(), Product.objects.filter(team=team).count()))