Skip to content

Commit

Permalink
Merge PR #403 into 18.0
Browse files Browse the repository at this point in the history
Signed-off-by lmignon
  • Loading branch information
OCA-git-bot committed Nov 10, 2024
2 parents 8558351 + 5ced4cb commit df192ce
Show file tree
Hide file tree
Showing 37 changed files with 7,597 additions and 0 deletions.
459 changes: 459 additions & 0 deletions fs_attachment/README.rst

Large diffs are not rendered by default.

2 changes: 2 additions & 0 deletions fs_attachment/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
from . import models
from .hooks import pre_init_hook
24 changes: 24 additions & 0 deletions fs_attachment/__manifest__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright 2017-2021 Camptocamp SA
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html)


{
"name": "Base Attachment Object Store",
"summary": "Store attachments on external object store",
"version": "18.0.1.0.0",
"author": "Camptocamp, ACSONE SA/NV, Odoo Community Association (OCA)",
"license": "AGPL-3",
"development_status": "Beta",
"category": "Knowledge Management",
"depends": ["fs_storage"],
"website": "https://github.com/OCA/storage",
"data": [
"security/fs_file_gc.xml",
"views/fs_storage.xml",
],
"external_dependencies": {"python": ["python_slugify"]},
"installable": True,
"auto_install": False,
"maintainers": ["lmignon"],
"pre_init_hook": "pre_init_hook",
}
95 changes: 95 additions & 0 deletions fs_attachment/fs_stream.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Copyright 2023 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
from __future__ import annotations

from odoo.http import STATIC_CACHE_LONG, Response, Stream, request
from odoo.tools import config

from .models.ir_attachment import IrAttachment

try:
from werkzeug.utils import send_file as _send_file
except ImportError:
from odoo.tools._vendor.send_file import send_file as _send_file


class FsStream(Stream):
fs_attachment = None

@classmethod
def from_fs_attachment(cls, attachment: IrAttachment) -> FsStream:
attachment.ensure_one()
if not attachment.fs_filename:
raise ValueError("Attachment is not stored into a filesystem storage")
return cls(
mimetype=attachment.mimetype,
download_name=attachment.name,
conditional=True,
etag=attachment.checksum,
type="fs",
size=attachment.file_size,
last_modified=attachment["write_date"],
fs_attachment=attachment,
)

def read(self):
if self.type == "fs":
with self.fs_attachment.open("rb") as f:
return f.read()
return super().read()

def get_response(self, as_attachment=None, immutable=None, **send_file_kwargs):
if self.type != "fs":
return super().get_response(
as_attachment=as_attachment, immutable=immutable, **send_file_kwargs
)
if as_attachment is None:
as_attachment = self.as_attachment
if immutable is None:
immutable = self.immutable
send_file_kwargs = {
"mimetype": self.mimetype,
"as_attachment": as_attachment,
"download_name": self.download_name,
"conditional": self.conditional,
"etag": self.etag,
"last_modified": self.last_modified,
"max_age": STATIC_CACHE_LONG if immutable else self.max_age,
"environ": request.httprequest.environ,
"response_class": Response,
**send_file_kwargs,
}
use_x_sendfile = self._fs_use_x_sendfile
# The file will be closed by werkzeug...
send_file_kwargs["use_x_sendfile"] = use_x_sendfile
if not use_x_sendfile:
f = self.fs_attachment.open("rb")
res = _send_file(f, **send_file_kwargs)
else:
x_accel_redirect = (
f"/{self.fs_attachment.fs_storage_code}{self.fs_attachment.fs_url_path}"
)
send_file_kwargs["use_x_sendfile"] = True
res = _send_file("", **send_file_kwargs)
# nginx specific headers
res.headers["X-Accel-Redirect"] = x_accel_redirect
# apache specific headers
res.headers["X-Sendfile"] = x_accel_redirect
res.headers["Content-Length"] = 0

if immutable and res.cache_control:
res.cache_control["immutable"] = None
return res

@classmethod
def _check_use_x_sendfile(cls, attachment: IrAttachment) -> bool:
return (
config["x_sendfile"]
and attachment.fs_url
and attachment.fs_storage_id.use_x_sendfile_to_serve_internal_url
)

@property
def _fs_use_x_sendfile(self) -> bool:
"""Return True if x-sendfile should be used to serve the file"""
return self._check_use_x_sendfile(self.fs_attachment)
38 changes: 38 additions & 0 deletions fs_attachment/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# Copyright 2023 ACSONE SA/NV
# License AGPL-3.0 or later (https://www.gnu.org/licenses/agpl).
import logging

from odoo.tools.sql import column_exists

_logger = logging.getLogger(__name__)


def pre_init_hook(env):
"""Pre init hook."""
# add columns for computed fields to avoid useless computation by the ORM
# when installing the module
cr = env.cr
if column_exists(cr, "ir_attachment", "fs_storage_id"):
return # columns already added; update probably failed partway
_logger.info("Add columns for computed fields on ir_attachment")
cr.execute(
"""
ALTER TABLE ir_attachment
ADD COLUMN fs_storage_id INTEGER;
ALTER TABLE ir_attachment
ADD FOREIGN KEY (fs_storage_id) REFERENCES fs_storage(id);
"""
)
cr.execute(
"""
ALTER TABLE ir_attachment
ADD COLUMN fs_url VARCHAR;
"""
)
cr.execute(
"""
ALTER TABLE ir_attachment
ADD COLUMN fs_storage_code VARCHAR;
"""
)
_logger.info("Columns added on ir_attachment")
Loading

0 comments on commit df192ce

Please sign in to comment.