Files
DEMO-AGENT/review_agent/file_summary/views.py

176 lines
5.9 KiB
Python

from django.contrib.auth.decorators import login_required
import logging
from pathlib import Path
from django.http import FileResponse, Http404, JsonResponse
from django.views.decorators.http import require_http_methods
from review_agent.models import Conversation, ExportedSummaryFile, FileAttachment
from review_agent.models import FileSummaryBatch, WorkflowEvent
from .events import serialize_event
from .storage import save_uploaded_attachment, serialize_attachment
logger = logging.getLogger("review_agent.file_summary.views")
def _conversation_for_user(user, conversation_id: int) -> Conversation:
conversation = Conversation.objects.filter(pk=conversation_id, user=user).first()
if not conversation:
raise Http404("对话不存在。")
return conversation
@require_http_methods(["POST", "GET"])
@login_required
def attachments(request, conversation_id: int):
conversation = _conversation_for_user(request.user, conversation_id)
if request.method == "POST":
files = request.FILES.getlist("files")
if not files:
return JsonResponse({"error": "请选择至少一个文件。"}, status=400)
logger.info(
"Attachment upload request received",
extra={
"conversation_id": conversation.pk,
"user_id": request.user.pk,
"file_count": len(files),
"filenames": [uploaded_file.name for uploaded_file in files],
},
)
saved = [
save_uploaded_attachment(
conversation=conversation,
user=request.user,
uploaded_file=uploaded_file,
)
for uploaded_file in files
]
logger.info(
"Attachment upload request finished",
extra={
"conversation_id": conversation.pk,
"attachment_ids": [attachment.pk for attachment in saved],
},
)
return JsonResponse({"attachments": [serialize_attachment(item) for item in saved]})
queryset = FileAttachment.objects.filter(conversation=conversation).order_by(
"original_name",
"-version_no",
)
logger.info(
"Attachment list requested",
extra={"conversation_id": conversation.pk, "attachment_count": queryset.count()},
)
return JsonResponse({"attachments": [serialize_attachment(item) for item in queryset]})
@require_http_methods(["DELETE"])
@login_required
def attachment_detail(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,
).first()
if not attachment:
raise Http404("附件不存在。")
attachment.upload_status = FileAttachment.UploadStatus.DELETED
attachment.is_active = False
attachment.save(update_fields=["upload_status", "is_active"])
logger.info(
"Attachment deleted",
extra={"conversation_id": conversation.pk, "attachment_id": attachment.pk},
)
return JsonResponse({"ok": True, "attachment": serialize_attachment(attachment)})
@require_http_methods(["GET"])
@login_required
def batch_status(request, batch_id: int):
batch = FileSummaryBatch.objects.filter(pk=batch_id, user=request.user).first()
if not batch:
raise Http404("批次不存在。")
return JsonResponse(
{
"batch": {
"id": batch.pk,
"batch_no": batch.batch_no,
"status": batch.status,
"product_name": batch.product_name,
"total_files": batch.total_files,
"success_files": batch.success_files,
"failed_files": batch.failed_files,
"total_pages": batch.total_pages,
},
"nodes": [
{
"node_code": node.node_code,
"node_name": node.node_name,
"status": node.status,
"progress": node.progress,
"message": node.message,
}
for node in batch.node_runs.order_by("id")
],
}
)
@require_http_methods(["GET"])
@login_required
def batch_events(request, batch_id: int):
batch = FileSummaryBatch.objects.filter(pk=batch_id, user=request.user).first()
if not batch:
raise Http404("批次不存在。")
after = request.GET.get("after") or "0"
try:
after_id = int(after)
except ValueError:
after_id = 0
events = WorkflowEvent.objects.filter(batch=batch, pk__gt=after_id).order_by("id")
return JsonResponse({"events": [serialize_event(event) for event in events]})
@require_http_methods(["GET"])
@login_required
def export_download(request, export_id: int):
exported = ExportedSummaryFile.objects.filter(
pk=export_id,
batch__user=request.user,
).first()
if not exported:
raise Http404("导出文件不存在。")
path = Path(exported.storage_path)
if not path.exists():
logger.warning(
"Export download missing file",
extra={"export_id": exported.pk, "storage_path": exported.storage_path},
)
return JsonResponse({"error": "文件不存在。"}, status=404)
content_type = (
"text/markdown; charset=utf-8"
if exported.export_type == ExportedSummaryFile.ExportType.MARKDOWN
else "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
logger.info(
"Export download started",
extra={
"export_id": exported.pk,
"batch_id": exported.batch_id,
"file_name": exported.file_name,
"content_type": content_type,
},
)
return FileResponse(
path.open("rb"),
as_attachment=True,
filename=exported.file_name,
content_type=content_type,
)