diff --git a/fs_attachment/models/ir_binary.py b/fs_attachment/models/ir_binary.py index d9ea167d02..a28faa2d1b 100644 --- a/fs_attachment/models/ir_binary.py +++ b/fs_attachment/models/ir_binary.py @@ -70,16 +70,17 @@ def _get_stream_from( if stream.type == "fs": if mimetype: stream.mimetype = mimetype + elif not stream.mimetype: + stream.mimetype = default_mimetype if filename: stream.download_name = filename elif record and filename_field in record: stream.download_name = record[filename_field] or stream.download_name - - if ( - not get_extension(stream.download_name) - and stream.mimetype != "application/octet-stream" - ): - stream.download_name += guess_extension(stream.mimetype) or "" + if ( + not get_extension(stream.download_name) + and stream.mimetype != "application/octet-stream" + ): + stream.download_name += guess_extension(stream.mimetype) or "" return stream diff --git a/fs_attachment/readme/newsfragments/684.bugfix.rst b/fs_attachment/readme/newsfragments/684.bugfix.rst new file mode 100644 index 0000000000..554605b859 --- /dev/null +++ b/fs_attachment/readme/newsfragments/684.bugfix.rst @@ -0,0 +1 @@ +Fix a 500 error when downloading an attachment stored in a filesystem storage that has no mimetype: fall back to the default mimetype, as core does for attachments in the filestore. diff --git a/fs_attachment/tests/test_stream.py b/fs_attachment/tests/test_stream.py index 38ee7a4c0f..1a1abbe79d 100644 --- a/fs_attachment/tests/test_stream.py +++ b/fs_attachment/tests/test_stream.py @@ -240,3 +240,51 @@ def test_image_url_name_with_newline(self): }, assert_content=self.image, ) + + def _create_attachment_without_extension(self): + return ( + self.env["ir.attachment"] + .with_context(storage_location=self.temp_backend.code) + .create({"name": "report", "raw": self.content}) + ) + + def test_content_url_name_without_extension(self): + """The extension is guessed from the mimetype for fs streams.""" + attachment = self._create_attachment_without_extension() + self.assertEqual(attachment.mimetype, "text/plain") + self.authenticate("admin", "admin") + self.assertDownload( + f"/web/content/{attachment.id}", + headers={}, + assert_status_code=200, + assert_headers={"Content-Disposition": "inline; filename=report.txt"}, + assert_content=self.content, + ) + + def test_content_url_without_mimetype(self): + """An fs attachment without mimetype can still be downloaded. + + The mimetype can be empty for attachments written outside the ORM + (imports, migrations). As in core, it falls back to the default + mimetype instead of crashing. + """ + attachment = self._create_attachment_without_extension() + self.env.cr.execute( + "UPDATE ir_attachment SET mimetype = NULL WHERE id = %s", (attachment.id,) + ) + attachment.invalidate_recordset(["mimetype"]) + stream = self.env["ir.binary"]._get_stream_from(attachment) + self.assertEqual(stream.type, "fs") + self.assertEqual(stream.mimetype, "application/octet-stream") + self.assertEqual(stream.download_name, "report") + self.authenticate("admin", "admin") + self.assertDownload( + f"/web/content/{attachment.id}", + headers={}, + assert_status_code=200, + assert_headers={ + "Content-Type": "application/octet-stream", + "Content-Disposition": "inline; filename=report", + }, + assert_content=self.content, + )