fix: 隔离图片创作商品会话
This commit is contained in:
@@ -1,8 +1,11 @@
|
||||
import uuid
|
||||
|
||||
from django.db import transaction
|
||||
from django.db.models import Count, Exists, OuterRef, Q
|
||||
from django.utils import timezone
|
||||
from rest_framework import status
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.parsers import FormParser, MultiPartParser
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
@@ -55,12 +58,50 @@ class GenerateImageView(APIView):
|
||||
raw_refs = [s for s in raw_refs.split(",") if s.strip()]
|
||||
reference_image_ids = [str(r).strip() for r in raw_refs if str(r).strip()]
|
||||
team = get_current_team(request.user)
|
||||
if product_id:
|
||||
try:
|
||||
normalized_product_id = str(uuid.UUID(product_id))
|
||||
except (TypeError, ValueError, AttributeError):
|
||||
return Response({"detail": "商品 ID 无效"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
if not Product.objects.filter(team=team, id=normalized_product_id).exists():
|
||||
return Response({"detail": "商品不存在或不属于当前团队"}, status=status.HTTP_400_BAD_REQUEST)
|
||||
product_id = normalized_product_id
|
||||
# 对话归属:传了 id 就用现成对话(限本团队);没传则自动开一条新对话,标题取 prompt 前 24 字。
|
||||
conversation = None
|
||||
if conversation_id:
|
||||
conversation = ImageConversation.objects.filter(
|
||||
team=team, id=conversation_id, is_deleted=False, purged_at__isnull=True
|
||||
).first()
|
||||
# 合法重跑/补图必须能在当前对话里找到原批次;归属强制读取原批次,不信任当前路由或客户端值。
|
||||
original_batch_task = None
|
||||
if conversation is not None and batch_id:
|
||||
candidates = AITask.objects.filter(
|
||||
team=team,
|
||||
conversation=conversation,
|
||||
is_deleted=False,
|
||||
purged_at__isnull=True,
|
||||
request_payload__batch_id=batch_id,
|
||||
).order_by("created_at")
|
||||
original_batch_task = next(
|
||||
(task for task in candidates if not (task.request_payload or {}).get("batch_append")),
|
||||
None,
|
||||
)
|
||||
if original_batch_task is not None:
|
||||
product_id = str((original_batch_task.request_payload or {}).get("product_id") or "").strip() or None
|
||||
else:
|
||||
# 任意 UUID 不能伪装成可追加批次;降级为普通新批次。
|
||||
batch_id = None
|
||||
retry_of_task_id = None
|
||||
elif batch_id:
|
||||
batch_id = None
|
||||
retry_of_task_id = None
|
||||
|
||||
# 普通新批次不能写入其他商品/通用范围的旧会话;竞态或旧客户端出现错配时自动开正确范围会话。
|
||||
if conversation is not None:
|
||||
same_mode = conversation.mode == mode
|
||||
same_product = str(conversation.product_id or "") == str(product_id or "")
|
||||
if not same_mode or (mode == ImageConversation.Mode.IMAGE and original_batch_task is None and not same_product):
|
||||
conversation = None
|
||||
if conversation is None:
|
||||
conversation = ImageConversation.objects.create(
|
||||
team=team,
|
||||
@@ -415,8 +456,31 @@ class ImageConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
mode = self.request.query_params.get("mode", "").strip()
|
||||
if mode:
|
||||
queryset = queryset.filter(mode=mode)
|
||||
if self.action == "list":
|
||||
scope = self.request.query_params.get("scope", "").strip()
|
||||
product_id = self.request.query_params.get("product_id", "").strip()
|
||||
if scope and product_id:
|
||||
raise ValidationError({"detail": "scope 与 product_id 不能同时传入"})
|
||||
if scope:
|
||||
if scope != "unbound":
|
||||
raise ValidationError({"detail": "scope 仅支持 unbound"})
|
||||
queryset = queryset.filter(product__isnull=True)
|
||||
elif product_id:
|
||||
try:
|
||||
normalized_product_id = str(uuid.UUID(product_id))
|
||||
except (TypeError, ValueError, AttributeError) as exc:
|
||||
raise ValidationError({"detail": "商品 ID 无效"}) from exc
|
||||
if not Product.objects.filter(team=self.get_team(), id=normalized_product_id).exists():
|
||||
raise ValidationError({"detail": "商品不存在或不属于当前团队"})
|
||||
queryset = queryset.filter(product_id=normalized_product_id)
|
||||
return queryset
|
||||
|
||||
def perform_create(self, serializer):
|
||||
product = serializer.validated_data.get("product")
|
||||
if product is not None and product.team_id != self.get_team().id:
|
||||
raise ValidationError({"product": "商品不存在或不属于当前团队"})
|
||||
super().perform_create(serializer)
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
# 只联动该会话生成任务产出的 Asset,不碰用户上传的参考素材。
|
||||
with transaction.atomic():
|
||||
@@ -499,6 +563,7 @@ class ImageConversationViewSet(TeamScopedViewSetMixin, ModelViewSet):
|
||||
"prompt": (t.request_payload or {}).get("prompt", ""),
|
||||
"batch_id": (t.request_payload or {}).get("batch_id", ""),
|
||||
"ratio": (t.request_payload or {}).get("ratio") or "",
|
||||
"product_id": str((t.request_payload or {}).get("product_id") or ""),
|
||||
# 重跑/补图任务:不计入批次「应出张数」,前端据此正确渲染失败格数量
|
||||
"rerun": bool((t.request_payload or {}).get("batch_append")),
|
||||
"retry_of_task_id": str((t.request_payload or {}).get("retry_of_task_id") or ""),
|
||||
|
||||
Reference in New Issue
Block a user