Files
survey-tools/survey_extra_fields/models/survey_user_input.py
Quentin Mondot c940289d40 [IMP] survey_extra_fields : handle file question on page navigation
* Show a stored file (after navigating back) the same way as a freshly
  selected one: a "filename + Remove file" chip, input hidden until removal.
* Keep the stored file when a page is re-submitted without a new selection
  (instead of overwriting it with a skipped answer).
* "Remove file" now sends a sentinel to actually delete the stored file,
  so the removal persists across navigation.
2026-06-10 17:23:31 +02:00

95 lines
3.7 KiB
Python

# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl.html).
from __future__ import annotations
import base64
import json
import os
from typing import TYPE_CHECKING
from odoo import _, models
from odoo.exceptions import UserError, ValidationError
if TYPE_CHECKING:
from odoo.addons.survey_extra_fields.models.survey_question import SurveyQuestion
class SurveyUserInput(models.Model):
_inherit = "survey.user_input"
def _save_lines(
self,
question: SurveyQuestion,
answer: str | None,
comment: str | None = None,
overwrite_existing: bool = True,
) -> None:
if question.question_type == "file":
old_answers = self.env["survey.user_input.line"].search([
("user_input_id", "=", self.id),
("question_id", "=", question.id),
])
if old_answers and not overwrite_existing:
raise UserError(_("This answer cannot be overwritten."))
if not answer and any(line.value_file for line in old_answers):
# No new file was submitted: a file input cannot be pre-filled
# by the browser when navigating back to a previous page, so an
# empty answer here does not mean the user removed their file.
# Keep the previously uploaded file instead of overwriting it
# with a skipped answer.
return
vals = {
"user_input_id": self.id,
"question_id": question.id,
"skipped": False,
"answer_type": "file",
}
file_data = json.loads(answer) if answer else {}
if file_data.get("cleared"):
# The user explicitly removed the file: drop the stored data and
# mark the line as skipped.
vals.update(answer_type=None, skipped=True, value_file=False, value_file_fname=False)
elif file_data:
file_b64 = file_data.get("data", "")
file_name = file_data.get("name", "")
self._check_file_constraints(question, file_b64, file_name)
vals["value_file"] = file_b64
vals["value_file_fname"] = file_name
else:
vals.update(answer_type=None, skipped=True)
if old_answers:
old_answers.write(vals)
else:
self.env["survey.user_input.line"].create(vals)
else:
return super()._save_lines(
question, answer, comment=comment, overwrite_existing=overwrite_existing
)
def _check_file_constraints(
self,
question: SurveyQuestion,
file_b64: str,
file_name: str
) -> None:
if question.max_file_size:
file_size = len(base64.b64decode(file_b64))
max_bytes = question.max_file_size * 1024 * 1024
if file_size > max_bytes:
raise ValidationError(
_("The file '%(name)s' exceeds the maximum allowed size of %(size)s MB.",
name=file_name, size=question.max_file_size)
)
if question.allowed_extensions:
allowed = [
allowed_extension.strip().lower()
for allowed_extension in question.allowed_extensions.split(",")
if allowed_extension.strip()
]
file_extension = os.path.splitext(file_name)[1].lower()
if file_extension not in allowed:
raise ValidationError(
_("The file '%(name)s' is not allowed. Accepted formats: %(exts)s.",
name=file_name, exts=question.allowed_extensions)
)