from django.db import models from apps.common.models import TeamOwnedModel, TimeStampedModel class Asset(TeamOwnedModel): class Type(models.TextChoices): IMAGE = "image", "Image" VIDEO = "video", "Video" AUDIO = "audio", "Audio" SUBTITLE = "subtitle", "Subtitle" DOCUMENT = "document", "Document" class Source(models.TextChoices): UPLOAD = "upload", "Upload" AI_GENERATED = "ai_generated", "AI Generated" EXPORTED = "exported", "Exported" SYSTEM = "system", "System" class Category(models.TextChoices): PERSON = "person", "Person" SCENE = "scene", "Scene" PRODUCT_IMAGE = "product_image", "Product Image" VIDEO_CLIP = "video_clip", "Video Clip" FINAL_VIDEO = "final_video", "Final Video" UPLOAD = "upload", "Upload" UNCATEGORIZED = "uncategorized", "Uncategorized" name = models.CharField(max_length=255) asset_type = models.CharField(max_length=24, choices=Type.choices) source = models.CharField(max_length=32, choices=Source.choices) category = models.CharField(max_length=32, choices=Category.choices, default=Category.UNCATEGORIZED) description = models.TextField(blank=True) origin_task = models.ForeignKey( "ai.AITask", on_delete=models.SET_NULL, null=True, blank=True, related_name="generated_assets", ) metadata = models.JSONField(default=dict, blank=True) is_deleted = models.BooleanField(default=False) # 火山人像素材库审核(仅真人 person 资产):"" = 未送审 / processing 审核中 / active 绿盾通过 / failed 红标(需改提示词重生) review_status = models.CharField(max_length=16, blank=True, db_default="") review_remote_id = models.CharField(max_length=128, blank=True, db_default="") review_error = models.TextField(blank=True, null=True, default="") # TEXT 不能有 DB 默认值(MySQL 1101),改用可空容忍「漏带字段」的插入 class Meta: indexes = [ # 资产库按团队 + 分类倒序时间翻页(products 详情素材趴 / 资产库) models.Index(fields=["team", "category", "-created_at"]), models.Index(fields=["team", "asset_type"]), ] def __str__(self) -> str: return self.name class AssetReviewGroup(TimeStampedModel): """一团队一火山人像素材组(真人资产审核统一上传到这里,单组可放 500 万)。""" team = models.OneToOneField("accounts.Team", on_delete=models.CASCADE, related_name="review_group") remote_group_id = models.CharField(max_length=128) name = models.CharField(max_length=128, blank=True) def __str__(self) -> str: return f"{self.team_id}:{self.remote_group_id}" class AssetFile(TimeStampedModel): asset = models.ForeignKey(Asset, on_delete=models.CASCADE, related_name="files") object_key = models.CharField(max_length=512) bucket = models.CharField(max_length=128) content_type = models.CharField(max_length=128, blank=True) size_bytes = models.BigIntegerField(default=0) checksum = models.CharField(max_length=128, blank=True) width = models.PositiveIntegerField(null=True, blank=True) height = models.PositiveIntegerField(null=True, blank=True) duration_ms = models.PositiveIntegerField(null=True, blank=True) preview_url = models.URLField(blank=True) is_primary = models.BooleanField(default=True) class Meta: indexes = [ models.Index(fields=["bucket", "object_key"]), ] def __str__(self) -> str: return self.object_key class AssetTag(TimeStampedModel): team = models.ForeignKey("accounts.Team", on_delete=models.CASCADE, related_name="asset_tags") name = models.CharField(max_length=64) class Meta: unique_together = [("team", "name")] def __str__(self) -> str: return self.name class AssetTagging(TimeStampedModel): asset = models.ForeignKey(Asset, on_delete=models.CASCADE, related_name="taggings") tag = models.ForeignKey(AssetTag, on_delete=models.CASCADE, related_name="taggings") class Meta: unique_together = [("asset", "tag")] class AssetUsage(TimeStampedModel): asset = models.ForeignKey(Asset, on_delete=models.CASCADE, related_name="usages") project = models.ForeignKey( "projects.Project", on_delete=models.CASCADE, null=True, blank=True, related_name="asset_usages", ) usage_type = models.CharField(max_length=64) context = models.JSONField(default=dict, blank=True)