oca-ocb-core/odoo-bringout-oca-ocb-mail/mail/models/ir_attachment.py
Ernad Husremovic 2d3ee4855a 19.0 vanilla
2026-03-09 09:30:27 +01:00

121 lines
4.8 KiB
Python

# Part of Odoo. See LICENSE file for full copyright and licensing details.
import contextlib
from odoo import _, models, fields, api
from odoo.exceptions import AccessError, UserError
from odoo.tools.misc import limited_field_access_token, verify_limited_field_access_token
from odoo.addons.mail.tools.discuss import Store
class IrAttachment(models.Model):
_inherit = 'ir.attachment'
thumbnail = fields.Image()
has_thumbnail = fields.Boolean(compute="_compute_has_thumbnail")
@api.depends("thumbnail")
def _compute_has_thumbnail(self):
for attachment in self.with_context(bin_size=True):
attachment.has_thumbnail = bool(attachment.thumbnail)
def _has_attachments_ownership(self, attachment_tokens):
""" Checks if the current user has ownership of all attachments in the recordset.
Ownership is defined as either:
- Having 'write' access to the attachment.
- Providing a valid, scoped 'attachment_ownership' access token.
:param list attachment_tokens: A list of access tokens
"""
attachment_tokens = attachment_tokens or ([None] * len(self))
if len(attachment_tokens) != len(self):
raise UserError(_("An access token must be provided for each attachment."))
def is_owned(attachment, token):
if not attachment.exists():
return False
if attachment.sudo(False).has_access("write"):
return True
return token and verify_limited_field_access_token(
attachment, "id", token, scope="attachment_ownership"
)
return all(is_owned(att, tok) for att, tok in zip(self, attachment_tokens, strict=True))
def _post_add_create(self, **kwargs):
""" Overrides behaviour when the attachment is created through the controller
"""
super()._post_add_create(**kwargs)
self.register_as_main_attachment(force=False)
def register_as_main_attachment(self, force=True):
""" Registers this attachment as the main one of the model it is
attached to.
:param bool force: if set, the method always updates the existing main attachment
otherwise it only sets the main attachment if there is none.
"""
todo = self.filtered(lambda a: a.res_model and a.res_id)
if not todo:
return
for model, attachments in todo.grouped("res_model").items():
related_records = self.env[model].browse(attachments.mapped("res_id"))
if not hasattr(related_records, '_message_set_main_attachment_id'):
return
# this action is generic; if user cannot update record do not crash
# just skip update
for related_record, attachment in zip(related_records, attachments):
with contextlib.suppress(AccessError):
related_record._message_set_main_attachment_id(attachment, force=force)
def _delete_and_notify(self, message=None):
if message:
# sudo: mail.message - safe write just updating the date, because guests don't have the rights
message.sudo().write({}) # to make sure write_date on the message is updated
for attachment in self:
attachment._bus_send(
"ir.attachment/delete",
{
"id": attachment.id,
"message": (
{"id": message.id, "write_date": message.write_date} if message else None
),
},
)
self.unlink()
def _get_store_ownership_fields(self):
return [Store.Attr("ownership_token", lambda a: a._get_ownership_token())]
def _to_store_defaults(self, target):
return [
"checksum",
"create_date",
"file_size",
"has_thumbnail",
"mimetype",
"name",
Store.Attr("raw_access_token", lambda a: a._get_raw_access_token()),
"res_name",
"res_model",
Store.One("thread", [], as_thread=True),
Store.Attr("thumbnail_access_token", lambda a: a._get_thumbnail_token()),
"type",
"url",
]
def _get_ownership_token(self):
""" Returns a scoped limited access token that indicates ownership of the attachment when
using _has_attachments_ownership. If verified by verify_limited_field_access_token,
accessing the attachment bypasses the ACLs.
:rtype: str
"""
self.ensure_one()
return limited_field_access_token(self, field_name="id", scope="attachment_ownership")
def _get_thumbnail_token(self):
self.ensure_one()
return limited_field_access_token(self, "thumbnail", scope="binary")