fix: 完善商品—模特库-视频项目-自由创作等删除先进垃圾桶及可恢复等流程

This commit is contained in:
hh
2026-07-10 15:06:07 +08:00
parent 3e52942547
commit 564215dc53
28 changed files with 1086 additions and 190 deletions
+55 -7
View File
@@ -5,6 +5,7 @@ import uuid
from django.db import transaction
from django.db.models import Count, Q
from django.http import HttpResponse, JsonResponse, StreamingHttpResponse
from django.utils import timezone
from rest_framework import status
from rest_framework.decorators import action
from rest_framework.parsers import FormParser, MultiPartParser
@@ -246,7 +247,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
return # 项目内已有商品三视图,不覆盖
tri = (
Asset.objects.filter(
team=team, is_deleted=False,
team=team, is_deleted=False, purged_at__isnull=True,
metadata__product_id=str(product_id), metadata__view="three_view",
)
.order_by("-created_at")
@@ -267,7 +268,12 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
# 按采用版分镜数收口一次(幂等;只裁从未生成过的尾段,生成中/已出片的段绝不动)。
# 用轻量查询判定 + 同步,再交给 super 做重 prefetch 序列化(避免重查询跑两遍)。
proj = (
Project.objects.filter(pk=kwargs.get("pk"), **{self.team_field: self.get_team()})
Project.objects.filter(
pk=kwargs.get("pk"),
is_deleted=False,
purged_at__isnull=True,
**{self.team_field: self.get_team()},
)
.only("id", "current_stage", "status", "product_id")
.first()
)
@@ -289,6 +295,13 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
return super().retrieve(request, *args, **kwargs)
def get_queryset(self):
if self.action in ("trash", "restore", "purge"):
return (
Project.objects.select_related("product", "product__cover_asset")
.prefetch_related("product__cover_asset__files")
.filter(is_deleted=True, purged_at__isnull=True, **{self.team_field: self.get_team()})
.order_by("-updated_at")
)
# 列表:轻查询(只 select_related product + 计数注解),不做详情那串 12 个重 prefetch
# ——原列表把每个项目的 阶段/片段/故事板/时间线/资产文件全拉出,20 个项目实测 ~2s。
if self.action == "list":
@@ -300,8 +313,43 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
video_segment_count=Count("video_segments", distinct=True),
)
)
return qs.filter(**{self.team_field: self.get_team()}).order_by("-updated_at")
return super().get_queryset()
return qs.filter(
is_deleted=False,
purged_at__isnull=True,
**{self.team_field: self.get_team()},
).order_by("-updated_at")
return super().get_queryset().filter(is_deleted=False, purged_at__isnull=True)
def perform_destroy(self, instance):
instance.is_deleted = True
instance.save(update_fields=["is_deleted", "updated_at"])
@action(detail=False, methods=["get"], url_path="trash")
def trash(self, request):
"""垃圾桶:列出本团队已软删且未彻底隐藏的视频项目。"""
qs = self.filter_queryset(self.get_queryset())
page = self.paginate_queryset(qs)
serializer_cls = ProjectListSerializer
if page is not None:
return self.get_paginated_response(serializer_cls(page, many=True).data)
return Response(serializer_cls(qs, many=True).data)
@action(detail=True, methods=["post"], url_path="restore")
def restore(self, request, pk=None):
"""从垃圾桶恢复视频项目。"""
project = self.get_object()
project.is_deleted = False
project.purged_at = None
project.save(update_fields=["is_deleted", "purged_at", "updated_at"])
return Response(ProjectSerializer(project).data, status=status.HTTP_200_OK)
@action(detail=True, methods=["delete"], url_path="purge")
def purge(self, request, pk=None):
"""彻底删除 = 二级软删除:从垃圾桶隐藏,项目 DB 记录保留。"""
project = self.get_object()
project.purged_at = timezone.now()
project.save(update_fields=["purged_at", "updated_at"])
return Response(status=status.HTTP_204_NO_CONTENT)
@action(detail=True, methods=["get"], url_path="export-clips")
def export_clips(self, request, pk=None):
@@ -359,7 +407,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
@action(detail=False, methods=["get"])
def summary(self, request):
"""项目统计(总数 + 各状态计数),供仪表盘/侧栏徽标——只跑 COUNT,不拉项目对象。"""
base = Project.objects.filter(team=self.get_team())
base = Project.objects.filter(team=self.get_team(), is_deleted=False, purged_at__isnull=True)
by_status = {row["status"]: row["n"] for row in base.values("status").annotate(n=Count("id"))}
return Response({"total": base.count(), "by_status": by_status})
@@ -594,7 +642,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
if asset is None and asset_id:
# 跨团队可见的官方模特形象图 / 三视图 → 克隆进本团队再挂
official = (
Asset.objects.filter(id=asset_id, is_deleted=False)
Asset.objects.filter(id=asset_id, is_deleted=False, purged_at__isnull=True)
.filter(Q(model_as_portrait__is_official=True) | Q(model_as_triview__is_official=True))
.first()
)
@@ -659,7 +707,7 @@ class ProjectViewSet(TeamScopedViewSetMixin, ModelViewSet):
if source_portrait_id:
candidate_ids.add(str(source_portrait_id))
model = (
ModelEntity.objects.filter(portrait_asset_id__in=candidate_ids, is_deleted=False)
ModelEntity.objects.filter(portrait_asset_id__in=candidate_ids, is_deleted=False, purged_at__isnull=True)
.exclude(triview_asset__isnull=True)
.select_related("triview_asset")
.first()