from pathlib import Path from django.contrib.auth.decorators import login_required from django.http import FileResponse, Http404, HttpResponse from django.shortcuts import get_object_or_404, redirect, render from django.urls import reverse from django.views.decorators.http import require_http_methods from .forms import LetterTemplateForm, MailMergeJobForm from .models import LetterTemplate, MailMergeJob from .services.docx_renderer import extract_placeholders from .tasks import run_mailmerge @login_required def dashboard(request): templates = LetterTemplate.objects.all()[:20] jobs = MailMergeJob.objects.select_related("template")[:20] return render(request, "mailmerge/dashboard.html", {"templates": templates, "jobs": jobs}) @login_required @require_http_methods(["GET", "POST"]) def template_upload(request): form = LetterTemplateForm(request.POST or None, request.FILES or None) if request.method == "POST" and form.is_valid(): tpl = form.save(commit=False) tpl.created_by = request.user tpl.save() # Platzhalter extrahieren – Datei liegt jetzt auf Disk tpl.placeholders = extract_placeholders(Path(tpl.file.path)) tpl.save(update_fields=["placeholders"]) return redirect(reverse("template-detail", args=[tpl.id])) return render(request, "mailmerge/template_form.html", {"form": form}) @login_required def template_detail(request, pk): tpl = get_object_or_404(LetterTemplate, pk=pk) return render(request, "mailmerge/template_detail.html", {"template": tpl}) @login_required @require_http_methods(["GET", "POST"]) def job_create(request): form = MailMergeJobForm(request.POST or None, request.FILES or None) if request.method == "POST" and form.is_valid(): job = form.save(commit=False) job.created_by = request.user job.save() run_mailmerge.delay(str(job.id)) return redirect(reverse("job-detail", args=[job.id])) return render(request, "mailmerge/job_form.html", {"form": form}) @login_required def job_detail(request, pk): job = get_object_or_404( MailMergeJob.objects.select_related("template"), pk=pk ) logs = job.logs.all() # HTMX partials: nur das Status-Fragment ausliefern if request.headers.get("HX-Request"): return render(request, "mailmerge/_job_status.html", {"job": job, "logs": logs}) return render(request, "mailmerge/job_detail.html", {"job": job, "logs": logs}) @login_required def job_download(request, pk): """PDF-Download – via X-Accel-Redirect in Production, direkter Stream im Dev.""" job = get_object_or_404(MailMergeJob, pk=pk) if not job.result_pdf: raise Http404("Kein Ergebnis-PDF vorhanden.") # In Dev (settings.DEBUG) direkt streamen from django.conf import settings if settings.DEBUG: return FileResponse(job.result_pdf.open("rb"), as_attachment=True, filename=Path(job.result_pdf.name).name) # In Production: Nginx serviert die Datei via internem Mount. response = HttpResponse() response["Content-Type"] = "application/pdf" response["Content-Disposition"] = ( f'attachment; filename="{Path(job.result_pdf.name).name}"' ) # Pfad relativ zu MEDIA_ROOT, gemappt auf /protected-media/ relative = job.result_pdf.name response["X-Accel-Redirect"] = f"/protected-media/{relative}" return response