feat(attachments): 补充附件管理接口
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.db.models import Count, Q
|
||||
import json
|
||||
import logging
|
||||
from pathlib import Path
|
||||
|
||||
@@ -8,6 +10,7 @@ from django.views.decorators.http import require_http_methods
|
||||
from review_agent.models import Conversation, ExportedSummaryFile, FileAttachment, Message
|
||||
from review_agent.models import FileSummaryBatch, WorkflowEvent
|
||||
from .events import serialize_event
|
||||
from .paths import resolve_storage_path
|
||||
|
||||
from .storage import save_uploaded_attachment, serialize_attachment
|
||||
|
||||
@@ -68,7 +71,7 @@ def attachments(request, conversation_id: int):
|
||||
return JsonResponse({"attachments": [serialize_attachment(item) for item in queryset]})
|
||||
|
||||
|
||||
@require_http_methods(["DELETE"])
|
||||
@require_http_methods(["DELETE", "PATCH"])
|
||||
@login_required
|
||||
def attachment_detail(request, conversation_id: int, attachment_id: int):
|
||||
conversation = _conversation_for_user(request.user, conversation_id)
|
||||
@@ -80,6 +83,32 @@ def attachment_detail(request, conversation_id: int, attachment_id: int):
|
||||
if not attachment:
|
||||
raise Http404("附件不存在。")
|
||||
|
||||
if request.method == "PATCH":
|
||||
try:
|
||||
payload = json.loads(request.body.decode("utf-8") or "{}")
|
||||
except json.JSONDecodeError:
|
||||
return JsonResponse({"error": "JSON 格式错误。"}, status=400)
|
||||
|
||||
update_fields = []
|
||||
original_name = (payload.get("original_name") or "").strip()
|
||||
if original_name:
|
||||
attachment.original_name = Path(original_name).name
|
||||
update_fields.append("original_name")
|
||||
if "is_active" in payload:
|
||||
attachment.is_active = bool(payload["is_active"])
|
||||
update_fields.append("is_active")
|
||||
if update_fields:
|
||||
attachment.save(update_fields=update_fields)
|
||||
logger.info(
|
||||
"Attachment updated",
|
||||
extra={
|
||||
"conversation_id": conversation.pk,
|
||||
"attachment_id": attachment.pk,
|
||||
"update_fields": update_fields,
|
||||
},
|
||||
)
|
||||
return JsonResponse({"ok": True, "attachment": serialize_attachment(attachment)})
|
||||
|
||||
attachment.upload_status = FileAttachment.UploadStatus.DELETED
|
||||
attachment.is_active = False
|
||||
attachment.save(update_fields=["upload_status", "is_active"])
|
||||
@@ -90,6 +119,65 @@ def attachment_detail(request, conversation_id: int, attachment_id: int):
|
||||
return JsonResponse({"ok": True, "attachment": serialize_attachment(attachment)})
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@login_required
|
||||
def conversation_list(request):
|
||||
conversations = (
|
||||
Conversation.objects.filter(user=request.user)
|
||||
.annotate(
|
||||
attachment_count=Count(
|
||||
"file_attachments",
|
||||
filter=~Q(file_attachments__upload_status=FileAttachment.UploadStatus.DELETED),
|
||||
)
|
||||
)
|
||||
.order_by("-updated_at", "-id")
|
||||
)
|
||||
return JsonResponse(
|
||||
{
|
||||
"conversations": [
|
||||
{
|
||||
"id": conversation.pk,
|
||||
"title": conversation.title or "新对话",
|
||||
"updated_at": conversation.updated_at.isoformat(),
|
||||
"attachment_count": conversation.attachment_count,
|
||||
}
|
||||
for conversation in conversations
|
||||
]
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@require_http_methods(["GET"])
|
||||
@login_required
|
||||
def attachment_download(request, conversation_id: int, attachment_id: int):
|
||||
conversation = _conversation_for_user(request.user, conversation_id)
|
||||
attachment = FileAttachment.objects.filter(
|
||||
pk=attachment_id,
|
||||
conversation=conversation,
|
||||
user=request.user,
|
||||
).exclude(upload_status=FileAttachment.UploadStatus.DELETED).first()
|
||||
if not attachment:
|
||||
raise Http404("附件不存在。")
|
||||
|
||||
path = resolve_storage_path(attachment.storage_path)
|
||||
if not path.exists():
|
||||
logger.warning(
|
||||
"Attachment download missing file",
|
||||
extra={"attachment_id": attachment.pk, "storage_path": attachment.storage_path},
|
||||
)
|
||||
return JsonResponse({"error": "文件不存在。"}, status=404)
|
||||
logger.info(
|
||||
"Attachment download started",
|
||||
extra={"conversation_id": conversation.pk, "attachment_id": attachment.pk},
|
||||
)
|
||||
return FileResponse(
|
||||
path.open("rb"),
|
||||
as_attachment=True,
|
||||
filename=attachment.original_name,
|
||||
content_type=attachment.content_type or "application/octet-stream",
|
||||
)
|
||||
|
||||
|
||||
def _serialize_message(message: Message) -> dict[str, object]:
|
||||
return {
|
||||
"id": message.pk,
|
||||
|
||||
Reference in New Issue
Block a user