comment_submission.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116
  1. """Independent file and comment results for the existing comment POST."""
  2. import json
  3. import logging
  4. import re
  5. from datetime import datetime
  6. from fastapi import HTTPException
  7. from starlette.concurrency import run_in_threadpool
  8. from starlette.datastructures import UploadFile
  9. from .comments import save_comment
  10. from .config import settings
  11. from .file_storage import UploadError, get_files, save_upload
  12. from .forderung_history import history_context
  13. from .jinja_templates import templates
  14. from .permissions import current_principal
  15. logger = logging.getLogger(__name__)
  16. def _rollback(db):
  17. try:
  18. db.rollback()
  19. except Exception:
  20. logger.exception("Rollback nach fehlgeschlagenem Kommentar nicht möglich")
  21. async def submit_comment(request, pkey, db, scope):
  22. results, messages = [], []
  23. comment_saved = False
  24. async with request.form() as form:
  25. uploads = form.getlist("attachments")
  26. comment = form.get("Kommentar", "")
  27. try:
  28. ids = json.loads(form.get("attachment_ids", "[]"))
  29. except (ValueError, TypeError):
  30. raise HTTPException(400, "Die Dateiauswahl ist ungültig.")
  31. if (
  32. not isinstance(comment, str)
  33. or not isinstance(ids, list)
  34. or len(ids) != len(uploads)
  35. or any(not isinstance(item, UploadFile) for item in uploads)
  36. or any(not isinstance(item, str) or not re.fullmatch(r"[A-Za-z0-9_-]{1,80}", item) for item in ids)
  37. or len(set(ids)) != len(ids)
  38. ):
  39. raise HTTPException(400, "Die Dateiauswahl ist ungültig.")
  40. for identity, upload in zip(ids, uploads):
  41. result = {"id": identity, "name": upload.filename or "", "success": False}
  42. try:
  43. await run_in_threadpool(
  44. save_upload,
  45. pkey,
  46. upload.filename or "",
  47. upload.file,
  48. settings.UPLOAD_MAX_FILE_SIZE_MB * 1024 * 1024,
  49. )
  50. result["success"] = True
  51. except UploadError as error:
  52. result["error"] = str(error)
  53. except OSError:
  54. logger.exception("Datei konnte für %s nicht gespeichert werden", pkey)
  55. result["error"] = (
  56. "Speichern fehlgeschlagen. Bitte Speicherplatz, Dateisperren und Zugriffsrechte prüfen."
  57. )
  58. results.append(result)
  59. if comment.strip():
  60. try:
  61. save_comment(
  62. db, pkey, username=current_principal(request).username,
  63. timestamp=datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), comment=comment,
  64. )
  65. db.commit()
  66. comment_saved = True
  67. except Exception:
  68. _rollback(db)
  69. logger.exception("Kommentar konnte für %s nicht gespeichert werden", pkey)
  70. messages.append("Der Kommentar konnte nicht gespeichert werden. Der Text bleibt im Eingabefeld.")
  71. elif not uploads:
  72. messages.append("Bitte einen Kommentar oder Dateien hinzufügen.")
  73. context = {}
  74. history_available = True
  75. try:
  76. context.update(history_context(db, pkey, "kommentare", scope))
  77. except Exception:
  78. history_available = False
  79. _rollback(db)
  80. logger.exception("Kommentarhistorie konnte nicht aktualisiert werden")
  81. messages.append(
  82. "Die Kommentare konnten nicht aktualisiert werden. Bereits gespeicherte Ergebnisse bleiben erhalten."
  83. )
  84. files_available = True
  85. try:
  86. context["files"] = get_files(pkey)
  87. except OSError:
  88. files_available = False
  89. logger.exception("Dateiliste konnte nicht aktualisiert werden")
  90. messages.append(
  91. "Die Dateiliste konnte nicht aktualisiert werden. Bereits gespeicherte Dateien bleiben erhalten."
  92. )
  93. context.setdefault("forderungen_details", [pkey])
  94. context.update(
  95. history_available=history_available,
  96. files_available=files_available,
  97. files_oob=True,
  98. send_result={"files": results, "comment_saved": comment_saved, "messages": messages},
  99. )
  100. return templates.TemplateResponse(
  101. request,
  102. "forderungen/details/comment_submission.html",
  103. context,
  104. headers={} if history_available else {"HX-Reswap": "none"},
  105. )